aes.c
AESInit,第 316-363 行)4.2 单块加密(BlockEncrypt,第 218-233 行)4.3 单块解密(BlockDecrypt,第 242-257 行)4.4 AESEncrypt 的分块与模式处理(第 374-398 行)4.5 AESDecrypt 的分块与模式处理(第 409-438 行)4.6 与 EVC 加密协议的配合(调用侧时序)4.7 加密协议帧格式(本文件只处理数据域,帧头由 evc_ii.c 拼装)5. 对外接口6. 依赖关系7. 备注与风险点源文件路径:
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. 函数清单
| 函数名 | 作用域 | 说明 |
|---|---|---|
RShiftWord | static | 对 4 字节字做循环右移(字节序轮转),密钥扩展的 RotWord 步骤(第 66 行) |
XorBytes | static | 两组数据逐字节异或,nCount 为 u8(第 84 行) |
AddKey | static | 状态与子密钥异或,固定长度 4*Nb = 16 字节(第 100 行) |
SubstituteBytes | static | 用给定盒(SBox / InvSBox)做字节替换,长度由调用方给(第 114 行) |
ShiftRows | static | 状态按列存放时的行移位,bInvert 控制反向(第 130 行) |
GfMultBy02 | static | GF(2^8) 域乘 2,最高位溢出时异或 BPOLY(第 159 行) |
MixColumns | static | 列混合,bInvert 控制逆列混合(第 181 行) |
BlockEncrypt | static | 单块加密:初始 AddKey + Nr 轮(SubBytes/ShiftRows/MixColumns/AddKey)(第 218 行) |
BlockDecrypt | static | 单块解密:等价逆序轮(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 行)
- 根据
type定参数(第 321-339 行):AES128 → Nr=10, Nk=4;AES192 → Nr=12, Nk=6;AES256 → Nr=14, Nk=8;default回落为 AES128 参数(宽松容错,但不会报错)。 memcpy(expandKey, key, 4*Nk)把原始密钥放到扩展区首部(第 342 行)。- 循环
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 行);- 其它:不变。
- 最后与新字左侧
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 行)
- 若明文/密文缓冲不同,先
memcpy一份到输出缓冲(第 378-381 行),随后在输出缓冲上就地加密。 - PKCS7 填充被注释掉(第 384 行),因此本函数不做任何填充,
dataLen必须是 16 的整数倍。 - 循环次数
dataLen / (4*Nb),每次前进 16 字节(第 387 行):- CBC:先把当前块与
pIV异或(第 391 行),再BlockEncrypt,然后把pIV指向刚产出的密文块(第 395 行)——即密文链接。 - ECB:直接
BlockEncrypt(pIV不参与)。
- CBC:先把当前块与
- 返回值恒为入参
dataLen(第 397 行)。若dataLen不是 16 的倍数,尾部的不足块不会被加密,而是保持memcpy出来的明文副本;调用方(evc_ii.c:2179-2186)自行用0xff补齐到 32 字节倍数来规避这一点。
4.5 AESDecrypt 的分块与模式处理(第 409-438 行)
- 同样先
memcpy保证就地/异地均可(第 413-416 行)。 - 指针先跳到最后一块:
pPlainText += dataLen - 4*Nb(第 419 行),然后倒序遍历dataLen/(4*Nb)次,每次回退 16 字节(第 420 行)。 - 每块先
BlockDecrypt,CBC 模式下再异或:- 若是最后处理的一块(
i == 1,即密文第一块),异或aesInfoP->pIV(第 427 行); - 否则异或已解密好的前一块(
pPlainText - 4*Nb,第 431 行)。
- 若是最后处理的一块(
因为是从后往前解,前一块的明文此刻已经就绪,无需暂存中间值。
- 返回值恒为
dataLen(第 437 行),PKCS7 去填充同样被注释掉。
4.6 与 EVC 加密协议的配合(调用侧时序)
- 建立通信:
evc_ii.c:480-484先把PUBLIC_KEY(32 字节)拷入aesInfo[id].key,调AES_data_Init(id)与AESInit(&aesInfo[id]),再用0x03 0x10读命令取回 32 字节密文。 - 换私钥:
evc_ii.c:493-496用公钥上下文AESDecrypt解出aes_key[32],拷回aesInfo[id].key后重新AESInit,并置u8_evc_ii_encryption_flag = TRUE。 - 之后收帧:
evc_ii.c:154在 Modbus CRC 通过后,以buf[0]-1选上下文,AESDecrypt解密数据域,再对解密结果重新校验一遍 Modbus CRC(双层校验:密文帧 CRC + 明文帧 CRC),失败则*len = 0。 - 发帧:
evc_ii.c:2187以AESEncrypt(&aesInfo[addr-1], sou_data, l_u8_enc_data, len + l_u8_remainder)加密,l_u8_remainder为0xff补齐字节数。 - 升级帧:
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. 对外接口
本文件导出(供谁调用)
- 全局变量:
u8 aes_key[32](第 8 行)、AESInfo_t aesInfo[4](第 10 行) - 函数:
AES_data_Init、AESInit、AESEncrypt、AESDecrypt - 调用方:
midware/evc/evc_ii.c(:154、:481-484、:493-496、:2187)、midware/evc/evc_upgrade.c(:140)
本文件调用的外部函数/变量
- 标准库:
memcpy、memset(在已注释代码中)、printf(已注释代码中未使用),来自string.h(第 2 行) - 无 EMS 其他模块的函数调用;
sou_data、l_u8_recv_data等缓冲由调用方提供。
6. 依赖关系
- 包含头文件:
stdio.h、string.h、stdint.h、aes.h、evc_ii.h、evc.h(第 1-6 行)。其中evc_ii.h、evc.h在aes.c中未被实际使用(无任何符号引用),属冗余 include,会把充电桩模块的宏与类型一并引入common层,形成 common → midware 的反向依赖。 - 被依赖:
midware/evc/evc_ii.c、midware/evc/evc_upgrade.c。工程 Keil 目标user/EMS_3_0.uvprojx中包含aes.c,参与编译。
7. 备注与风险点
common反向依赖midware:aes.c顶部 include 了evc_ii.h与evc.h(第 5-6 行),但文件中没有任何符号用到它们。这会让通用层在编译期绑定充电桩协议头,属层次违规与无用依赖,建议删除。- 无填充 + 长度非 16 倍数时静默留明文:
AESEncrypt只处理dataLen/(4*Nb)个完整块(第 387 行),余数部分原样保留或未定义初始化。midware/evc/evc_ii.c:2179做了补齐,但这是调用方的隐式义务,没有断言/返回错误兜底。 AESDecrypt指针下溢:第 419 行pPlainText += dataLen - 4*Nb。当dataLen == 0时该指针向低地址偏移 16 字节(在 C 中属未定义行为),虽因循环次数为 0 而未解引用,仍是可被静态检查工具标记的写法。- CBC 分支不可用:
aesInfo[4]零初始化,pIV恒为 NULL,AES_data_Init又强制 ECB。若将来切到AES_MODE_CBC,第 391/427 行会对 NULL 解引用。同时AESEncrypt的 CBC 分支会解引用pIV指向的外部 IV 缓冲(第 37 行注释要求调用方提供)。 XorBytes长度参数为u8:第 84 行u8 nCount。当前仅以4(Rcon)或4*Nb = 16调用,不会溢出;但接口对更大长度会静默截断,属脆弱签名。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,不会报错。AES_data_Init不清key/expandKey:换私钥前后key缓冲残留上一次内容,evc_ii.c依赖自己在memcpy前memset(:481、:493),属跨文件的隐式约定。aesInfo越界无防护:AESDecrypt(&aesInfo[buf[0]-1], ...)(evc_ii.c:154)未拦截buf[0] > 4,而数组仅 4 项。- 大小写/命名混用:
AES_data_Init与AESInit命名风格不一致(前者下划线、后者驼峰),且第 297-303 行的注释标题误写成AESInit,容易在检索时误判。 printf调试残余:被注释的 PKCS7 函数与本文件主体均无调试输出,但#include "stdio.h"(第 1 行)在 Release 构建中属额外依赖,可清理。