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

CBC-ECB加密实现中ECB填充异常问题排查求助

问题:Cryptopals第2组第10题ECB填充异常问题

这是一项加密学习练习,并非用于实际场景的加密系统开发。我正在完成Cryptopals第2组第10题,需自行实现CBC模式(基于ECB加解密)的加解密算法。我理解的CBC实现逻辑为:取当前16字节明文块与前一个密文块(初始为IV)异或,再对结果进行ECB加密。

核心问题

  • 使用自定义ECB加密函数处理16字节块时,必须额外添加16字节填充才能在解密时得到原长度输入,导致16字节明文对应32字节密文。但同挑战的其他解决方案中,16字节输入可得到16字节密文,且挑战提供的待解密文件也无额外填充。
  • 运行原代码时,解密无法得到明文末尾的C段;注释ecb_encrypt中的填充代码块并修改EVP_EncryptUpdate参数后,才能正常解密得到完整明文。怀疑填充处理逻辑存在错误,但不知从何处排查。

完整代码

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

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

typedef enum {
    BLOCK_SIZE_128_BIT      = 128 / 8,
    BLOCK_SIZE_256_BIT      = 256 / 8,
    BLOCK_SIZE_CUSTOM_VALUE = 0 
} paddingBlockSize;

typedef struct {
    void*    dataWithPadding;
    uint64_t dataLengthWithPadding;
    uint8_t  valueOfByteForPadding;
} PKCS7_Padding;  

typedef struct {
    void*    dataWithoutPadding;
    uint64_t dataLengthWithoutPadding;
    uint8_t  valueOfRemovedByteFromData;
} PKCS7_unPadding;

PKCS7_Padding* addPadding(const void* const data, const uint64_t dataLength, const uint8_t BLOCK_SIZE)
{
    if (0 == BLOCK_SIZE)
    {
        puts("ERROR: block size value must be 0 < BLOCK_SIZE < 256");
        exit(-1);
    }
    
    PKCS7_Padding* paddingResult = (PKCS7_Padding*) malloc(sizeof(PKCS7_Padding));
    if (NULL == paddingResult)
    {
        perror("problem with PKCS7_Padding* paddingResult");
        exit(-1);
    }

    uint8_t paddingBytesAmount           = BLOCK_SIZE - (dataLength % BLOCK_SIZE);
    paddingResult->valueOfByteForPadding = paddingBytesAmount;
    paddingResult->dataLengthWithPadding = dataLength + paddingBytesAmount; 
    
    uint8_t* dataWithPadding = (uint8_t*) malloc(paddingResult->dataLengthWithPadding);
    if (NULL == paddingResult)
    {
        perror("problem with uint8_t* dataWithPadding");
        exit(-1);
    }
    
    memcpy(dataWithPadding, data, dataLength); 
    for (uint8_t i = 0; i < paddingBytesAmount; i++)
    {
        dataWithPadding[dataLength + i] = paddingResult->valueOfByteForPadding;
    }
    paddingResult->dataWithPadding = dataWithPadding;

    return paddingResult;
}

PKCS7_unPadding* removePadding(const void* const data, const uint64_t dataLength)
{
    PKCS7_unPadding* unpaddingResult = (PKCS7_unPadding*) malloc(sizeof(PKCS7_unPadding));
    if (NULL == unpaddingResult)
    {
        perror("problem with PKCS7_Padding* unpaddingResult");
        exit(-1);
    }
    
    uint8_t paddingBytesAmount                  = *((uint8_t *)data + dataLength - 1);
    unpaddingResult->valueOfRemovedByteFromData = paddingBytesAmount; 
    unpaddingResult->dataLengthWithoutPadding   = dataLength - paddingBytesAmount;

    uint8_t* dataWithoutPadding = (uint8_t*) malloc(unpaddingResult->dataLengthWithoutPadding);
    if (NULL == dataWithoutPadding)
    {
        perror("problem with uint8_t* dataWithoutPadding");
        exit(-1);
    }

    memcpy(dataWithoutPadding, data, unpaddingResult->dataLengthWithoutPadding);   
    unpaddingResult->dataWithoutPadding = dataWithoutPadding;

    return unpaddingResult;
}

