EMS 知识库
首页 / knowledge/代码解析/common

aes.c

源文件路径:common/aes.c 所属模块:common(通用工具库) 代码行数:440

1. 文件职责

本文件实现标准的 AES-128/192/256 分组对称加解密算法(ECB / CBC 两种模式),并提供密钥扩展。它是 EMS 主控与充电桩(EVC-II、EVC)之间加密通信的算法核心:midware/evc/evc_ii.c 在获取到充电桩公钥后先用本文件的 AESDecrypt 解出一机一密私钥,随后每一帧 Modbus TCP/串口数据域的收发都分别经 AESEncrypt / AESDecrypt 处理;midware/evc/evc_upgrade.c 用 AESDecrypt 解密充电桩固件升级帧。本文件处在「协议帧数据域」这一环节,由 EVC 通信任务的收发函数同步调用(非 uCOS-II 任务实体本身,无自己的任务/信号量)。所有算法均在调用者线程栈上执行,不使用动态内存。

2. 关键数据结构与宏

名称类型说明
SBox[256]static const u8 表加密用 S 盒,标准 AES 替换表,定义于第 19-37 行
InvSBox[256]static const u8 表解密用逆 S 盒,定义于第 40-57 行
aes_key[32]全局变量全局私钥缓冲,midware/evc/evc_ii.c:493-494 用它接收解密出的 32 字节私钥
aesInfo[4]全局变量数组4 路 AES 上下文(AESInfo_t),索引 = 充电桩地址 - 1;本文件所有函数以指针方式操作其中一项
Nb宏(来自 aes.h)4,分组为 4 个 32 位字
BPOLY宏(来自 aes.h)0x1B,GF(2^8) 乘 2 约简多项式
AESInfo_t结构体(aes.h)上下文:Nk / Nr / type / mode / key[32] / pIV / expandKey[240]
Rcon[4]局部数组轮常量 {0x01,0x00,0x00,0x00},密钥扩展中每 Nk 步经 GfMultBy02 递推(第 319、354 行)
AESAddPKCS7Padding静态函数(已整段注释,第 268-274 行)PKCS7 填充,未参与编译
AESDelPKCS7Padding静态函数(已整段注释,第 284-296 行)PKCS7 去填充,未参与编译

3. 函数清单

函数名作用域说明
RShiftWordstatic对 4 字节字做循环右移(字节序轮转),密钥扩展的 RotWord 步骤(第 66 行)
XorBytesstatic两组数据逐字节异或,nCount 为 u8(第 84 行)
AddKeystatic状态与子密钥异或,固定长度 4*Nb = 16 字节(第 100 行)
SubstituteBytesstatic用给定盒(SBox / InvSBox)做字节替换,长度由调用方给(第 114 行)
ShiftRowsstatic状态按列存放时的行移位,bInvert 控制反向(第 130 行)
GfMultBy02staticGF(2^8) 域乘 2,最高位溢出时异或 BPOLY(第 159 行)
MixColumnsstatic列混合,bInvert 控制逆列混合(第 181 行)
BlockEncryptstatic单块加密:初始 AddKey + Nr 轮(SubBytes/ShiftRows/MixColumns/AddKey)(第 218 行)
BlockDecryptstatic单块解密:等价逆序轮(ShiftRows/InvSubBytes/AddKey/InvMixColumns)(第 242 行)
AES_data_Init全局把 aesInfo[id] 的 mode 置 ECB、type 置 AES256,不碰密钥(第 304 行)
AESInit全局由 type 推导 Nk/Nr,并完成密钥扩展写入 expandKey(第 316 行)
AESEncrypt全局对 dataLen 字节明文分块加密,支持就地(pPlainText == pCipherText)与 CBC(第 374 行)
AESDecrypt全局对 dataLen 字节密文分块解密,从最后一块倒推,支持 CBC(第 409 行)
AESAddPKCS7Padding—(被注释,未编译)PKCS7 填充,见第 268 行;调用点在 AESEncrypt 内也被注释
AESDelPKCS7Padding—(被注释,未编译)PKCS7 去填充,见第 284 行

