2. AES加密指令基础

任务目标

本案例使用RISC-V标量加密扩展中的aes32esmi指令,实现AES-128加密算法的一轮(Round)变换。通过该案例,掌握aes32esmi指令的编码格式、操作语义及其在AES轮函数中的具体调用方式。

指令语义

aes32esmi指令执行AES的ShiftRows、SubBytes、MixColumns和AddRoundKey四个步骤中与单列相关的组合操作。其伪代码定义为:

aes32esmi rd, rs1, rs2, bs
// 从rs2中取字节bs(0~3),经S盒替换后与rs1的对应列进行MixColumns运算,
// 结果异或rs1后写入rd的低32位,rd高32位清零。

该指令一次处理一个字节,完成一轮加密需要4次aes32esmi调用(分别对应状态矩阵的4列),每列调用4次(对应4个字节),共16次调用。

核心代码实现

// aes_round.c
#include <stdint.h>

// RISC-V AES 扩展指令内联汇编封装
// bs: 字节选择(0~3),对应状态矩阵列中的字节位置
static inline uint32_t aes32esmi(uint32_t rs1, uint32_t rs2, int bs) {
    uint32_t rd;
    asm volatile (
        "aes32esmi %0, %1, %2, %3"
        : "=r"(rd)
        : "r"(rs1), "r"(rs2), "i"(bs)
    );
    return rd;
}

// AES-128 单轮加密(不含初始AddRoundKey)
// state: 16字节状态矩阵,按列主序排列为4个32位字
// round_key: 当前轮密钥,同样按列主序排列
void aes_round_enc(uint32_t state[4], const uint32_t round_key[4]) {
    uint32_t t[4];
    
    // 对每一列执行4次aes32esmi,完成ShiftRows+SubBytes+MixColumns
    for (int col = 0; col < 4; col++) {
        uint32_t col_in = state[col];
        uint32_t col_out = 0;
        
        // 字节0:行不移位
        col_out ^= aes32esmi(col_in, col_in, 0);
        // 字节1:行循环左移1位(对应列索引(col+1)%4)
        col_out ^= aes32esmi(col_in, state[(col+1)%4], 1);
        // 字节2:行循环左移2位
        col_out ^= aes32esmi(col_in, state[(col+2)%4], 2);
        // 字节3:行循环左移3位
        col_out ^= aes32esmi(col_in, state[(col+3)%4], 3);
        
        t[col] = col_out;
    }
    
    // 加轮密钥
    for (int i = 0; i < 4; i++) {
        state[i] = t[i] ^ round_key[i];
    }
}

// 测试入口:执行一轮加密并打印结果
#include <stdio.h>

int main() {
    // 测试向量(FIPS-197附录B中间值)
    // 初始状态:经过初始AddRoundKey后的值
    uint32_t state[4] = {
        0x00102030, 0x40506070, 0x8090a0b0, 0xc0d0e0f0
    };
    // 第一轮轮密钥
    uint32_t round_key[4] = {
        0x47460601, 0x1cdc0e82, 0x96b98b3d, 0x6aae8c7d
    };
    
    aes_round_enc(state, round_key);
    
    printf("Round output:\n");
    for (int i = 0; i < 4; i++) {
        printf("%08x ", state[i]);
    }
    printf("\n");
    
    return 0;
}

编译与运行

# 使用支持K扩展的GCC交叉编译器
riscv64-unknown-elf-gcc -march=rv32imac_zkse -mabi=ilp32 -o aes_round aes_round.c

# 在QEMU中运行(需支持K扩展)
qemu-riscv32 -cpu rv32,zkse=on ./aes_round

验证方法

程序输出应与FIPS-197标准中AES-128第一轮加密后的中间值一致。预期输出为:

Round output:
89d810e8 855aedab 0d4158e0 7d9cfe8f

该值对应FIPS-197附录B中第一轮Round 1 output的State值(按列主序排列)。若输出与此一致,则说明aes32esmi指令的调用方式、字节选择顺序及列混合逻辑均正确。

关键说明

  • aes32esmi的bs参数为立即数,表示从rs2中选取的字节位置,对应ShiftRows后该列中来自不同行的字节。
  • 状态矩阵按列主序存储:state[0]的低字节为第0行第0列元素,高字节为第3行第0列元素。
  • 本实现仅完成一轮加密变换,不含密钥扩展和初始AddRoundKey,适用于验证指令功能或作为完整AES实现的轮函数模块。