简体   繁体   English

EVP_DecryptFinal_ex中用于解密的最终块的正确格式是什么?

[英]What is the correct format for final block in EVP_DecryptFinal_ex for decryption?

I have implemented a simple AES-256-GCM encryption and decryption for learning purposes. 我已出于学习目的实施了简单的AES-256-GCM加密和解密。 While testing my code if I enter strings lengths multiples of 6 then I get the correct output but for other cases the decrypted data has some garbage characters appended to it. 在测试代​​码时,如果我输入的字符串长度为6的倍数,则可以得到正确的输出,但在其他情况下,解密后的数据将附加一些垃圾字符。

Case1:
Enter string: abcdef
Enter key: sdasdasdsa
-^%�
abcdef
6

Case2:
Enter string: abcdefghi
Enter key: sadsadsad
򈇢\h�,�[�
abcdefghi�\�
-1

Now I read on the https://www.openssl.org/docs/crypto/EVP_EncryptFinal_ex.html that 现在,我在https://www.openssl.org/docs/crypto/EVP_EncryptFinal_ex.html上阅读了

EVP_DecryptFinal() will return an error code if padding is enabled and the
final block is not correctly formatted.

But since padding is enabled by default in this case I am guessing that the problem is with the correct formatting of the final block. 但是由于在这种情况下默认情况下会启用填充,所以我猜测问题出在最后一块的正确格式上。 I have attached my code below: 我在下面附加了我的代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/evp.h>

void handleErrors()
{
    printf("Some error occured\n");
}

int encrypt(unsigned char *plaintext, int plaintext_len, unsigned char *aad,
    int aad_len, unsigned char *key, unsigned char *iv,
    unsigned char *ciphertext, unsigned char *tag)
{
    EVP_CIPHER_CTX *ctx;

    int len, ciphertext_len=0;

    /* Create and initialise the context */
    if(!(ctx = EVP_CIPHER_CTX_new()))
        handleErrors();

    /* Initialise the encryption operation. */
    if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL))
        handleErrors();

    /* Set IV length if default 12 bytes (96 bits) is not appropriate */
    if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL))
        handleErrors();

    /* Initialise key and IV */
    if(1 != EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv)) handleErrors();

    /* Provide any AAD data. This can be called zero or more times as
     * required
     */
    if(1 != EVP_EncryptUpdate(ctx, NULL, &len, aad, aad_len))
        handleErrors();

    /* Provide the message to be encrypted, and obtain the encrypted output.
     * EVP_EncryptUpdate can be called multiple times if necessary
     */
    if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len))
        handleErrors();
    ciphertext_len+= len;

    /* Finalise the encryption. Normally ciphertext bytes may be written at
     * this stage, but this does not occur in GCM mode
     */
    if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) handleErrors();
    ciphertext_len += len;

    /* Get the tag */
    if(1 != EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag))
        handleErrors();

    /* Clean up */
    EVP_CIPHER_CTX_free(ctx);

    return ciphertext_len;
}


int decrypt(unsigned char *ciphertext, int ciphertext_len, unsigned char *aad,
    int aad_len, unsigned char *tag, unsigned char *key, unsigned char *iv,
    unsigned char *plaintext)
{
    EVP_CIPHER_CTX *ctx;
    int len, plaintext_len=0, ret;

    /* Create and initialise the context */
    if(!(ctx = EVP_CIPHER_CTX_new())) 
        handleErrors();

    /* Initialise the decryption operation. */
    if(!EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL))
        handleErrors();

    /* Set IV length. Not necessary if this is 12 bytes (96 bits) */
    if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL))
        handleErrors();

    /* Initialise key and IV */
    if(!EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv)) handleErrors();

    /* Provide any AAD data. This can be called zero or more times as
     * required
     */
    if(!EVP_DecryptUpdate(ctx, NULL, &len, aad, aad_len))
        handleErrors();

    /* Provide the message to be decrypted, and obtain the plaintext output.
     * EVP_DecryptUpdate can be called multiple times if necessary
     */
    if(!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len))
        handleErrors();
    plaintext_len+= len;

    /* Set expected tag value. Works in OpenSSL 1.0.1d and later */
    if(!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, tag))
        handleErrors();

    /* Finalise the decryption. A positive return value indicates success,
     * anything else is a failure - the plaintext is not trustworthy.
     */
    ret = EVP_DecryptFinal_ex(ctx, plaintext + len, &len);

    /* Clean up */
    EVP_CIPHER_CTX_free(ctx);

    if(ret > 0)
    {
        /* Success */
        plaintext_len += len;
        return plaintext_len;
    }
    else
    {
        /* Verify failed */
        return -1;
    }
}


int main (void)
{
    unsigned char str[1024],key[10],ciphertext[1024+EVP_MAX_BLOCK_LENGTH],tag[100],pt[1024];
    unsigned char iv[]="1234567890abcdef";
    unsigned char aad[]="1234567890123456";
    int k;
    printf("Enter string: ");
    scanf("%s",str);
    printf("Enter key: ");
    scanf("%s",key);

    encrypt(str, strlen(str), aad, strlen(aad), key, iv, ciphertext, tag);
    printf("%s\n",ciphertext);
    k = decrypt(ciphertext, strlen(ciphertext), aad, strlen(aad), tag, key, iv, pt);
    printf("%s\n%d\n",pt,k);
}

