OpenSSL EVP_aes_128_cbc 解密得到意外结果
OpenSSL EVP_aes_128_cbc decryption got unexpected result
我用EVP_aes_128_cbc密码加密了一个字符串,然后更改了密文的第一个字节,并解密了这个更改后的密文。出乎意料的是,它没有解密错误或得到完全错误的结果,而是得到了错误的第一个 16 字节和相同的剩余字符串。这是加密函数:
int do_crypt1(unsigned char *in, unsigned char *outbuf, int inlen, int do_encrypt)
{
unsigned char inbuf[1024];
int outlen;
EVP_CIPHER_CTX *ctx;
/*
* Bogus key and IV: we'd normally set these from
* another source.
*/
unsigned char key[32] = "test";
unsigned char iv[] = "1234567812345678";
/* Don't set key or IV right away; we want to check lengths */
ctx = EVP_CIPHER_CTX_new();
EVP_CipherInit_ex(ctx, EVP_aes_128_cbc(), NULL, NULL, NULL,
do_encrypt);
OPENSSL_assert(EVP_CIPHER_CTX_key_length(ctx) == 16);
OPENSSL_assert(EVP_CIPHER_CTX_iv_length(ctx) == 16);
/* Now we can set key and IV */
EVP_CipherInit_ex(ctx, NULL, NULL, key, iv, do_encrypt);
int update_len = 0;
for (;;) {
memcpy(inbuf, in, inlen);
if (inlen <= 0)
break;
if (!EVP_CipherUpdate(ctx, outbuf, &outlen, inbuf, inlen)) {
/* Error */
EVP_CIPHER_CTX_free(ctx);
return 0;
}
update_len += outlen;
if(inlen <= 1024)
break;
}
if (!EVP_CipherFinal_ex(ctx, outbuf+outlen, &outlen)) {
/* Error */
EVP_CIPHER_CTX_free(ctx);
return 0;
}
EVP_CIPHER_CTX_free(ctx);
update_len += outlen;
return update_len;
}
主要是:
unsigned char* b = (unsigned char*)malloc(1024);
unsigned char* hmac_code = (unsigned char*)malloc(1024);
int len = do_crypt1(value, hmac_code, strlen(value), AES_ENCRYPT);
printf("%s\n", value);
for (i = 0; i < len; i++)
printf("%x", *(hmac_code + i));
printf("\n");
printf("%d\n", len);
// printf("%s\n", hmac_code);
*hmac_code = 0x23;
len = do_crypt1(hmac_code, b, len, AES_DECRYPT);
printf("%d\n", len);
printf("%s\n", b);
free(hmac_code);
free(b);
result
谁能告诉我原因以及如何解决这个问题?
实际上,只要您的明文至少为 17 个字节,第一个 17 个解密字节应该是错误的——但是由于您将无效解密显示为文本,一些的字节可能是不可见的:在您的示例中,输出短了 2 个字符,因此显然前 17 个字符中的 2 个字符不可见。这是像 AES 这样的 16 字节块密码的 2 块(或更多)CBC 密文的第一个块(开始时)损坏的预期结果;查看 wikipedia 中的图表以了解原因。
如果要检测密文损坏,请不要使用CBC模式,至少不要单独使用。 Common/standard 现在的解决方案是:
添加认证,如HMAC。 (有趣的是,你的代码已经使用了变量名 hmac_code
,即使你没有做任何与 HMAC 相关的事情。)
使用经过身份验证的加密模式,如 GCM,它在内部有效地结合了加密和(某种类型的)MAC。今天大多数经过身份验证的模式,包括 GCM,也支持 'additional' 或 'associated' 经过身份验证但未加密的数据,因此称为 AEAD,但您可能关心也可能不关心.
使用错误传播模式。这些在几十年前很流行,大约在尼克松、福特、卡特和里根总统的时代,但现在大多被认为已经过时并且不受 OpenSSL 的直接支持。
此外,顺便说一句,您的代码完全不适合超过 1024 字节的值,而您的测试显然不会执行这些值。首先你根本不需要将这样的值分解成块,但如果出于某种原因你想要,你实现的方法是错误的。
另外,CBC 的 IV 应该是不同的 和 每次都不可预测;使用像这样的硬编码值会使您面临两种不同的 类 攻击。一般来说,你会在 Stacks 上得到的与安全相关的建议是“不要自己动手”。由不知道自己在做什么的人编写的加密代码,即使 if/when 它产生正确的输出,通常也是不安全的。这是 crypto/security 软件与其他软件的主要区别;你可以很容易地看到你的编辑器或电子表格或数据库是否产生正确或错误的输出,只要输出正确,这通常就是你所需要的,但你不能通过查看 encryption/decryption 程序的输出来判断是否安全。
我用EVP_aes_128_cbc密码加密了一个字符串,然后更改了密文的第一个字节,并解密了这个更改后的密文。出乎意料的是,它没有解密错误或得到完全错误的结果,而是得到了错误的第一个 16 字节和相同的剩余字符串。这是加密函数:
int do_crypt1(unsigned char *in, unsigned char *outbuf, int inlen, int do_encrypt)
{
unsigned char inbuf[1024];
int outlen;
EVP_CIPHER_CTX *ctx;
/*
* Bogus key and IV: we'd normally set these from
* another source.
*/
unsigned char key[32] = "test";
unsigned char iv[] = "1234567812345678";
/* Don't set key or IV right away; we want to check lengths */
ctx = EVP_CIPHER_CTX_new();
EVP_CipherInit_ex(ctx, EVP_aes_128_cbc(), NULL, NULL, NULL,
do_encrypt);
OPENSSL_assert(EVP_CIPHER_CTX_key_length(ctx) == 16);
OPENSSL_assert(EVP_CIPHER_CTX_iv_length(ctx) == 16);
/* Now we can set key and IV */
EVP_CipherInit_ex(ctx, NULL, NULL, key, iv, do_encrypt);
int update_len = 0;
for (;;) {
memcpy(inbuf, in, inlen);
if (inlen <= 0)
break;
if (!EVP_CipherUpdate(ctx, outbuf, &outlen, inbuf, inlen)) {
/* Error */
EVP_CIPHER_CTX_free(ctx);
return 0;
}
update_len += outlen;
if(inlen <= 1024)
break;
}
if (!EVP_CipherFinal_ex(ctx, outbuf+outlen, &outlen)) {
/* Error */
EVP_CIPHER_CTX_free(ctx);
return 0;
}
EVP_CIPHER_CTX_free(ctx);
update_len += outlen;
return update_len;
}
主要是:
unsigned char* b = (unsigned char*)malloc(1024);
unsigned char* hmac_code = (unsigned char*)malloc(1024);
int len = do_crypt1(value, hmac_code, strlen(value), AES_ENCRYPT);
printf("%s\n", value);
for (i = 0; i < len; i++)
printf("%x", *(hmac_code + i));
printf("\n");
printf("%d\n", len);
// printf("%s\n", hmac_code);
*hmac_code = 0x23;
len = do_crypt1(hmac_code, b, len, AES_DECRYPT);
printf("%d\n", len);
printf("%s\n", b);
free(hmac_code);
free(b);
result
谁能告诉我原因以及如何解决这个问题?
实际上,只要您的明文至少为 17 个字节,第一个 17 个解密字节应该是错误的——但是由于您将无效解密显示为文本,一些的字节可能是不可见的:在您的示例中,输出短了 2 个字符,因此显然前 17 个字符中的 2 个字符不可见。这是像 AES 这样的 16 字节块密码的 2 块(或更多)CBC 密文的第一个块(开始时)损坏的预期结果;查看 wikipedia 中的图表以了解原因。
如果要检测密文损坏,请不要使用CBC模式,至少不要单独使用。 Common/standard 现在的解决方案是:
添加认证,如HMAC。 (有趣的是,你的代码已经使用了变量名
hmac_code
,即使你没有做任何与 HMAC 相关的事情。)使用经过身份验证的加密模式,如 GCM,它在内部有效地结合了加密和(某种类型的)MAC。今天大多数经过身份验证的模式,包括 GCM,也支持 'additional' 或 'associated' 经过身份验证但未加密的数据,因此称为 AEAD,但您可能关心也可能不关心.
使用错误传播模式。这些在几十年前很流行,大约在尼克松、福特、卡特和里根总统的时代,但现在大多被认为已经过时并且不受 OpenSSL 的直接支持。
此外,顺便说一句,您的代码完全不适合超过 1024 字节的值,而您的测试显然不会执行这些值。首先你根本不需要将这样的值分解成块,但如果出于某种原因你想要,你实现的方法是错误的。
另外,CBC 的 IV 应该是不同的 和 每次都不可预测;使用像这样的硬编码值会使您面临两种不同的 类 攻击。一般来说,你会在 Stacks 上得到的与安全相关的建议是“不要自己动手”。由不知道自己在做什么的人编写的加密代码,即使 if/when 它产生正确的输出,通常也是不安全的。这是 crypto/security 软件与其他软件的主要区别;你可以很容易地看到你的编辑器或电子表格或数据库是否产生正确或错误的输出,只要输出正确,这通常就是你所需要的,但你不能通过查看 encryption/decryption 程序的输出来判断是否安全。