AES-CMAC实现中多次调用EVP_EncryptUpdate结果异常求助
AES-128 CMAC中EVP_EncryptUpdate多次调用失效的原因及解决方法
问题核心
CMAC的计算逻辑要求逐块处理并维护中间异或状态,而EVP_EncryptUpdate是为CBC/GCM等标准流式加密模式设计的接口,它内部会缓存不足块大小的数据,不会暴露每一块的加密结果——这就是导致多次调用结果异常的根本原因。
原因拆解
根据RFC4493的CMAC流程:
- 生成子密钥K1、K2;
- 对前n-1个完整块,每块加密后与中间CMAC值异或;
- 最后一块若为完整块,异或K1后加密;若为不完整块,补位后异或K2再加密;
- 最终加密输出即为CMAC值。
当你单次传入N*块大小的缓冲区时,EVP_EncryptUpdate会一次性输出所有块的加密结果,你可以按CMAC逻辑逐块处理异或;但多次调用时,EVP_EncryptUpdate会把不足16字节(AES-128块大小)的数据存在内部上下文,不会输出,导致你无法按CMAC要求及时处理每一块的异或操作,中间状态出错。
正确实现方式
放弃依赖EVP_EncryptUpdate的流式缓存,手动控制分块和中间状态:
- 初始化EVP加密上下文(用ECB模式,因为CMAC的块加密是ECB方式);
- 处理前n-1个完整块:
unsigned char intermediate_cmac[16] = {0}; unsigned char temp_block[16]; unsigned char encrypted_block[16]; int out_len; for (int i = 0; i < num_full_blocks; i++) { // 当前块与中间CMAC值异或 for (int j = 0; j < 16; j++) { temp_block[j] = input[i*16 + j] ^ intermediate_cmac[j]; } // 加密异或后的块 EVP_EncryptUpdate(ctx, encrypted_block, &out_len, temp_block, 16); // 更新中间CMAC值为加密结果 memcpy(intermediate_cmac, encrypted_block, 16); } - 处理最后一块:
unsigned char final_block[16] = {0}; int remaining = input_len % 16; memcpy(final_block, input + num_full_blocks*16, remaining); if (remaining == 0) { // 完整块,异或K1 for (int j = 0; j < 16; j++) { final_block[j] ^= K1[j]; } } else { // 不完整块,补位后异或K2 final_block[remaining] = 0x80; for (int j = 0; j < 16; j++) { final_block[j] ^= K2[j]; } } // 最后一次异或加密 for (int j = 0; j < 16; j++) { final_block[j] ^= intermediate_cmac[j]; } EVP_EncryptUpdate(ctx, cmac_result, &out_len, final_block, 16);
关键提示
- CMAC的块处理逻辑是加密(当前块 XOR 中间状态),而非简单加密所有块后再处理,这和标准流式加密模式完全不同;
- 必须用ECB模式初始化EVP上下文,因为CMAC的每个块加密都是独立的,不需要IV或链式状态。
内容的提问来源于stack exchange,提问作者Allen Curtis
相关产品推荐
相关产品推荐

