openSSL 下的 AES CBC 加密给出了意想不到的结果

AES CBC encryption under openSSL gives unexpected Result

我根据AES-128/CBC.

使用openSSL库函数进行加解密

代码如下所示(不要被 THROW / EXIT 宏混淆,它们只是 goto)。无论函数 returns 是什么,您都会看到 printf 输出以跟踪来回发送到 OpenSSL 的内容:

enc_status_t aes_cipher_ext(uint8_t should_encrypt, enc_aes128_key_t *key, uint8_t *iv, void *in, uint32_t inlen, void *out, uint32_t outlen, uint32_t * outlen_act)

{
    enc_status_t status;
    buffer_t inbuf, outbuf;
    int ok;
    const uint32_t BUFSIZE = AES_BUFFERSIZE;
    uint8_t *read_buf = NULL;
    uint8_t *cipher_buf = NULL;
    uint32_t blocksize;
    int out_len;
    uint32_t numRead;

    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();

    /* initialize input read buffer */

    status = buffer_init(&inbuf, in, inlen);
    if (status != ENC_OK) EXIT();
    status = buffer_init(&outbuf, out, outlen);
    if (status != ENC_OK) EXIT();

    /* initialize AES engine */
    ok = EVP_CipherInit(ctx, EVP_aes_128_cbc(), key->data, iv, should_encrypt);

    if(!ok) THROW(status = ENC_ERR_OPENSSL);

    blocksize = EVP_CIPHER_CTX_block_size(ctx);


    read_buf   = malloc(BUFSIZE);
    cipher_buf = malloc(BUFSIZE + blocksize);
    out_len = BUFSIZE + blocksize;

    while (TRUE) {  /*lint !e716 see below */
        /* read data and cipher */
        status = buffer_read(&inbuf, read_buf, BUFSIZE, &numRead);
        if (status != ENC_OK) EXIT();

        printf("AES input: num read = %d\n", numRead);
        dump_ram(read_buf, numRead);

        if (should_encrypt)
        {
            ok = EVP_EncryptUpdate(ctx, cipher_buf, &out_len, read_buf, numRead);
        }
        else
        {
            ok = EVP_DecryptUpdate(ctx, cipher_buf, &out_len, read_buf, numRead);
        }
        status = buffer_write(&outbuf, cipher_buf, out_len);
        printf("AES result bytes: ok=%d\n", ok);
        dump_ram(cipher_buf, out_len);
        if (status != ENC_OK) EXIT();

        if (numRead < BUFSIZE)
        { 
            break; /* this breaks the while */  
        }
    }
    /* handle last block */

    if (should_encrypt)
    {

        ok = EVP_EncryptFinal_ex(ctx, cipher_buf, &out_len);
    }
    else
    {
        ok = EVP_DecryptFinal_ex(ctx, cipher_buf, &out_len);
    }
    printf("AES LAST: ok=%d\n", ok);
    dump_ram(cipher_buf, out_len);
    status = buffer_write(&outbuf, cipher_buf, out_len);
    if (status != ENC_OK) EXIT();


    *outlen_act = outbuf.act_len;


exit_label:
    /* de allocate */
    if (cipher_buf) free(cipher_buf);
    if (read_buf) free(read_buf);
    EVP_CIPHER_CTX_free(ctx);
    return status;

}

当我用

提供函数时
 Key   = 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36 
 IV    = 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36
 Plain = 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36

我得到输出:

AES input: num read = 16

31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36
AES result bytes: ok=1

85 7d 51 22 1d 87 4e 53 63 3c da 9f d5 dc 7d 29   <-- first result
AES LAST: ok=1

71 d5 5e 76 23 14 db 09 f6 d8 04 2f d7 5d b6 c9   <-- second result

总而言之,我从 16 字节的纯文本中得到 32 字节的密文:

C = 85 7d 51 22 1d 87 4e 53 63 3c da 9f d5 dc 7d 29 71 d5 5e 76 23 14 db 09 f6 d8 04 2f d7 5d b6 c9

问:为什么openSSL会额外增加16个字节? IMO 在这种情况下应该没有填充...

用这个32字节的密文进行解密,我得到,使用相同的密码

AES input: num read = 32

85 7d 51 22 1d 87 4e 53 63 3c da 9f d5 dc 7d 29
71 d5 5e 76 23 14 db 09 f6 d8 04 2f d7 5d b6 c9
AES result bytes: ok=1

31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36
AES LAST: ok=1

所以我得到了原始的 16 字节纯文本,DecryptFinal 返回零字节(因此没有转储)。

为了比较,当我解密 32 字节的密文时 this tool 结果是

31  32  33  34  35  36  37  38  39  30  31  32  33  34  35  36
10  10  10  10  10  10  10  10  10  10  10  10  10  10  10  10

1   2   3   4   5   6   7   8   9   0   1   2   3   4   5   6
.   .   .   .   .   .   .   .   .   .   .   .   .   .   .   .

所以结果是原始纯文本(第一行)加上额外的一行0x10。

尝试使用相同的工具来加密我的纯文本只会给我一个 16 字节的加密字符串,这与我从 openSSL 获得的前 16 个字节相同:

85 7d 51 22 1d 87 4e 53 63 3c da 9f d5 dc 7d 29

为什么?问题出在哪里??

用于填充。这些函数填充明文,以便生成的长度是块大小 (16) 的倍数。已经是块的倍数的数据仍然被 填充 ,因为否则不可能区分故意添加的填充和恰好 的纯文本]看起来像一个有效的填充。

通常的填充方式是添加 N 个字节的值 N。所以一个 0x01 字节,两个 0x02 字节等,最多十六 0x10 个字节完整块,如您的情况。

来自文档 here:

If padding is enabled (the default) then EVP_EncryptFinal_ex() encrypts the "final" data, that is any data that remains in a partial block. It uses standard block padding (aka PKCS padding) as described in the NOTES section, below.

在注释下:

PKCS padding works by adding n padding bytes of value n to make the total length of the encrypted data a multiple of the block size. Padding is always added so if the data is already a multiple of the block size n will equal the block size. For example if the block size is 8 and 11 bytes are to be encrypted then 5 padding bytes of value 5 will be added.

但是您可以控制是否启用填充:

EVP_CIPHER_CTX_set_padding() enables or disables padding. By default encryption operations are padded using standard block padding and the padding is checked and removed when decrypting. If the pad parameter is zero then no padding is performed, the total amount of data encrypted or decrypted must then be a multiple of the block size or an error will occur.