void freePaddingResult(PKCS7_Padding* puddingResult)
{
    free(puddingResult->dataWithPadding);
    free(puddingResult);
}

void freeUnPaddingResult(PKCS7_unPadding* unPuddingResult)
{
    free(unPuddingResult->dataWithoutPadding);
    free(unPuddingResult);
}

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;

    /*size_t padded_length = 0;
    if (plain_bytes_len % 16 > 0) {
        padded_length = plain_bytes_len + (16 - (plain_bytes_len % 16));
    } else {
        padded_length = plain_bytes_len + 16;
    }

    PKCS7_Padding *padded   = addPadding(plain_bytes, plain_bytes_len, padded_length);
    unsigned char *padded_message    = padded->dataWithPadding;
    plain_bytes_len = padded->dataLengthWithPadding;*/

    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))//
    //if(1 != EVP_EncryptUpdate(ctx, cipher_bytes, &len, padded_message, 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;

    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[strlen(plain_text)];
    size_t cipher_bytes_len = strlen(plain_text);

    ecb_encrypt(plain_text, key, (unsigned char *) &cipher_bytes, cipher_bytes_len, &cipher_bytes_len);

    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]);
    }
    printf("\n");
       
    return 0;
}

编译命令:gcc main.c -lcrypto


问题根源分析

  1. OpenSSL EVP接口默认填充误解:OpenSSL的EVP_EncryptFinal_ex会自动处理PKCS#7填充,若手动添加填充会导致双重填充——当明文长度为块大小整数倍时,手动加16字节填充,再加OpenSSL自动填充的一个完整块,最终密文长度翻倍。
  2. 填充函数调用错误:注释的填充代码中,调用addPadding时传入的第三个参数是填充后的总长度,而非块大小(16),导致填充字节数计算错误。
  3. 解密未处理填充:当前ecb_decrypt未调用removePadding移除PKCS#7填充,导致解密结果包含冗余字节或解密失败。

修复方案

1. 修正ECB加解密的填充控制

禁用OpenSSL自动填充,手动处理PKCS#7填充:

// 加密函数中添加:禁用自动填充
EVP_CIPHER_CTX_set_padding(ctx, 0);
// 解密函数中同样添加该行代码

2. 修正addPadding调用方式

调用时第三个参数必须传入块大小(如BLOCK_SIZE_128_BIT):

PKCS7_Padding *padded = addPadding(plain_bytes, plain_bytes_len, BLOCK_SIZE_128_BIT);

3. 完整修正后的ECB加解密函数

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;

    // 手动添加PKCS#7填充
    PKCS7_Padding *padded = addPadding(plain_bytes, plain_bytes_len, BLOCK_SIZE_128_BIT);
    unsigned char *padded_message = padded->dataWithPadding;
    size_t padded_len = padded->dataLengthWithPadding;

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

    // 禁用OpenSSL自动填充
    EVP_CIPHER_CTX_set_padding(ctx, 0);

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

    if(1 != EVP_EncryptUpdate(ctx, cipher_bytes, &len, padded_message, padded_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);
    freePaddingResult(padded);
    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;

    // 禁用OpenSSL自动填充
    EVP_CIPHER_CTX_set_padding(ctx, 0);

    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;

    // 手动移除PKCS#7填充
    PKCS7_unPadding *unpadded = removePadding(plain_bytes, *plain_bytes_len);
    memcpy(plain_bytes, unpadded->dataWithoutPadding, unpadded->dataLengthWithoutPadding);
    *plain_bytes_len = unpadded->dataLengthWithoutPadding;

    EVP_CIPHER_CTX_free(ctx);
    freeUnPaddingResult(unpadded);
    return 0;
}

4. 修正main函数缓冲区大小

确保加密缓冲区足够容纳填充后的数据:

unsigned char cipher_bytes[strlen((char*)plain_text) + BLOCK_SIZE_128_BIT];

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 21:07:04