EVP_DecryptFinal_ex调用返回失败,解密数据错误的解决方法

Posted anda0109

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了EVP_DecryptFinal_ex调用返回失败,解密数据错误的解决方法相关的知识,希望对你有一定的参考价值。

在使用openssl进行数据加解密时,解密数据时偶尔会出现问题,即当数据长度为16的整数倍时会出现解密数据部分不正确的情况。此情况下EVP_DecryptFinal_ex函数调用失败。查阅资料如下:

【EVP_EncryptFinal_ex】

    该函数处理最后(Final)的一段数据。在函数在padding功能打开的时候(缺省)才有效,这时候,它将剩余的最后的所有数据进行加密处理。该算法使用标志的块padding方式(AKA PKCS padding)。加密后的数据写入到参数out里面,参数out的长度至少应该能够一个加密块。写入的数据长度信息输入到outl参数里面。该函数调用后,表示所有数据都加密完了,不应该再调用EVP_EncryptUpdate函数。如果没有设置padding功能,那么本函数不会加密任何数据,如果还有剩余的数据,那么就会返回错误信息,也就是说,这时候数据总长度不是块长度的整数倍。操作成功返回1,否则返回0。

    PKCS padding标准是这样定义的,在被加密的数据后面加上n个值为n的字节,使得加密后的数据长度为加密块长度的整数倍。无论在什么情况下,都是要加上padding的,也就是说,如果被加密的数据已经是块长度的整数倍,那么这时候n就应该等于块长度。比如,如果块长度是9,要加密的数据长度是11,那么5个值为5的字节就应该增加在数据的后面。

    【EVP_DecryptInit_ex, EVP_DecryptUpdate和EVP_DecryptFinal_ex】

    这三个函数是上面三个函数相应的解密函数。这些函数的参数要求基本上都跟上面相应的加密函数相同。如果padding功能打开了,EVP_DecryptFinal会检测最后一段数据的格式,如果格式不正确,该函数会返回错误代码。此外,如果打开了padding功能,EVP_DecryptUpdate函数的参数out的长度应该至少为(inl+cipher_block_size)字节;但是,如果加密块的长度为1,则其长度为inl字节就足够了。三个函数都是操作成功返回1,否则返回0。

    需要注意的是,虽然在padding功能开启的情况下,解密操作提供了错误检测功能,但是该功能并不能检测输入的数据或密钥是否正确,所以即便一个随机的数据块也可能无错的完成该函数的调用。如果padding功能关闭了,那么当解密数据长度是块长度的整数倍时,操作总是返回成功的结果。

将代码修改如下,即当解密长度为16的整数倍时,执行函数EVP_DecryptFinal_ex,则解密成功。
int AesEncryptBuf (  IN unsigned char * szIn,
				     IN int inLen,
					 OUT unsigned char * szOut ,
					 IN int outLen,
					 IN unsigned char * key,
					 IN int iKeyLen,
					 IN int iType)

	unsigned char ukey[EVP_MAX_KEY_LENGTH];
	unsigned char iv[EVP_MAX_IV_LENGTH];
	unsigned char in[N];
	int outl = 0;   //单次update输出数据大小
	int outl_total = 0;

	int isSuccess;

	//evp加密上下文环境
	EVP_CIPHER_CTX ctx;   
	const	EVP_CIPHER *cipher;

	//选择算法
	if(iType == 128)
	
		cipher = EVP_aes_128_ecb();
	
	else if(iType == 256)
	
		cipher = EVP_aes_256_ecb();
	
	else
	
		printf("iType should be 128 or 256.");
		return 0;
	
	//生成ukey和iv
	int len = sizeof(key);
	EVP_BytesToKey(cipher, EVP_md5(), NULL, (const unsigned char *)key, len-1/*iKeyLen*/, 1, ukey, iv);

	//初始化ctx,加密算法初始化
	EVP_CIPHER_CTX_init(&ctx);
	isSuccess = EVP_EncryptInit_ex(&ctx,cipher,NULL,ukey,iv);
	if(!isSuccess)
	
		printf("EVP_EncryptInit_ex() failed");
		EVP_CIPHER_CTX_cleanup(&ctx);

		return 0;
	

	//加密数据
	while(inLen > N)
	
		memcpy(in, szIn, N);
		inLen -= N;
		szIn += N;

		isSuccess = EVP_EncryptUpdate(&ctx, szOut + outl_total, &outl, in, N);
		if(!isSuccess)
		
			printf("EVP_EncryptUpdate() failed");
			EVP_CIPHER_CTX_cleanup(&ctx);

			return 0;
		
		outl_total += outl;
	

	if(inLen > 0)
	
		memcpy(in, szIn, inLen);
		isSuccess = EVP_EncryptUpdate(&ctx, szOut + outl_total, &outl, in, inLen);
		outl_total += outl;
		

	isSuccess = EVP_EncryptFinal_ex(&ctx,szOut + outl_total,&outl);
	if(!isSuccess)
	
		printf("EVP_EncryptFinal_ex() failed");
		EVP_CIPHER_CTX_cleanup(&ctx);

		return 0;
	
	outl_total += outl;
		
	printf("AesEncryptBuf加密成功\\n");
	EVP_CIPHER_CTX_cleanup(&ctx);
	return 1;



