openSSL下的AES CBC加密会产生意外结果

时间:2017-05-16 17:16:38

标签: openssl aes cbc-mode

我使用openSSL库函数来加密和解密基于AES-128 / CBC的目的。

代码如下所示(不要被THROW / EXIT宏混淆,它们只是得到的)。无论函数返回什么,您都会看到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解密32bytes cipered文本时 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

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

1 个答案:

答案 0 :(得分:2)

用于填充。这些函数填充明文,使得结果长度是块大小的倍数(16)。已经是块的倍数的数据仍然填充,因为否则无法在有目的地添加的填充和恰好看起来就像一个有效的填充。

填充的通常方法是添加N个字节的值N.因此,对于一个完整的块,要么是一个0x01字节,要么是两个0x02个字节等,要多达十六个0x10字节,就像你的情况一样。

来自文档here

  

如果启用了填充(默认值),那么 EVP_EncryptFinal_ex()会对&#34; final&#34;进行加密。数据,即保留在部分块中的任何数据。它使用标准块填充(也称为PKCS填充),如下面的NOTES部分所述。

并在NOTES:

  

PKCS填充通过添加n个值为n的填充字节来使加密数据的总长度为块大小的倍数。总是添加填充,因此如果数据已经是块大小的倍数,则n将等于块大小。例如,如果块大小为8且要加密11个字节,则将添加5个值为5的填充字节。

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

  

EVP_CIPHER_CTX_set_padding()启用或禁用填充。默认情况下,使用标准块填充填充加密操作,并在解密时检查和删除填充。如果pad参数为零,则不执行填充,则加密或解密的数据总量必须是块大小的倍数,否则将发生错误。