OpenSSL加解密结合Base64编码时数据丢失问题求助
OpenSSL 1.1.1s加解密结合Base64时数据丢失问题修复
你的代码核心问题是加密阶段未刷新Base64 BIO缓冲区,导致最后一段加密数据的Base64编码未写入文件,解密时因密文不完整引发报错和数据丢失。此外还有几处细节问题需要完善,以下是具体分析和修复后的代码:
问题根源
Base64编码按块处理(每3字节原始数据转4字节Base64字符),OpenSSL的Base64 BIO会缓存数据,只有当缓存满或主动调用BIO_flush()时才会输出数据。你的加密流程中,写完最后一段加密数据后直接释放BIO,导致缓冲区中剩余的不足一个Base64块的数据被丢弃,解密时读不到完整密文,最终EVP_CipherFinal_ex()因填充验证失败报错,同时丢失部分数据。
另外,代码还存在以下次要问题:
- 头部读写未校验返回值,可能出现magic或salt写入/读取不完整的情况
- 解密循环中直接判断
inlen <=0就退出,未考虑Base64 BIO暂未解码出数据的情况 - 资源释放逻辑不够严谨,可能导致内存泄漏
修复后的完整代码
#include <openssl/bio.h> #include <openssl/evp.h> #include <openssl/rand.h> #include <openssl/err.h> #include <string.h> #include <stdio.h> #define OK 0 #define ERROR -1 #define MAGIC_SIZE 8 // 根据实际定义调整 #define SALTSIZE 8 #define ENC_BUFF_SIZE 1024 // 写入加密头部(magic + salt) int BIO_enc_header(BIO* file, unsigned char* Salt) { // 生成随机salt失败直接返回错误 if (RAND_bytes(Salt, SALTSIZE) != 1) { return ERROR; } // 严格校验magic写入长度 int len = BIO_write(file, MAGIC, MAGIC_SIZE); if (len != MAGIC_SIZE) { return ERROR; } // 严格校验salt写入长度 len = BIO_write(file, Salt, SALTSIZE); if (len != SALTSIZE) { return ERROR; } return OK; } // 读取并校验解密头部 int BIO_dec_header(BIO* file, unsigned char* Salt) { char mBuff[MAGIC_SIZE + 1] = { 0 }; int mlen = BIO_read(file, mBuff, MAGIC_SIZE); // 校验magic读取长度和内容 if (mlen != MAGIC_SIZE || memcmp(mBuff, MAGIC, MAGIC_SIZE) != 0) { return ERROR; } // 校验salt读取长度 int salt_len = BIO_read(file, Salt, SALTSIZE); if (salt_len != SALTSIZE) { return ERROR; } return OK; } #define BASE64 1 int do_crypt_bio(BIO* bio_in, BIO* bio_out, int do_encrypt, unsigned char* szPassword) { int Result = OK; unsigned char salt[SALTSIZE] = { 0 }; BIO* b64 = NULL; #ifdef BASE64 b64 = BIO_new(BIO_f_base64()); if (!b64) { return ERROR; } #endif // 保存原始BIO指针,用于头部读写(避免头部被Base64处理) BIO* raw_bio_in = bio_in; BIO* raw_bio_out = bio_out; // 挂载Base64 BIO到链上 #ifdef BASE64 if (do_encrypt) { bio_out = BIO_push(b64, bio_out); BIO_set_flags(bio_out, BIO_FLAGS_BASE64_NO_NL); } else { bio_in = BIO_push(b64, bio_in); BIO_set_flags(bio_in, BIO_FLAGS_BASE64_NO_NL); } #endif unsigned char inbuf[ENC_BUFF_SIZE + EVP_MAX_BLOCK_LENGTH], outbuf[ENC_BUFF_SIZE + EVP_MAX_BLOCK_LENGTH]; int inlen, outlen; EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); if (!ctx) { Result = ERROR; goto cleanup; } // 处理头部:加密写头部,解密读头部 if (do_encrypt) { if (BIO_enc_header(raw_bio_out, salt) != OK) { Result = ERROR; goto cleanup; } } else { if (BIO_dec_header(raw_bio_in, salt) != OK) { Result = ERROR; goto cleanup; } } #define AES_256_KEY_SIZE 32 unsigned char key[AES_256_KEY_SIZE] = { 0 }; unsigned char iv[AES_BLOCK_SIZE] = { 0 }; // 从密码和salt生成密钥和IV int key_len = EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha256(), salt, szPassword, strlen((char*)szPassword), 5, key, iv); if (key_len != AES_256_KEY_SIZE) { Result = ERROR; goto cleanup; } // 初始化加密上下文 if (!EVP_CipherInit_ex(ctx, EVP_aes_256_cbc(), NULL, NULL, NULL, do_encrypt) || !EVP_CipherInit_ex(ctx, NULL, NULL, key, iv, do_encrypt)) { Result = ERROR; goto cleanup; } // 循环处理数据 while (1) { memset(inbuf, 0, sizeof(inbuf)); inlen = BIO_read(bio_in, inbuf, ENC_BUFF_SIZE); if (inlen < 0) { Result = ERROR; break; } // 确认是否真的到达EOF,避免Base64 BIO未解码完数据就退出 if (inlen == 0) { if (BIO_eof(bio_in)) { break; } else { continue; } } // 加密/解密更新 if (!EVP_CipherUpdate(ctx, outbuf, &outlen, inbuf, inlen)) { Result = ERROR; break; } // 校验写入长度 if (BIO_write(bio_out, outbuf, outlen) != outlen) { Result = ERROR; break; } } // 处理最后一段数据(填充) if (Result == OK) { int f_len; if (!EVP_CipherFinal_ex(ctx, outbuf, &f_len)) { Result = ERROR; ERR_print_errors_fp(stderr); } else if (f_len > 0) { if (BIO_write(bio_out, outbuf, f_len) != f_len) { Result = ERROR; } } } // 关键:加密完成后必须flush Base64 BIO,确保所有数据写入文件 if (do_encrypt && BASE64 && Result == OK) { BIO_flush(bio_out); } cleanup: // 释放资源 if (ctx) { EVP_CIPHER_CTX_free(ctx); } #ifdef BASE64 // 释放整个BIO链,避免内存泄漏 if (do_encrypt) { BIO_free_all(bio_out); } else { BIO_free_all(bio_in); } #endif return Result; } int EncryptFile(char* input, char *encrypted, char* password) { BIO* bio_in = BIO_new_file(input, "rb"); if (!bio_in) { return ERROR; } BIO* bio_out = BIO_new_file(encrypted, "wb+"); if (!bio_out) { BIO_free(bio_in); return ERROR; } int Result = do_crypt_bio(bio_in, bio_out, 1, (unsigned char*)password); BIO_free(bio_in); BIO_free(bio_out); return Result; } int DecryptFile(char* encrypted, char* output, char* password) { BIO* bio_in = BIO_new_file(encrypted, "rb"); if (!bio_in) { return ERROR; } BIO* bio_out = BIO_new_file(output, "wb+"); if (!bio_out) { BIO_free(bio_in); return ERROR; } int Result = do_crypt_bio(bio_in, bio_out, 0, (unsigned char*)password); BIO_free(bio_in); BIO_free(bio_out); return Result; }
核心修复点
- 添加Base64 BIO Flush:加密完成后调用
BIO_flush(bio_out),强制将Base64缓冲区中剩余数据写入文件,解决密文截断问题。 - 头部读写逻辑优化:使用原始文件BIO处理magic和salt,确保头部以明文形式写入/读取,避免被Base64编码/解码干扰。
- 完善错误校验:增加magic内容校验、读写长度校验,避免处理无效文件或不完整数据。
- 优化循环退出条件:解密时判断
BIO_eof(bio_in)确认是否真的到达文件末尾,避免提前退出导致数据遗漏。 - 资源释放优化:使用
BIO_free_all()释放整个BIO链,避免内存泄漏和重复释放问题。
内容的提问来源于stack exchange,提问作者user2881914
相关产品推荐
相关产品推荐