int AesDecryptBuf (  IN unsigned char * szIn,
					 IN int inLen,
					 IN unsigned char * szOut ,
					 IN int outLen,
					 IN unsigned char * key,
					 IN int iKeyLen,
					 IN int iType)

	unsigned char ukey[EVP_MAX_KEY_LENGTH];
	unsigned char iv[EVP_MAX_IV_LENGTH];
	unsigned char in[N];
	//int inl;   //输入数据大小
	//unsigned char out[N];
	int outl = 0;   //输出数据大小
	int outl_total = 0;
	int isSuccess;

	EVP_CIPHER_CTX ctx;   //evp加密上下文环境
	const EVP_CIPHER *cipher;

	//选择算法
	if(iType == 128)
	
		cipher = EVP_aes_128_ecb();
	
	else if(iType == 256)
	
		cipher = EVP_aes_256_ecb();
	
	else
	
		printf("iType should be 128 or 256.");
		return 0;
	
	//生成ukey和iv
	int len = sizeof(key);
	EVP_BytesToKey(cipher,EVP_md5(),NULL,(const unsigned char *)key, len-1/*iKeyLen*/, 1, ukey, iv);

	//初始化ctx,加密算法初始化
	EVP_CIPHER_CTX_init(&ctx);
	isSuccess = EVP_DecryptInit_ex(&ctx,cipher,NULL,ukey,iv);
	if(!isSuccess)
	
		printf("EVP_DecryptInit_ex() failed");
		EVP_CIPHER_CTX_cleanup(&ctx);
		return 0;
	

	//解密数据
	while(inLen > N)
	
		memcpy(in, szIn, N);
		inLen -= N;
		szIn += N;

		isSuccess = EVP_DecryptUpdate(&ctx, szOut + outl_total, &outl, in, N);
		if(!isSuccess)
		
			printf("EVP_DecryptUpdate() failed");
			EVP_CIPHER_CTX_cleanup(&ctx);
			return 0;
		
		outl_total += outl;
	

	if(inLen > 0)
	
		memcpy(in, szIn, inLen);
		isSuccess = EVP_DecryptUpdate(&ctx, szOut + outl_total, &outl, in, inLen);
		outl_total += outl;
		
	
	//如果解密数据是分组长度16的整数倍,EVP_DecryptFinal_ex会调用失败而且解密数据不正确
	//因此当解密数据为16的整数倍时,不执行EVP_DecryptFinal_ex,解密结果正确
	if(inLen % 16 != 0)
	
		isSuccess = EVP_DecryptFinal_ex(&ctx, szOut + outl_total, &outl);
		if(!isSuccess)
		
			printf("EVP_DecryptFinal_ex() failed\\n");
			EVP_CIPHER_CTX_cleanup(&ctx);
			return 0;
		
		outl_total += outl;
	
	

	printf("AesDecryptBuf解密成功\\n");
	EVP_CIPHER_CTX_cleanup(&ctx);
	return 1;


以上是关于EVP_DecryptFinal_ex调用返回失败,解密数据错误的解决方法的主要内容,如果未能解决你的问题,请参考以下文章

OpenSSL 上的 EVP_DecryptFinal_ex 错误

EVP_DecryptFinal_ex:使用 Node.js 时解密错误

如何在文件解密期间解决“EVP_DecryptFinal_ex:错误解密”

数字信封例程:坏解密

接口返回失败是啥意思

从控制器检索数据并返回 ajax 调用失败