CBC-ECB加密实现中ECB填充异常问题排查求助
问题:Cryptopals第2组第10题ECB填充异常问题
这是一项加密学习练习,并非用于实际场景的加密系统开发。我正在完成Cryptopals第2组第10题,需自行实现CBC模式(基于ECB加解密)的加解密算法。我理解的CBC实现逻辑为:取当前16字节明文块与前一个密文块(初始为IV)异或,再对结果进行ECB加密。
核心问题
- 使用自定义ECB加密函数处理16字节块时,必须额外添加16字节填充才能在解密时得到原长度输入,导致16字节明文对应32字节密文。但同挑战的其他解决方案中,16字节输入可得到16字节密文,且挑战提供的待解密文件也无额外填充。
- 运行原代码时,解密无法得到明文末尾的C段;注释
ecb_encrypt中的填充代码块并修改EVP_EncryptUpdate参数后,才能正常解密得到完整明文。怀疑填充处理逻辑存在错误,但不知从何处排查。
完整代码
#include <stdio.h> #include <string.h> #include <stdint.h> #include <stdlib.h> #include <openssl/conf.h> #include <openssl/evp.h> #include <openssl/err.h> typedef enum { BLOCK_SIZE_128_BIT = 128 / 8, BLOCK_SIZE_256_BIT = 256 / 8, BLOCK_SIZE_CUSTOM_VALUE = 0 } paddingBlockSize; typedef struct { void* dataWithPadding; uint64_t dataLengthWithPadding; uint8_t valueOfByteForPadding; } PKCS7_Padding; typedef struct { void* dataWithoutPadding; uint64_t dataLengthWithoutPadding; uint8_t valueOfRemovedByteFromData; } PKCS7_unPadding; PKCS7_Padding* addPadding(const void* const data, const uint64_t dataLength, const uint8_t BLOCK_SIZE) { if (0 == BLOCK_SIZE) { puts("ERROR: block size value must be 0 < BLOCK_SIZE < 256"); exit(-1); } PKCS7_Padding* paddingResult = (PKCS7_Padding*) malloc(sizeof(PKCS7_Padding)); if (NULL == paddingResult) { perror("problem with PKCS7_Padding* paddingResult"); exit(-1); } uint8_t paddingBytesAmount = BLOCK_SIZE - (dataLength % BLOCK_SIZE); paddingResult->valueOfByteForPadding = paddingBytesAmount; paddingResult->dataLengthWithPadding = dataLength + paddingBytesAmount; uint8_t* dataWithPadding = (uint8_t*) malloc(paddingResult->dataLengthWithPadding); if (NULL == paddingResult) { perror("problem with uint8_t* dataWithPadding"); exit(-1); } memcpy(dataWithPadding, data, dataLength); for (uint8_t i = 0; i < paddingBytesAmount; i++) { dataWithPadding[dataLength + i] = paddingResult->valueOfByteForPadding; } paddingResult->dataWithPadding = dataWithPadding; return paddingResult; } PKCS7_unPadding* removePadding(const void* const data, const uint64_t dataLength) { PKCS7_unPadding* unpaddingResult = (PKCS7_unPadding*) malloc(sizeof(PKCS7_unPadding)); if (NULL == unpaddingResult) { perror("problem with PKCS7_Padding* unpaddingResult"); exit(-1); } uint8_t paddingBytesAmount = *((uint8_t *)data + dataLength - 1); unpaddingResult->valueOfRemovedByteFromData = paddingBytesAmount; unpaddingResult->dataLengthWithoutPadding = dataLength - paddingBytesAmount; uint8_t* dataWithoutPadding = (uint8_t*) malloc(unpaddingResult->dataLengthWithoutPadding); if (NULL == dataWithoutPadding) { perror("problem with uint8_t* dataWithoutPadding"); exit(-1); } memcpy(dataWithoutPadding, data, unpaddingResult->dataLengthWithoutPadding); unpaddingResult->dataWithoutPadding = dataWithoutPadding; return unpaddingResult; } void freePaddingResult(PKCS7_Padding* puddingResult) { free(puddingResult->dataWithPadding); free(puddingResult); } void freeUnPaddingResult(PKCS7_unPadding* unPuddingResult) { free(unPuddingResult->dataWithoutPadding); free(unPuddingResult); } int ecb_encrypt(unsigned char *plain_bytes, unsigned char *key, unsigned char *cipher_bytes, size_t plain_bytes_len, size_t *cipher_bytes_len) { EVP_CIPHER_CTX *ctx; int len = 0; /*size_t padded_length = 0; if (plain_bytes_len % 16 > 0) { padded_length = plain_bytes_len + (16 - (plain_bytes_len % 16)); } else { padded_length = plain_bytes_len + 16; } PKCS7_Padding *padded = addPadding(plain_bytes, plain_bytes_len, padded_length); unsigned char *padded_message = padded->dataWithPadding; plain_bytes_len = padded->dataLengthWithPadding;*/ if(!(ctx = EVP_CIPHER_CTX_new())) return 4; if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, NULL)) return 5; if(1 != EVP_EncryptUpdate(ctx, cipher_bytes, &len, plain_bytes, plain_bytes_len))// //if(1 != EVP_EncryptUpdate(ctx, cipher_bytes, &len, padded_message, plain_bytes_len)) return 6; *cipher_bytes_len = len; if(1 != EVP_EncryptFinal_ex(ctx, cipher_bytes + *cipher_bytes_len, &len)) return 7; EVP_CIPHER_CTX_free(ctx); return 0; } int ecb_decrypt(unsigned char *cipher_bytes, unsigned char *key, unsigned char *plain_bytes, size_t cipher_bytes_len, size_t *plain_bytes_len) { EVP_CIPHER_CTX *ctx; int len = 0; if(!(ctx = EVP_CIPHER_CTX_new())) return 4; if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, NULL)) return 5; if(1 != EVP_DecryptUpdate(ctx, plain_bytes, &len, cipher_bytes, cipher_bytes_len)) return 6; *plain_bytes_len = len; if(1 != EVP_DecryptFinal_ex(ctx, plain_bytes + *plain_bytes_len, &len)) return 7; EVP_CIPHER_CTX_free(ctx); return 0; } int main() { unsigned char plain_text[] = "AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCC"; unsigned char key[] = "ABCDEFGHIJKLMNOP"; unsigned char cipher_bytes[strlen(plain_text)]; size_t cipher_bytes_len = strlen(plain_text); ecb_encrypt(plain_text, key, (unsigned char *) &cipher_bytes, cipher_bytes_len, &cipher_bytes_len); size_t plain_bytes_len = 0; unsigned char plain_bytes[cipher_bytes_len]; ecb_decrypt(cipher_bytes, key, (unsigned char *) &plain_bytes, cipher_bytes_len, &plain_bytes_len); printf("plain_bytes_len = %lu\n", plain_bytes_len); for(size_t i = 0; i < plain_bytes_len; i++){ printf("%c", plain_bytes[i]); } printf("\n"); return 0; }
编译命令:gcc main.c -lcrypto
问题根源分析
- OpenSSL EVP接口默认填充误解:OpenSSL的
EVP_EncryptFinal_ex会自动处理PKCS#7填充,若手动添加填充会导致双重填充——当明文长度为块大小整数倍时,手动加16字节填充,再加OpenSSL自动填充的一个完整块,最终密文长度翻倍。 - 填充函数调用错误:注释的填充代码中,调用
addPadding时传入的第三个参数是填充后的总长度,而非块大小(16),导致填充字节数计算错误。 - 解密未处理填充:当前
ecb_decrypt未调用removePadding移除PKCS#7填充,导致解密结果包含冗余字节或解密失败。
修复方案
1. 修正ECB加解密的填充控制
禁用OpenSSL自动填充,手动处理PKCS#7填充:
// 加密函数中添加:禁用自动填充 EVP_CIPHER_CTX_set_padding(ctx, 0); // 解密函数中同样添加该行代码
2. 修正addPadding调用方式
调用时第三个参数必须传入块大小(如BLOCK_SIZE_128_BIT):
PKCS7_Padding *padded = addPadding(plain_bytes, plain_bytes_len, BLOCK_SIZE_128_BIT);
3. 完整修正后的ECB加解密函数
int ecb_encrypt(unsigned char *plain_bytes, unsigned char *key, unsigned char *cipher_bytes, size_t plain_bytes_len, size_t *cipher_bytes_len) { EVP_CIPHER_CTX *ctx; int len = 0; // 手动添加PKCS#7填充 PKCS7_Padding *padded = addPadding(plain_bytes, plain_bytes_len, BLOCK_SIZE_128_BIT); unsigned char *padded_message = padded->dataWithPadding; size_t padded_len = padded->dataLengthWithPadding; if(!(ctx = EVP_CIPHER_CTX_new())) return 4; // 禁用OpenSSL自动填充 EVP_CIPHER_CTX_set_padding(ctx, 0); if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, NULL)) return 5; if(1 != EVP_EncryptUpdate(ctx, cipher_bytes, &len, padded_message, padded_len)) return 6; *cipher_bytes_len = len; if(1 != EVP_EncryptFinal_ex(ctx, cipher_bytes + *cipher_bytes_len, &len)) return 7; *cipher_bytes_len += len; EVP_CIPHER_CTX_free(ctx); freePaddingResult(padded); return 0; } int ecb_decrypt(unsigned char *cipher_bytes, unsigned char *key, unsigned char *plain_bytes, size_t cipher_bytes_len, size_t *plain_bytes_len) { EVP_CIPHER_CTX *ctx; int len = 0; if(!(ctx = EVP_CIPHER_CTX_new())) return 4; // 禁用OpenSSL自动填充 EVP_CIPHER_CTX_set_padding(ctx, 0); if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, NULL)) return 5; if(1 != EVP_DecryptUpdate(ctx, plain_bytes, &len, cipher_bytes, cipher_bytes_len)) return 6; *plain_bytes_len = len; if(1 != EVP_DecryptFinal_ex(ctx, plain_bytes + *plain_bytes_len, &len)) return 7; *plain_bytes_len += len; // 手动移除PKCS#7填充 PKCS7_unPadding *unpadded = removePadding(plain_bytes, *plain_bytes_len); memcpy(plain_bytes, unpadded->dataWithoutPadding, unpadded->dataLengthWithoutPadding); *plain_bytes_len = unpadded->dataLengthWithoutPadding; EVP_CIPHER_CTX_free(ctx); freeUnPaddingResult(unpadded); return 0; }
4. 修正main函数缓冲区大小
确保加密缓冲区足够容纳填充后的数据:
unsigned char cipher_bytes[strlen((char*)plain_text) + BLOCK_SIZE_128_BIT];
内容的提问来源于stack exchange,提问作者DavidT
相关产品推荐
相关产品推荐