共 13 个参与编译的函数(9 个 static + 4 个全局),另有 2 个被整段注释、不参与编译的静态函数。本文件不含函数指针与回调。

4. 核心逻辑解析

本文件无状态机、无定时器、无 uCOS-II 同步原语(不调用 OSTimeDly / OSSemPend / 邮箱),是纯计算库,加解密开销在调用者的任务上下文内同步消耗。

4.1 密钥扩展(AESInit,第 316-363 行)

  1. 根据 type 定参数(第 321-339 行):AES128 → Nr=10, Nk=4;AES192 → Nr=12, Nk=6;AES256 → Nr=14, Nk=8;default 回落为 AES128 参数(宽松容错,但不会报错)。
  2. memcpy(expandKey, key, 4*Nk) 把原始密钥放到扩展区首部(第 342 行)。
  3. 循环 i 从 Nk 到 Nb*(Nr+1)-1,每步新字 = 前一字的拷贝(第 346 行)后分三种处理:
    • i % Nk == 0:RShiftWord(RotWord)→ SubstituteBytes(SBox)(SubWord)→ XorBytes(Rcon),随后 Rcon[0] = GfMultBy02(Rcon[0])(第 348-355 行);
    • Nk > 6 且 i % Nk == Nb(仅 AES-256):只做一次 SubWord(第 356-359 行);
    • 其它:不变。
  4. 最后与新字左侧 Nk 个字异或(第 361 行)得到该轮子密钥字。

扩展后 expandKey 中共 Nb*(Nr+1) 个 32 位字,供 BlockEncrypt/BlockDecrypt 按 4*Nb*i 字节偏移取轮密钥。

4.2 单块加密(BlockEncrypt,第 218-233 行)

AddKey(state, expandKey[0])                      // 白化
for i = 1 .. Nr:
    SubstituteBytes(state, 16, SBox)             // SubBytes
    ShiftRows(state, 0)                          // 正向行移位
    if i != Nr: MixColumns(state, 0)             // 最后一轮不做列混合
    AddKey(state, &expandKey[4*Nb*i])            // 轮密钥加

ShiftRows 的地址公式按「列优先存放」约定:状态下标为 r + 4*c(第 141 行 pState[r + 4*c]),行 r 左旋 r 位(bInvert=0 时 temp = r,第 144 行)。

MixColumns 使用「全异或 + 乘 2」的优化写法(第 190-194 行):temp = a0^a1^a2^a3,result[k] = temp ^ a[k] ^ GfMultBy02(a[k]^a[k+1]);逆列混合额外经两次 GfMultBy02 构造 4(a0+a2)、4(a1+a3) 再回代(第 196-205 行)。

4.3 单块解密(BlockDecrypt,第 242-257 行)

AddKey(state, &expandKey[4*Nb*Nr])
for i = Nr .. 1:
    ShiftRows(state, 1)                          // 反向行移位(右旋 r 位)
    SubstituteBytes(state, 16, InvSBox)
    AddKey(state, &expandKey[4*Nb*(i-1)])
    if i != 1: MixColumns(state, 1)              // 逆列混合

采用等价逆密码(Equivalent Inverse Cipher)的轮序,省掉了单独的逆列混合顺序调整。

4.4 AESEncrypt 的分块与模式处理(第 374-398 行)

4.5 AESDecrypt 的分块与模式处理(第 409-438 行)

因为是从后往前解,前一块的明文此刻已经就绪,无需暂存中间值。

4.6 与 EVC 加密协议的配合(调用侧时序)

  1. 建立通信:evc_ii.c:480-484 先把 PUBLIC_KEY(32 字节)拷入 aesInfo[id].key,调 AES_data_Init(id) 与 AESInit(&aesInfo[id]),再用 0x03 0x10 读命令取回 32 字节密文。
  2. 换私钥:evc_ii.c:493-496 用公钥上下文 AESDecrypt 解出 aes_key[32],拷回 aesInfo[id].key 后重新 AESInit,并置 u8_evc_ii_encryption_flag = TRUE。
  3. 之后收帧:evc_ii.c:154 在 Modbus CRC 通过后,以 buf[0]-1 选上下文,AESDecrypt 解密数据域,再对解密结果重新校验一遍 Modbus CRC(双层校验:密文帧 CRC + 明文帧 CRC),失败则 *len = 0。
  4. 发帧:evc_ii.c:2187 以 AESEncrypt(&aesInfo[addr-1], sou_data, l_u8_enc_data, len + l_u8_remainder) 加密,l_u8_remainder 为 0xff 补齐字节数。
  5. 升级帧:evc_upgrade.c:140 与收帧同构。

