You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
}

核心修复点

  1. 添加Base64 BIO Flush:加密完成后调用BIO_flush(bio_out),强制将Base64缓冲区中剩余数据写入文件,解决密文截断问题。
  2. 头部读写逻辑优化:使用原始文件BIO处理magic和salt,确保头部以明文形式写入/读取,避免被Base64编码/解码干扰。
  3. 完善错误校验:增加magic内容校验、读写长度校验,避免处理无效文件或不完整数据。
  4. 优化循环退出条件:解密时判断BIO_eof(bio_in)确认是否真的到达文件末尾,避免提前退出导致数据遗漏。
  5. 资源释放优化:使用BIO_free_all()释放整个BIO链,避免内存泄漏和重复释放问题。

内容的提问来源于stack exchange,提问作者user2881914

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 12:35:28