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

使用OpenSSL AES-ECB解密时丢失最后一段明文的问题

OpenSSL AES-ECB解密丢失最后一段明文问题排查

问题描述

用OpenSSL C API实现AES-ECB加密解密时,解密后丢失最后一段明文(示例中所有'C'字符)。EVP_DecryptUpdate返回长度为32而非预期的48,EVP_DecryptFinal_ex调用失败,最终明文长度仅为32,怀疑是填充问题但无法定位原因。

复现代码

#include <stdio.h>
#include <string.h>

#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>

int ecb_encrypt(unsigned char *plain_bytes, unsigned char *key, unsigned char *cipher_bytes, size_t plain_bytes_len, size_t *cipher_bytes_len) {
    EVP_CIPHER_CTX *ctx;
    int len = 0;

    if(!(ctx = EVP_CIPHER_CTX_new()))
        return 4;

    if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, NULL))
        return 5;
    
    if(1 != EVP_EncryptUpdate(ctx, cipher_bytes, &len, plain_bytes, plain_bytes_len))
        return 6;
    *cipher_bytes_len = len;

    if(1 != EVP_EncryptFinal_ex(ctx, cipher_bytes + *cipher_bytes_len, &len))
        return 7;

    EVP_CIPHER_CTX_free(ctx);
    return 0;
}

int ecb_decrypt(unsigned char *cipher_bytes, unsigned char *key, unsigned char *plain_bytes, size_t cipher_bytes_len, size_t *plain_bytes_len) {
    EVP_CIPHER_CTX *ctx;
    int len = 0;

    if(!(ctx = EVP_CIPHER_CTX_new()))
        return 4;

    if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, NULL))
        return 5;

    if(1 != EVP_DecryptUpdate(ctx, plain_bytes, &len, cipher_bytes, cipher_bytes_len))
        return 6;
    *plain_bytes_len = len;
    printf("len = %d\n", len);
    printf("plain_bytes_len = %lu\n", *plain_bytes_len);
    if(1 != EVP_DecryptFinal_ex(ctx, plain_bytes + *plain_bytes_len, &len))
        return 7;
    
    EVP_CIPHER_CTX_free(ctx);
    return 0;
}

int main() {
    unsigned char plain_text[] = "AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCC";
    unsigned char key[]        = "ABCDEFGHIJKLMNOP";
    unsigned char cipher_bytes[48];
    size_t cipher_bytes_len = 0;

    ecb_encrypt(plain_text, key, (unsigned char *) &cipher_bytes, 48, &cipher_bytes_len);
    printf("cipher_bytes_len = %lu\n", cipher_bytes_len);

    //decrypt
    size_t plain_bytes_len = 0;
    unsigned char plain_bytes[cipher_bytes_len];

    ecb_decrypt(cipher_bytes, key, (unsigned char *) &plain_bytes, cipher_bytes_len, &plain_bytes_len);

    printf("plain_bytes_len = %lu\n", plain_bytes_len);
    for(size_t i = 0; i < plain_bytes_len; i++){
        printf("%c", plain_bytes[i]);
    }
       
    return 0;
}

程序输出

cipher_bytes_len = 48
len = 32
plain_bytes_len = 32
plain_bytes_len = 32
AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBB

问题根源与修复方案

1. 加密函数未累加EVP_EncryptFinal_ex的输出长度

AES-128块大小为16字节,当明文长度是块大小整数倍时(示例中48字节正好是3个块),EVP_EncryptFinal_ex会额外添加一个完整的PKCS#7填充块(16字节)。原代码未将这个填充块的长度加到最终密文长度中,导致密文长度仅为48字节(实际应为64字节)。

修复后的加密函数:

int ecb_encrypt(unsigned char *plain_bytes, unsigned char *key, unsigned char *cipher_bytes, size_t plain_bytes_len, size_t *cipher_bytes_len) {
    EVP_CIPHER_CTX *ctx;
    int len = 0;

    if(!(ctx = EVP_CIPHER_CTX_new()))
        return 4;

    if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, NULL))
        return 5;
    
    if(1 != EVP_EncryptUpdate(ctx, cipher_bytes, &len, plain_bytes, plain_bytes_len))
        return 6;
    *cipher_bytes_len = len;

    if(1 != EVP_EncryptFinal_ex(ctx, cipher_bytes + *cipher_bytes_len, &len))
        return 7;
    *cipher_bytes_len += len; // 累加填充块长度

    EVP_CIPHER_CTX_free(ctx);
    return 0;
}

2. 解密函数未累加EVP_DecryptFinal_ex的输出长度

EVP_DecryptFinal_ex负责处理填充并返回去除填充后的明文长度,原代码未将该长度加到最终明文长度中。同时,由于加密后的密文长度错误,导致解密时无法正确解析填充,函数调用失败。

修复后的解密函数:

int ecb_decrypt(unsigned char *cipher_bytes, unsigned char *key, unsigned char *plain_bytes, size_t cipher_bytes_len, size_t *plain_bytes_len) {
    EVP_CIPHER_CTX *ctx;
    int len = 0;

    if(!(ctx = EVP_CIPHER_CTX_new()))
        return 4;

    if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, key, NULL))
        return 5;

    if(1 != EVP_DecryptUpdate(ctx, plain_bytes, &len, cipher_bytes, cipher_bytes_len))
        return 6;
    *plain_bytes_len = len;

    if(1 != EVP_DecryptFinal_ex(ctx, plain_bytes + *plain_bytes_len, &len))
        return 7;
    *plain_bytes_len += len; // 累加Final阶段输出的明文长度
    
    EVP_CIPHER_CTX_free(ctx);
    return 0;
}

3. 主函数缓冲区大小不足

原代码中cipher_bytes仅定义为48字节,无法容纳加密后的64字节密文;解密时plain_bytes按错误的密文长度分配,缓冲区大小不足。

修复后的主函数:

int main() {
    unsigned char plain_text[] = "AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCC";
    unsigned char key[]        = "ABCDEFGHIJKLMNOP";
    unsigned char cipher_bytes[64]; // 扩容至64字节,适配加密后长度
    size_t cipher_bytes_len = 0;

    ecb_encrypt(plain_text, key, cipher_bytes, 48, &cipher_bytes_len);
    printf("cipher_bytes_len = %lu\n", cipher_bytes_len);

    //decrypt
    size_t plain_bytes_len = 0;
    unsigned char plain_bytes[64]; // 分配足够大的缓冲区

    ecb_decrypt(cipher_bytes, key, plain_bytes, cipher_bytes_len, &plain_bytes_len);

    printf("plain_bytes_len = %lu\n", plain_bytes_len);
    for(size_t i = 0; i < plain_bytes_len; i++){
        printf("%c", plain_bytes[i]);
    }
    printf("\n");
       
    return 0;
}

补充说明

  • OpenSSL默认使用PKCS#7填充:明文长度为块大小整数倍时,添加一个完整块(每个字节值为块大小0x10);解密时EVP_DecryptFinal_ex自动去除填充。
  • 调试时可调用ERR_print_errors_fp(stderr)打印OpenSSL错误信息,快速定位问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 05:24:51