我已经实现了一个简单的AES-256-GCM加密和解密用于学习目的。在测试我的代码时,如果我输入字符串长度为6的倍数,那么我得到正确的输出,但在其他情况下,解密的数据会附加一些乱码。
Case1:
Enter string: abcdef
Enter key: sdasdasdsa
-^%�
abcdef
6
Case2:
Enter string: abcdefghi
Enter key: sadsadsad
\h�,�[�
abcdefghi�\�
-1
现在我读了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.
但是,由于在这种情况下默认启用了填充,我猜测问题在于最终块的格式是否正确。我在下面附上了我的代码:
#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);
}
答案 0 :(得分:3)
decrypt(ciphertext, strlen(ciphertext), aad, strlen(aad), tag, key, iv, pt);
decrypt(ciphertext, strlen(ciphertext), ...
错了。密文中可能存在嵌入的NULL,在这种情况下它将被截断。在您的情况下,正在向decrypt
函数提供其他字符。很难说有多少 - 直到strlen
碰巧在内存中遇到NULL。
您需要捕获encrypt
和decrpyt
的返回值以正确设置各种长度:
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);
...
aad, strlen(aad)
可能会遇到同样的问题,但我认为它还没有显露出来。
EVP_DecryptFinal_ex中用于解密的最终块的正确格式是什么?
回到名义上的问题:没有。你的问题出在其他地方。
unsigned char *plaintext
函数中使用的decrypt
缓冲区可能存在溢出。你没有传递一段时间,所以decrypt
高兴地写了超出它的长度......
答案 1 :(得分:2)
在上面的示例中,您将键,IV数据定义为字符串。密钥和IV不应该由字符串组成,因为它们不包含任何可能的字节。这是最大化安全性所必需的;目前,您正在限制可能的密钥数量。键应该由一个函数生成,该函数的输出与随机无法区分。
您应该使用密码密钥派生函数(PBKDF)(如PBKDF2)从密码创建密钥。 IV应该是随机的并且用密文发送。 IV和密钥应该是静态大小的,IV是16字节,密钥是16,24或32字节。
然而,要测试你可以使用简单的数组初始化(当你使用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 };
或者你可以分配X字节的内存并用一个值填充它,只要你给函数正确的大小。
除了strlen()
和sizeof()
之外,您需要将所有plaintext
替换为ciphertext
。在前者中,您实际上是在加密字符串,因此strlen
是有意义的。在后者中,您需要使用encrypt
操作的结果。密文包含在加密方法返回的长度(正确)的缓冲区中。
最后,现代密码操作 on bytes 而不是字符,因此您需要为所有输入提供字节大小。对于使用相同基元类型处理字节和字符的任何语言都存在这种问题(当然,对于C语言,char
)。它还倾向于隐藏编码/解码问题(例如关于UTF-8)。
如果你想处理未知长度,你必须多次调用EVP_EncryptUpdate
或EVP_DecryptUpdate
,保持返回多少字节的分数(正如你现在所做的那样)。然后在输入结束时,您只需最后一次调用更新方法,然后调用EVP_EncryptFinal_ex
或EVP_DecryptFinal_ex
。在这种情况下,您当然应该将您的方法重构为 init / update / final 部分,并使用一些缓冲区来输入和输出。