使用OpenSSL EVP API解密时写入垃圾值及bad decrypt问题排查
问题分析与解决:AES-256-CBC解密归档文件的两类问题
问题背景
开发一款基于C语言+OpenSSL EVP API的归档加解密程序,实现AES-256-CBC解密时遇到两个核心问题:
- 当文件剩余待解密字节数小于读取缓冲区大小时,输出文件会写入垃圾值;
- 将读取缓冲区大小
inLen改为block_size后,触发OpenSSL的"bad decrypt"错误,且仅在文件大小为block_size倍数且多个此类文件归档时出现。
解密核心代码
int TARIM_decrypt_aes256(FILE* infile, FILE* outfile, unsigned char* key, unsigned char* iv, unsigned long long int fileLoc, unsigned long int fileSize) { if (infile == NULL || outfile == NULL || key == NULL || iv == NULL) { printf("(ERROR) decrypt_aes256: One/more parameters are NULL pointers\n"); return 1; } EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); if (ctx == NULL) { printf("(ERROR) decrypt_aes256: Failed to generate cipher context. OpenSSL: %s\n", ERR_error_string(ERR_get_error(), NULL)); return 1; } EVP_CIPHER* cipher = EVP_CIPHER_fetch(NULL, "AES-256-CBC", NULL); if (cipher == NULL) { printf("(ERROR) decrypt_aes256: Failed to fetch cipher. OpenSSL: %s\n", ERR_error_string(ERR_get_error(), NULL)); EVP_CIPHER_CTX_free(ctx); return 1; } // Explicitly Enable Padding EVP_CIPHER_CTX_set_padding(ctx, 1); int num_read = 0; int block_size = EVP_CIPHER_block_size(cipher); int inLen = 1024; int outLen = inLen + block_size; unsigned char inbuffer[inLen], outbuffer[outLen]; // Allow space for additional block in outbuffer if (!EVP_CipherInit_ex2(ctx, cipher, NULL, NULL, 0, NULL)) { printf("(ERROR) decrypt_aes256: Failed to Initiate Cipher. OpenSSL: %s\n", ERR_error_string(ERR_get_error(), NULL)); EVP_CIPHER_free(cipher); EVP_CIPHER_CTX_free(ctx); return 1; } // Assert size of key and iv OPENSSL_assert(EVP_CIPHER_CTX_key_length(ctx) == 32); OPENSSL_assert(EVP_CIPHER_CTX_iv_length(ctx) == 16); // Set key and iv if (!EVP_CipherInit_ex2(ctx, NULL, key, iv, 0, NULL)) { printf("(ERROR) decrypt_aes256: Failed to set key and iv. OpenSSL: %s\n", ERR_error_string(ERR_get_error(), NULL)); EVP_CIPHER_free(cipher); EVP_CIPHER_CTX_free(ctx); return 1; } // Seek to File Location fseek(infile, fileLoc, SEEK_SET); // Loop until bytes read unsigned long long int b_count = 0; while (1) { num_read = fread(inbuffer, sizeof(unsigned char), inLen, infile); b_count += num_read; if (!EVP_CipherUpdate(ctx, outbuffer, &outLen, inbuffer, num_read)) { printf("(ERROR) decrypt_aes256: Failed to pass bytes to the cipher. OpenSSL: %s\n", ERR_error_string(ERR_get_error(), NULL)); EVP_CIPHER_free(cipher); EVP_CIPHER_CTX_free(ctx); return 1; } fwrite(outbuffer, sizeof(unsigned char), outLen, outfile); // EOF if (b_count >= fileSize || num_read < inLen) { break; } } // Cipher Final block with padding if (!EVP_CipherFinal_ex(ctx, outbuffer, &outLen)) { printf("(ERROR) decrypt_aes256: Failed to pass bytes from final block to cipher. OpenSSL: %s\n", ERR_error_string(ERR_get_error(), NULL)); EVP_CIPHER_free(cipher); EVP_CIPHER_CTX_free(ctx); return 1; } fwrite(outbuffer, sizeof(unsigned char), outLen, outfile); // Clean up EVP_CIPHER_free(cipher); EVP_CIPHER_CTX_free(ctx); return 0; }
缓冲区修改后的错误情况
修改inLen为块大小:
int inLen = block_size;
触发错误日志:
(ERROR) decrypt_aes256: Failed to pass bytes from final block to cipher. OpenSSL: error:1C800064:Provider routines::bad decrypt
归档位置计算代码
// Get Relative Location unsigned long long int relativeLoc = 0; for (long long int itr = 0; itr < option_num; itr++) { if (block_size == 0 || fArray[itr].fsize == 0) { relativeLoc += fArray[itr].fsize; } else { // Adjust for padding relativeLoc += ((fArray[itr].fsize / block_size) + 1) * block_size; } }
问题原因与解决方案
问题1:剩余字节小于缓冲区时写入垃圾值
原因:循环中未限制读取字节数,当剩余待解密数据不足缓冲区大小时,fread会读取到后续归档文件的字节(跨文件读取),这些无关字节被送入解密流程后,输出垃圾数据。
解决:每次读取时计算剩余待处理的加密数据长度,限制fread的读取量,避免跨文件读取:
// 循环逻辑修改 unsigned long long int b_count = 0; while (b_count < fileSize) { // 计算本次最大可读取的字节数,不超过剩余待处理数据 int read_size = (fileSize - b_count) < inLen ? (fileSize - b_count) : inLen; num_read = fread(inbuffer, sizeof(unsigned char), read_size, infile); if (num_read <= 0) break; b_count += num_read; if (!EVP_CipherUpdate(ctx, outbuffer, &outLen, inbuffer, num_read)) { // 错误处理 return 1; } fwrite(outbuffer, sizeof(unsigned char), outLen, outfile); }
问题2:改为block_size触发bad decrypt错误
原因:
- 当原始文件大小为
block_size倍数时,加密时OpenSSL会自动添加一个完整的填充块(因为启用了padding),所以加密后文件大小是((原始大小/block_size)+1)*block_size; - 解密时传入的
fileSize是原始文件大小,导致未读取完整的加密数据(遗漏了填充块),EVP_CipherFinal_ex无法验证填充,触发"bad decrypt"; - 用
block_size作为缓冲区时,最后一次读取刚好读完原始大小对应的加密数据,没有读取填充块,导致后续Final步骤失败。
解决:
- 解密时传入加密后的文件大小(即归档位置计算代码中算出的
relativeLoc增量值),确保读取完整的加密数据(包括填充块); - 解密完成后,根据原始文件大小截断输出,或者依赖
EVP_CipherFinal_ex自动去掉填充(启用padding时,Final步骤会输出去掉填充后的字节,此时总输出字节数等于原始文件大小)。
关键修正点
- 归档时记录每个文件的原始大小和加密后大小,解密时分别传入;
- 读取加密数据时,用加密后大小作为读取终止条件;
- 写入输出文件时,最终总写入字节数等于原始文件大小。
内容的提问来源于stack exchange,提问作者user21011675
相关产品推荐
相关产品推荐

