OpenSSL EVP API加解密异常:长串解密报错、关填充致空输出/段错误
异常根因定位
所有报错本质上是do_crypt函数对OpenSSL EVP接口的参数约定、返回值处理存在系统性偏差,具体拆解如下:
为什么长字符串加密后解密会触发bad decrypt错误
- 短字符串能正常加解密完全是巧合:长度11的"Hello world"小于AES-128的16字节块长,块模式下
EVP_CipherUpdate需要攒够完整块才会输出密文,因此这一步不会产生任何输出,回传的长度值为0。后续调用EVP_CipherFinal_ex时,哪怕代码没有做缓冲区位置偏移,也会因为初始输出位置在缓冲区起始处,刚好把带填充的完整块密文写到正确位置,流程能跑通。 - 长字符串就会暴露核心代码bug:以长度24的"Hello world! How are you?"为例,输入长度超过16字节块长,
EVP_CipherUpdate会先处理第一个完整块,输出16字节密文到缓冲区,同时通过outlen参数回传这16字节的长度。 - 两个致命错误直接破坏密文完整性:
- 原代码调用
EVP_CipherFinal_ex时,传入的输出指针仍然是缓冲区的起始地址outbuf.data(),没有偏移到Update已经写完的位置(即outbuf.data() + outlen),导致Final阶段生成的带PKCS#7填充的最后一块密文,直接覆盖了Update阶段已经写好的第一块密文。 EVP_CipherFinal_ex回传的outlen值是Final阶段自身生成的密文长度,完全覆盖了Update阶段的输出长度,最终加密返回的总密文长度、内容都完全错乱。
- 原代码调用
- 用这串损坏的密文解密时,最后一块的填充值根本不符合PKCS#7的格式校验规则,自然会在Final阶段报
bad decrypt错误。
为什么关闭填充后会出现空输出、甚至段错误
- 空输出的本质是
outlen值被Final接口直接覆盖:- 测试用的"Some Crypto Text"刚好为16字节,关闭填充后不需要做块补位,
EVP_CipherUpdate会直接输出完整的16字节密文,回传outlen=16。 - 后续调用
EVP_CipherFinal_ex时,因为没有填充需要处理,Final阶段不会输出任何字节,会直接把传入的outlen变量值覆盖为0。 - 函数返回时调用方拿到的
outlen为0,构造字符串时从缓冲区起始位置取0长度的内容,自然输出为空。
- 测试用的"Some Crypto Text"刚好为16字节,关闭填充后不需要做块补位,
- 段错误的本质是错误分支的越界访问:
- 在原16字节测试字符串基础上新增1个字符后,总长度为17字节,不是AES块长的整数倍,关闭填充模式时EVP接口会直接返回错误,打印"data not multiple of block length"的原因提示。
- 原代码在错误分支直接返回空的
std::vector<unsigned char>,但作为引用参数传入的outlen值还停留在之前Update阶段写入的16字节。 - 调用方拿到空vector后,直接尝试访问
out.begin() + outlen,也就是对长度为0的vector做16字节的偏移访问,完全越界,直接触发段错误。
- 额外的叠加影响:原代码将加密后的二进制密文直接转为std::string跨函数传递,虽然std::string本身支持存储二进制空字符,但密文中的不可打印字节在控制台输出时本来就无法正常显示,和代码bug叠加后进一步放大了异常的表现。
内容的提问来源于stack exchange,提问作者TomateVerde
相关产品推荐
相关产品推荐

