OpenSSL C API实现AES-128-CBC解密失败,求问题排查
解密失败(EVP_DecryptFinal返回0)的原因与修复方案
看起来你在把命令行的OpenSSL解密逻辑转成C API时,踩了几个常见的格式坑,这才导致EVP_DecryptFinal返回0(解密失败)。我帮你拆解一下问题,再给出修正后的代码:
问题1:直接把Base64字符串当作密文传入解密函数
命令行里的openssl enc -base64 -d会自动先对输入的Base64字符串解码,得到原始二进制密文,再执行解密。但你的代码里直接把Base64编码后的hex_enc_password传给了decrypt函数——而OpenSSL的EVP解密API只认二进制密文,不认Base64格式的文本。这就好比你把加密后的zip文件的Base64文本直接丢给解压软件,它肯定识别不了。
问题2:密钥格式完全错了
你的hex_key是hexlify(key)得到的十六进制字符串(比如把Base64密钥转成了类似"1ac4"的文本),但EVP_DecryptInit需要的是二进制密钥字节,不是十六进制字符串。举个例子:如果hexlify得到"1ac4",你需要把这个文本转换成两个字节0x1a和0xc4,而不是直接把"1ac4"这个字符串当密钥传入——这就像你把密码的ASCII文本当成密钥,而不是密码对应的二进制哈希值。
潜在问题:IV的长度与正确性
AES-128-CBC要求IV必须是16字节(和块大小一致),如果你的iv变量长度不对,或者和命令行里用的{iv}不一致,解密也会失败。
修正后的完整代码示例
我给你写了包含所有必要转换和错误处理的代码,你可以参考:
#include <openssl/evp.h> #include <string.h> #include <stdlib.h> #include <stdio.h> // 辅助函数:十六进制字符串转二进制字节 unsigned char* hex_to_bin(const char* hex_str, size_t* out_len) { size_t len = strlen(hex_str); if (len % 2 != 0) { *out_len = 0; return NULL; } *out_len = len / 2; unsigned char* bin_buf = malloc(*out_len); if (!bin_buf) { *out_len = 0; return NULL; } for (size_t i = 0; i < len; i += 2) { sscanf(hex_str + i, "%2hhx", &bin_buf[i/2]); } return bin_buf; } // 辅助函数:Base64解码(基于OpenSSL BIO) unsigned char* base64_decode(const char* b64_str, size_t* out_len) { BIO *bio_mem, *bio_b64; size_t b64_len = strlen(b64_str); unsigned char* decrypted_buf = malloc(b64_len); if (!decrypted_buf) { *out_len = 0; return NULL; } bio_mem = BIO_new_mem_buf(b64_str, b64_len); bio_b64 = BIO_new(BIO_f_base64()); bio_mem = BIO_push(bio_b64, bio_mem); BIO_set_flags(bio_mem, BIO_FLAGS_BASE64_NO_NL); // 忽略Base64中的换行符 *out_len = BIO_read(bio_mem, decrypted_buf, b64_len); BIO_free_all(bio_mem); return decrypted_buf; } // 修正后的解密函数 char* decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *key, unsigned char *iv ) { int out_len_update = 0; int out_len_final = 0; // 输出缓冲区要预留块大小的空间,处理PKCS#7 padding size_t out_buf_size = ciphertext_len + EVP_CIPHER_block_size(EVP_aes_128_cbc()); unsigned char *plaintext_buf = malloc(out_buf_size); if (!plaintext_buf) { return NULL; } memset(plaintext_buf, 0, out_buf_size); EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); if (!ctx) { free(plaintext_buf); return NULL; } // 初始化解密上下文,key和iv都是二进制字节 if (EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv) != 1) { EVP_CIPHER_CTX_free(ctx); free(plaintext_buf); return NULL; } // 解密主体数据 if (EVP_DecryptUpdate(ctx, plaintext_buf, &out_len_update, ciphertext, ciphertext_len) != 1) { EVP_CIPHER_CTX_free(ctx); free(plaintext_buf); return NULL; } // 完成解密,处理padding if (EVP_DecryptFinal_ex(ctx, plaintext_buf + out_len_update, &out_len_final) != 1) { EVP_CIPHER_CTX_free(ctx); free(plaintext_buf); fprintf(stderr, "解密失败:可能是密文损坏、密钥/IV错误或padding问题\n"); return NULL; } // 为文本明文添加结束符(如果明文是二进制数据可以去掉) plaintext_buf[out_len_update + out_len_final] = '\0'; EVP_CIPHER_CTX_free(ctx); return (char*)plaintext_buf; } int main() { // 原始Base64格式的密钥 char* key_b64 = "lbzA54tAAg3E/jPxiJc34e=="; size_t key_bin_len; // 解码Base64得到二进制密钥字节 unsigned char* key_bin = base64_decode(key_b64, &key_bin_len); if (!key_bin || key_bin_len != 16) { // AES-128需要16字节密钥 fprintf(stderr, "无效密钥:长度必须为16字节\n"); return 1; } // 原始二进制密文 unsigned char encrypted[] = {0x3d,0xd2,0x02,0xc3,0xae,0x1e,0xf5,0x7e,0x33,0xc3,0x1d,0x7b,0x1e,0x48,0x73,0x84}; size_t encrypted_len = sizeof(encrypted); // 模拟生成Base64编码后的密文(对应你的hex_enc_password) size_t b64_enc_len; unsigned char* hex_enc_password = base64_encode(encrypted, encrypted_len, &b64_enc_len); // 假设你有正确的base64_encode实现 if (!hex_enc_password) { free(key_bin); return 1; } // 先解码Base64得到原始二进制密文 size_t ciphertext_len; unsigned char* ciphertext = base64_decode((char*)hex_enc_password, &ciphertext_len); free(hex_enc_password); if (!ciphertext) { free(key_bin); return 1; } // 初始化IV,替换成你命令行里使用的真实IV(必须是16字节) unsigned char iv[16] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}; // 执行解密 char* plaintext = decrypt(ciphertext, ciphertext_len, key_bin, iv); free(ciphertext); free(key_bin); if (plaintext) { printf("解密结果:%s\n", plaintext); free(plaintext); } return 0; }
关键修复点总结
- 添加Base64解码:解密前先把Base64编码的密文转成二进制,对应命令行的
-base64 -d。 - 密钥格式转换:把Base64或十六进制格式的密钥转成二进制字节,这是EVP API要求的输入格式。
- 缓冲区预留空间:解密输出缓冲区要比密文长一个块大小,用来处理PKCS#7 padding。
- 完善错误处理:添加上下文创建、初始化的错误检查,避免内存泄漏。
内容的提问来源于stack exchange,提问作者John Smith
相关产品推荐
相关产品推荐

