使用OpenSSL AES-128-ECB解密时EVP_DecryptFinal_ex报错求助
问题描述
我正在开发一个基于OpenSSL库的C语言程序,用于执行多项密码学操作。但在使用AES-128-ECB算法解密文件s83660-cipher.bin时,遇到EVP_DecryptFinal_ex:bad decrypt错误,解密失败。
实现流程
- 使用密钥文件
s83660-source-key.bin,通过AES-128-ECB解密s83660-cipher.bin; - 计算解密后文件的SHA512/224哈希值,并与
s83660-digest.bin中的哈希值对比; - 使用密钥文件
s83660-dest-key.bin,通过Camellia-256-OFB算法加密解密后的文件。
相关代码
#include <openssl/evp.h> #include <openssl/aes.h> #include <openssl/camellia.h> #include <openssl/sha.h> #include <openssl/err.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define BUFFER_SIZE 1024 #define SHA512_224_DIGEST_LENGTH 28 void handleErrors(void) { ERR_print_errors_fp(stderr); abort(); } void decrypt_aes_128_ecb(const char *cipher_file, const char *key_file, const char *output_file) { FILE *cf = fopen(cipher_file, "rb"); FILE *kf = fopen(key_file, "rb"); FILE *of = fopen(output_file, "wb"); if (!cf || !kf || !of) { perror("File error"); exit(EXIT_FAILURE); } // Read key unsigned char key[16]; if (fread(key, 1, 16, kf) != 16) { fprintf(stderr, "Error reading key\n"); exit(EXIT_FAILURE); } key[0] = 0; // Salting // Print key for debugging printf("Key after modification: "); for (int i = 0; i < 16; i++) { printf("%02x", key[i]); } printf("\n"); // Initialize decryption EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); if (!ctx) handleErrors(); // Disable padding for manual handling if (EVP_CIPHER_CTX_set_padding(ctx, 0) != 1) handleErrors(); if (EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, NULL) != 1) handleErrors(); unsigned char buffer[BUFFER_SIZE]; unsigned char plaintext[BUFFER_SIZE + EVP_MAX_BLOCK_LENGTH]; int len; int plaintext_len = 0; while ((len = fread(buffer, 1, BUFFER_SIZE, cf)) > 0) { int out_len; if (EVP_DecryptUpdate(ctx, plaintext, &out_len, buffer, len) != 1) handleErrors(); fwrite(plaintext, 1, out_len, of); plaintext_len += out_len; } // Print intermediate plaintext length for debugging printf("Intermediate plaintext length: %d\n", plaintext_len); if (EVP_DecryptFinal_ex(ctx, plaintext, &len) != 1) { // Print specific OpenSSL error message ERR_print_errors_fp(stderr); handleErrors(); } fwrite(plaintext, 1, len, of); plaintext_len += len; printf("Final plaintext length: %d\n", plaintext_len); // Remove padding manually int pad_len = plaintext[plaintext_len - 1]; if (pad_len > 0 && pad_len <= AES_BLOCK_SIZE) { plaintext_len -= pad_len; ftruncate(fileno(of), plaintext_len); // Adjust file length to remove padding } EVP_CIPHER_CTX_free(ctx); fclose(cf); fclose(kf); fclose(of); } void compute_sha512_224(const char *input_file, const char *digest_file) { FILE *inf = fopen(input_file, "rb"); FILE *df = fopen(digest_file, "rb"); if (!inf || !df) { perror("File error"); exit(EXIT_FAILURE); } EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); if (!mdctx) handleErrors(); if (EVP_DigestInit_ex(mdctx, EVP_sha512_224(), NULL) != 1) handleErrors(); unsigned char buffer[BUFFER_SIZE]; int len; while ((len = fread(buffer, 1, BUFFER_SIZE, inf)) > 0) { if (EVP_DigestUpdate(mdctx, buffer, len) != 1) handleErrors(); } unsigned char hash[SHA512_224_DIGEST_LENGTH]; unsigned int hash_len; if (EVP_DigestFinal_ex(mdctx, hash, &hash_len) != 1) handleErrors(); EVP_MD_CTX_free(mdctx); unsigned char expected_hash[SHA512_224_DIGEST_LENGTH]; if (fread(expected_hash, 1, SHA512_224_DIGEST_LENGTH, df) != SHA512_224_DIGEST_LENGTH) { fprintf(stderr, "Error reading expected hash\n"); exit(EXIT_FAILURE); } if (memcmp(hash, expected_hash, SHA512_224_DIGEST_LENGTH) == 0) { printf("Hashes match\n"); } else { printf("Hashes do not match\n"); } fclose(inf); fclose(df); } void encrypt_camellia_256_ofb(const char *input_file, const char *key_file, const char *output_file) { FILE *inf = fopen(input_file, "rb"); FILE *kf = fopen(key_file, "rb"); FILE *of = fopen(output_file, "wb"); if (!inf || !kf || !of) { perror("File error"); exit(EXIT_FAILURE); } unsigned char key[32]; unsigned char iv[16] = {0}; // Initialize IV to 0 if (fread(key, 1, 32, kf) != 32) { fprintf(stderr, "Error reading key\n"); exit(EXIT_FAILURE); } EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); if (!ctx) handleErrors(); if (EVP_EncryptInit_ex(ctx, EVP_camellia_256_ofb(), NULL, key, iv) != 1) handleErrors(); unsigned char buffer[BUFFER_SIZE]; unsigned char ciphertext[BUFFER_SIZE + EVP_MAX_BLOCK_LENGTH]; int len; int ciphertext_len = 0; while ((len = fread(buffer, 1, BUFFER_SIZE, inf)) > 0) { int out_len; if (EVP_EncryptUpdate(ctx, ciphertext, &out_len, buffer, len) != 1) handleErrors(); fwrite(ciphertext, 1, out_len, of); ciphertext_len += out_len; } if (EVP_EncryptFinal_ex(ctx, ciphertext, &len) != 1) handleErrors(); fwrite(ciphertext, 1, len, of); ciphertext_len += len; EVP_CIPHER_CTX_free(ctx); fclose(inf); fclose(kf); fclose(of); } int main(void) { decrypt_aes_128_ecb("s83660-cipher.bin", "s83660-source-key.bin", "decrypted.jpg"); compute_sha512_224("decrypted.jpg", "s83660-digest.bin"); encrypt_camellia_256_ofb("decrypted.jpg", "s83660-dest-key.bin", "s83660-dest-cipher.bin"); return 0; }
程序输出
Key after modification: 000db08f3b6c505d76e9e88962e94c57 Intermediate plaintext length: 102224 139956396554048:error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt:crypto/evp/evp_enc.c:646: Aborted (core dumped)
已采取的排查步骤
- 验证密钥长度为16字节;
- 按照要求修改了密钥的第一个字节;
- 尝试禁用自动填充并手动处理填充。
环境信息
- OpenSSL版本:OpenSSL 1.1.1
- GCC版本:9.3.0
- 操作系统:Ubuntu 20.04 LTS
咨询问题
- 为何密钥和数据看似正确,仍出现
EVP_DecryptFinal_ex:bad decrypt错误? - 手动处理填充的方式是否存在问题?
- 还有哪些额外的调试步骤可以采取?
解决方案
1. bad decrypt错误的原因
当你调用EVP_CIPHER_CTX_set_padding(ctx, 0)禁用自动填充后,EVP_DecryptFinal_ex要求输入的密文长度必须是AES块大小(16字节)的整数倍。如果密文总长度不满足这个条件,函数就会返回错误。
另外两个可能的原因:
- 密钥修改不符合加密方规则:如果加密时用的是原始密钥,你修改第一个字节会导致密钥不匹配,触发解密错误;
- 密文文件损坏:文件读取过程中出现截断,导致实际读取的密文长度和原始加密后的长度不一致。
2. 手动处理填充的问题
你的填充处理逻辑存在多处问题:
- 禁用自动填充后,
EVP_DecryptFinal_ex直接报错,后续的手动填充处理代码根本无法执行; - 你尝试从
plaintext数组的最后一个字节获取填充长度,但plaintext会被每次DecryptUpdate覆盖,此时的内容并不是整个解密文件的最后字节; - 即使能获取填充长度,
ftruncate修改文件长度的逻辑依赖于正确的填充值,但当前流程无法保证这一点。
如果加密方使用的是标准PKCS#7填充,正确做法是不要禁用自动填充,让OpenSSL自动处理;只有当加密方用了自定义填充规则时,才需要手动处理。
3. 额外调试步骤
- 检查密文文件长度:用
ls -l s83660-cipher.bin查看文件大小,确认是否是16字节的整数倍; - 验证密钥正确性:将修改后的密钥(
000db08f3b6c505d76e9e88962e94c57)和加密方提供的预期密钥对比,确保修改规则正确; - 测试小样本:用已知的AES-128-ECB密文、密钥、明文测试代码,验证基础逻辑是否正确;
- 打印密文头部:读取密文时打印前32字节的十六进制值,和加密方提供的样本对比,确认文件读取无误;
- 查看错误详情:用
ERR_error_string(0x06065064, NULL)打印错误的详细描述,辅助定位问题; - 临时注释
abort():让程序在报错后继续执行,查看是否能获取更多上下文信息。
内容的提问来源于stack exchange,提问作者diePuppe
相关产品推荐
相关产品推荐