decrypt(ciphertext, strlen(ciphertext), aad, strlen(aad), tag, key, iv, pt);

decrypt(ciphertext, strlen(ciphertext), ... is wrong. There could be an embedded NULL in the cipher text, in which case it will be truncated. In your case, additional characters are being fed to the decrypt function. Its hard to say how many - its until strlen happen to run into a NULL in memory. decrypt(ciphertext, strlen(ciphertext), ...是错误的。 decrypt(ciphertext, strlen(ciphertext), ...可能存在嵌入的NULL,在这种情况下它将被截断。在您的情况下,其他字符被馈送到decrypt函数中。说出多少-它直到被strlen碰巧碰到内存中的NULL。

You need to capture the return value of encrypt and decrpyt to set various length's properly: 您需要捕捉的返回值encryptdecrpyt正确设置各种长度的:

int x;
...

x = encrypt(str, strlen(str), aad, strlen(aad), key, iv, ciphertext, tag);
...

x = decrypt(ciphertext, x, aad, strlen(aad), tag, key, iv, pt);
...

You might have the same problem for aad, strlen(aad) , but I don't think it has revealed itself yet. 您可能会对aad, strlen(aad)遇到相同的问题,但我认为它尚未显示出来。


What is the correct format for final block in EVP_DecryptFinal_ex for decryption? EVP_DecryptFinal_ex中用于解密的最终块的正确格式是什么?

To get back to the titular question: there is none. 回到名义上的问题:没有。 Your problem lies elsewhere. 您的问题出在其他地方。


You probably have an overflow in the unsigned char *plaintext buffer used in the decrypt function. 您可能在decrypt函数中使用的unsigned char *plaintext缓冲区中溢出。 You're not passing in a length, so decrypt happily writes beyond the length of it... 您没有传递长度,所以decrypt愉快地写超过了长度...

In the above example you are defining your key, IV data to be strings. 在上面的示例中,您将密钥IV数据定义为字符串。 A key and IV should never consist of a string as they do not contain any possible byte. 键和IV绝不能包含字符串,因为它们不包含任何可能的字节。 This is required to maximize the amount of security; 这是最大限度地提高安全性所必需的; currently you are limiting the amount of possible keys. 当前,您正在限制可能的密钥数量。 Keys should be generated by a function whose output is indistinguishable from random. 键应由其输出与随机输出无法区分的函数生成。

You should use a Password Key Derivation Function (PBKDF) such as PBKDF2 to create a key from a password. 您应该使用诸如PBKDF2之类的密码密钥派生功能(PBKDF)从密码中创建密钥。 The IV should be random and send with the ciphertext. IV应该是随机的,并以密文发送。 The IV and key should be statically sized, the IV being 16 bytes and the key 16, 24 or 32 bytes. IV和密钥应为静态大小,IV为16字节,密钥为16、24或32字节。

To test however you could use simple array initialization (32 bytes for the key as you are using AES-256): 但是,要测试,您可以使用简单的数组初始化(使用AES-256时,密钥为32字节):

unsigned char key[32] = { 0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,
                          0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,
                          0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,
                          0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00 };
unsigned char iv[16] = { 0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,
                         0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00 };

or you could allocate X bytes of memory and fill it with a value of course, as long as you give the function the correct size. 或者,您可以分配X字节的内存并用一个当然的值填充它,只要您为函数提供正确的大小即可。

You need to replace all occurrences of strlen() with sizeof() , except for plaintext and ciphertext . 您需要用sizeof()替换所有出现的strlen()plaintextciphertext除外。 In the former you are actually encrypting a string, so strlen makes sense. 在前一种情况下,您实际上是在加密字符串,因此strlen是有意义的。 In the latter you need to use the result of the encrypt operation. 在后者中,您需要使用encrypt操作的结果。 The ciphertext is contained within a buffer of the length (correctly) returned by your encryption method. 密文包含在加密方法返回的长度(正确)的缓冲区中。

In the end modern ciphers operate on bytes instead of characters , so you need to provide the size in bytes for all input. 最后,现代密码使用字节而不是字符进行操作 ,因此您需要提供所有输入的字节大小。 This kind of issue is present for any language that treats bytes and characters using the same primitive type ( char for the C-language of course). 对于使用相同原始类型(当然,C语言为char处理字节和字符的任何语言,都会出现这种问题。 It also tends to hide encoding/decoding issues (eg with regards to UTF-8). 它还倾向于隐藏编码/解码问题(例如,关于UTF-8)。

If you want to handle unknown lengths then you have to call EVP_EncryptUpdate or EVP_DecryptUpdate multiple times, keeping score on how many bytes are returned (as you are doing now). 如果要处理未知长度,则必须多次调用EVP_EncryptUpdateEVP_DecryptUpdate ,以记分返回的字节数(如您现在所做的那样)。 Then at the end of the input you simply call the update method a final time and then call EVP_EncryptFinal_ex or EVP_DecryptFinal_ex . 然后,在输入结束时,只需在最后一次调用update方法,然后调用EVP_EncryptFinal_exEVP_DecryptFinal_ex In that case you should of course refactor your method into an init/update/final part and use some buffer for the input and output. 在那种情况下,您当然应该将方法重构为init / update / final部分,并为输入和输出使用一些缓冲区。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM