0x01 问题复现
最近基于 opessl 的 C 语言库做 AES 加解密的实现。在测试的过程中,发现概率性(一半概率)的会出现用加密生成的字符串,解密后的内容只剩一半的问题,具体的测试用的字符串如下:
加密用的key: IWAcrPFhmrBD19TrSfb7L3Oc0TRUOV6p
加密用的IV: c9c47d64e1b036f540a38255ba88af48
加密用的字符串:IWAcrPFhmrBD19TrSfb7L3Oc0TRUOV6p
复制代码
正常调用 AES128 加密,结果用 Base64 转换成字符串输出,得到的加密后的字符串为:ER04NG0hqYv+qyySaxLbNaVMZxBLBnxMIwfNAF/rBV9uDChhlx3CKW758nzNHmBB 具体的加密转换过程输出为:
plain len:32,value:
.......................................
49 57 41 63 72 50 46 68
6D 72 42 44 31 39 54 72
53 66 62 37 4C 33 4F 63
30 54 52 55 4F 56 36 70
aes len:48,value:
.......................................
11 1D 38 34 6D 21 A9 8B
FE AB 2C 92 6B 12 DB 35
A5 4C 67 10 4B 06 7C 4C
23 07 CD 00 5F EB 05 5F
6E 0C 28 61 97 1D C2 29
6E F9 F2 7C CD 1E 60 41
base len:64,value:
.......................................
45 52 30 34 4E 47 30 68
71 59 76 2B 71 79 79 53
61 78 4C 62 4E 61 56 4D
5A 78 42 4C 42 6E 78 4D
49 77 66 4E 41 46 2F 72
42 56 39 75 44 43 68 68
6C 78 33 43 4B 57 37 35
38 6E 7A 4E 48 6D 42 42
复制代码
可以看到,数据从一个 32 位的数组,AES 加密后变成 48 位,最终基于 base64 转换为一个 64 位的数组。
然后神奇的事情发生了,我们用上面得到的加密后的字符串,进行解密,可以得到下面的结果:
src len:64,value:
.......................................
45 52 30 34 4E 47 30 68
71 59 76 2B 71 79 79 53
61 78 4C 62 4E 61 56 4D
5A 78 42 4C 42 6E 78 4D
49 77 66 4E 41 46 2F 72
42 56 39 75 44 43 68 68
6C 78 33 43 4B 57 37 35
38 6E 7A 4E 48 6D 42 42
aes len:27,value:
.......................................
11 1D 38 34 6D 21 A9 8B
FE AB 2C 92 6B 12 DB 35
A5 4C 67 10 4B 06 7C 4C
23 07 CD
plain len:16,value:
.......................................
49 57 41 63 72 50 46 68
6D 72 42 44 31 39 54 72
复制代码
从中间的转换过程来看,base64 的输入是正常的,还是 64 位,但是到了 ase 解密完了以后,就不对了,变成了只剩下了 27 位的数组了,最终输出结果变成了 16 位数组,对比原始数据,数据减少了一半。
这种情况也不是每次都能出现,比如将刚才输入的内容: IWAcrPFhmrBD19TrSfb7L3Oc0TRUOV6p 的最后一位,改成 s,也就是 IWAcrPFhmrBD19TrSfb7L3Oc0TRUOV6s 计算结果又是正常了。
0x02 问题分析
本人对于 AES 算法本身不是非常了解,不过所有的对称加密算法,本质上面就是一组数矩阵基于某种运算规则进行各种变换。结合问题的现象来看,最终输出的结果其实是解密成功了,只是少了一半数据。我估计可能的原因是在转换的过程中,出现了类似溢出的问题,导致最终结果缺失数据。这可能是 openssl 本身的代码实现可能有问题导致的。为了验证是不是我本身代码的问题,我用编译好的 openssl 程序进行加解密测试了一波:
加密:
echo "IWAcrPFhmrBD19TrSfb7L3Oc0TRUOV6p" > name.txt
openssl enc -aes-128-cbc -in name.txt -a -out enc_name.txt -K IWAcrPFhmrBD19TrSfb7L3Oc0TRUOV6p -iv c9c47d64e1b036f540a38255ba88af48
复制代码
输出的结果保存到 enc_name.txt 文件中,内容如下:
ER04NG0hqYv+qyySaxLbNaVMZxBLBnxMIwfNAF/rBV96qBLPB4x4fTViwzW47KpF
复制代码
解密:
openssl enc -d -aes-128-cbc -in enc_name.txt -a -out dec_name.txt -K IWAcrPFhmrBD19TrSfb7L3Oc0TRUOV6p -iv c9c47d64e1b036f540a38255ba88af48
cat dec_name.txt
复制代码
输出结果如下:
IWAcrPFhmrBD19TrSfb7L3Oc0TRUOV6p
复制代码
可以看到,基于 openssl 程序生成是可以正常解密回来的,说明应该不是算法和参数本身的问题,问题应该是我自己的代码实现这里。
回到上面的现象,其实问题是比较清楚了,就是 base64 解密的时候,数组的长度变了,少了部分内容应该是 48 位)。看 base64 解密的实现:
int openssl_base64_decrypt(
const char *ciphertext, unsigned int ciphertext_len, unsigned char *plaintext)
{
BIO *b64 = NULL;
BIO *bmem = NULL;
b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
bmem = BIO_new_mem_buf(ciphertext, ciphertext_len);
bmem = BIO_push(b64, bmem);
BIO_read(bmem, plaintext, ciphertext_len);
BIO_free_all(bmem);
return strlen(plaintext);
}
复制代码
问题出现在最后一行。如果转换后的结果数组里面,中间有零的话,其实获取到的不是最终的结果长度。为了验证假设,我这边打印了下完整数组的内容:
aes len:27,value:
.......................................
11 1D 38 34 6D 21 A9 8B
FE AB 2C 92 6B 12 DB 35
A5 4C 67 10 4B 06 7C 4C
23 07 CD 00 5F EB 05 5F
6E 0C 28 61 97 1D C2 29
6E F9 F2 7C CD 1E 60 41
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
复制代码
Bingo! 果然是有零。位置为第 28 位,所以得出数组的长度是 27 这个结论,最终导致后面的 AES 解密缺失数据。至此,问题定位完成。
0x03 问题解决
修改 base64 解密代码,获取准确的数组长度,代码如下:
int openssl_base64_decrypt(
const char *ciphertext, unsigned int ciphertext_len, unsigned char *plaintext)
{
BIO *b64 = NULL;
BIO *bmem = NULL;
BUF_MEM *bptr;
b64 = BIO_new(BIO_f_base64());
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
bmem = BIO_new_mem_buf(ciphertext, ciphertext_len);
bmem = BIO_push(b64, bmem);
int len = BIO_read(bmem, plaintext, ciphertext_len);
BIO_free_all(bmem);
return len;
}
复制代码
再测试,可以正常解密。至此,问题完全解决。
评论