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

解析OpenSSL AES-256-CBC加解密的IV异常问题

AES-256-CBC加解密异常:不同IV仍能解密的原因及修复

问题现象

  • 加密时生成随机IV,解密时生成独立随机IV,未传递同一IV,但解密后原输入内容一致
  • 加密前拼接16字节固定前缀,解密后前16字节为乱码,去掉后得到原文本;前缀改为20字节仍能正常解密,改为小于16字节则解密结果异常

原因分析

  1. OpenSSL BIO_cipher自动写入IV:你使用的BIO_f_cipher默认会自动将加密时的IV写入密文开头,解密时BIO会自动读取密文开头的IV用于解密,你在解密函数中生成的随机IV实际上被忽略了——这就是不同IV仍能解密的核心原因。
  2. CBC分组特性影响:AES-CBC以16字节为一个分组加密:
    • 前缀为16字节时,刚好是第一个完整分组,解密时该分组结果会和加密IV异或得到乱码,但后续分组依赖前一个密文块作为IV,解密链不受影响,去掉前16字节后原文本正常。
    • 前缀为20字节时,前16字节是第一个分组,剩下4字节和原文本开头组成第二个分组,乱码仅存在于前16字节,去掉后原文本完整。
    • 前缀小于16字节时,前缀和原文本前半部分组成第一个分组,解密后的乱码会覆盖原文本开头内容,导致结果异常。

修复方案

方案1:保留BIO自动处理IV的行为(推荐)

解密时不再生成随机IV,让BIO自动读取密文开头的IV:

int aes_256_cbc_decrypt(const string& text, const unsigned char* key, string& output)
{
    if (!all_ciphers_added) {
        OpenSSL_add_all_ciphers();
        all_ciphers_added = true;
    }

    const EVP_CIPHER* cipher = EVP_get_cipherbyname(THE_ALGORITHM);
    if (cipher == NULL) {
        return -1;
    }

    BIO* memory = BIO_new_mem_buf((void*)text.c_str(), -1);
    if (memory == NULL) {
        return -1;
    }

    BIO* base64 = BIO_new(BIO_f_base64());
    if (base64 == NULL) {
        BIO_free(memory);
        return -1;
    }

    BIO_set_flags(base64, BIO_FLAGS_BASE64_NO_NL);

    BIO* decryptor = BIO_new(BIO_f_cipher());
    if (decryptor == NULL) {
        BIO_free_all(base64);
        BIO_free(memory);
        return -1;
    }

    // 不再生成随机IV,传NULL让BIO自动读取密文开头的IV
    BIO_set_cipher(decryptor, cipher, key, NULL, 0);

    BIO_push(decryptor, base64);
    BIO_push(base64, memory);

    char buff[BUFF_SIZE+1];
    int len;

    while ((len = BIO_read(decryptor, buff, BUFF_SIZE)) > 0) {
        buff[len] = '\0';
        output += buff;
    }

    BIO_free_all(decryptor);

    if (len == 0) {
        if (output.size() > 16) {
            // 此时前16字节是加密时拼接的原前缀,不再是乱码
            output = output.substr(16);
        }
        return 0;
    }
    else { // len < 0
        output.clear();
        return -1;
    }
}

方案2:禁用BIO自动写入IV,手动传递IV

如果需要完全控制IV的存储和传递,可禁用BIO自动写入IV的功能,手动将IV和密文绑定:

// 加密函数修改BIO_set_cipher相关逻辑
int aes_256_cbc_encrypt(const string& text, const unsigned char* key, string& output)
{
    // ... 原有代码 ...

    unsigned char iv[AES_CBC_IV_LENGTH];
    RAND_bytes(iv, AES_CBC_IV_LENGTH);
    BIO_set_cipher(encryptor, cipher, key, iv, 1);
    // 禁用自动写入IV
    BIO_ctrl(encryptor, BIO_CTRL_SET_CIPHER_FLAGS, EVP_CIPH_NO_PADDING, NULL);

    // ... 原有代码 ...

    // 手动将IV写入内存BIO的开头
    BIO_write(memory, iv, AES_CBC_IV_LENGTH);
    // 再写入加密后的数据
    if (BIO_write(encryptor, data, data_len) != data_len) {
        BIO_free_all(encryptor);
        free(data);
        return -1;
    }

    // ... 原有代码 ...
}

// 解密函数需要先读取开头的IV,再设置给BIO
int aes_256_cbc_decrypt(const string& text, const unsigned char* key, string& output)
{
    // ... 原有代码 ...

    // 先从base64解码后的内容中读取IV
    unsigned char iv[AES_CBC_IV_LENGTH];
    BIO_read(base64, iv, AES_CBC_IV_LENGTH);
    // 再设置IV到解密BIO
    BIO_set_cipher(decryptor, cipher, key, iv, 0);

    // ... 原有代码 ...
}

验证说明

修复后,解密时前16字节会还原为加密时拼接的原前缀,去掉后得到原文本,且前缀长度小于16字节时的异常也会消失。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 20:37:14