4.7 加密协议帧格式(本文件只处理数据域,帧头由 evc_ii.c 拼装)

加密帧由 evc_ii.c:2190-2194 组装,字段顺序为:addr(1B) + len_hi/len_lo(2B,有效数据长度) + remainder(1B) + 密文数据域 + Modbus CRC16(2B)。接收侧 evc_ii.c:153 用 buf[3] 参与长度还原((buf[1]<<8|buf[2]) + buf[3]),即密文域长度 = 有效长度 + 填充长度。校验方式为 Modbus CRC-16(GetCrcCode_Modbus),超时/重试由 evc_ii_master_send_cmd(..., 500) 的 500 ms 超时与调用方的重试计数管理,与本文件无关。

5. 对外接口

本文件导出(供谁调用)

本文件调用的外部函数/变量

6. 依赖关系

7. 备注与风险点

  1. common 反向依赖 midware:aes.c 顶部 include 了 evc_ii.h 与 evc.h(第 5-6 行),但文件中没有任何符号用到它们。这会让通用层在编译期绑定充电桩协议头,属层次违规与无用依赖,建议删除。
  2. 无填充 + 长度非 16 倍数时静默留明文:AESEncrypt 只处理 dataLen/(4*Nb) 个完整块(第 387 行),余数部分原样保留或未定义初始化。midware/evc/evc_ii.c:2179 做了补齐,但这是调用方的隐式义务,没有断言/返回错误兜底。
  3. AESDecrypt 指针下溢:第 419 行 pPlainText += dataLen - 4*Nb。当 dataLen == 0 时该指针向低地址偏移 16 字节(在 C 中属未定义行为),虽因循环次数为 0 而未解引用,仍是可被静态检查工具标记的写法。
  4. CBC 分支不可用:aesInfo[4] 零初始化,pIV 恒为 NULL,AES_data_Init 又强制 ECB。若将来切到 AES_MODE_CBC,第 391/427 行会对 NULL 解引用。同时 AESEncrypt 的 CBC 分支会解引用 pIV 指向的外部 IV 缓冲(第 37 行注释要求调用方提供)。
  5. XorBytes 长度参数为 u8:第 84 行 u8 nCount。当前仅以 4(Rcon)或 4*Nb = 16 调用,不会溢出;但接口对更大长度会静默截断,属脆弱签名。
  6. Rcon 未按需扩展:第 319 行只初始化 4 字节,第 354 行仅对 Rcon[0] 递推。AES-256 需要 Rcon[1..6](0x02,0x04,...,0x40)用于第 7、14 轮以上的扩展字——本实现用「每 Nk 步 GfMultBy02 递推」的等价方式覆盖,其正确性依赖 Nk 与轮数的匹配;对 AES-128/192/256 三档标准参数是正确的,但若调用方传入非法 type(走 default)则参数被静默改写为 AES128,不会报错。
  7. AES_data_Init 不清 key/expandKey:换私钥前后 key 缓冲残留上一次内容,evc_ii.c 依赖自己在 memcpy 前 memset(:481、:493),属跨文件的隐式约定。
  8. aesInfo 越界无防护:AESDecrypt(&aesInfo[buf[0]-1], ...)(evc_ii.c:154)未拦截 buf[0] > 4,而数组仅 4 项。
  9. 大小写/命名混用:AES_data_Init 与 AESInit 命名风格不一致(前者下划线、后者驼峰),且第 297-303 行的注释标题误写成 AESInit,容易在检索时误判。
  10. printf 调试残余:被注释的 PKCS7 函数与本文件主体均无调试输出,但 #include "stdio.h"(第 1 行)在 Release 构建中属额外依赖,可清理。