最近有一个业务需求,需要前端传递一个密码到后端,期间要对传递的密码通过进行对称加密,我们约定使用成熟的AES加密方法。
前端使用php,后端用python,但是发现前端兄弟加密后的字符串,在python端解密后末尾总会有16字节长度的\x10
字符内容,通过python的ord('\x10')
输出可知,这就是数字16
的Unicode code。
众所周知,在使用AES进行对称加密之前,需要将加密的内容长度补全至16的倍数。如果前端兄弟无法解决加密内容中总有额外的16字节\x10
字符的问题,那么后端就要考虑多余的处理逻辑,看起来奇奇怪怪的。
于是我百度了下php的openssl_encrypt
函数,发现其中option选项有4个:
- 0
- OPENSSL_RAW_DATA=1
- OPENSSL_ZERO_PADDING=2
- OPENSSL_NO_PADDING=3
其中赫然写着OPENSSL_NO_PADDING
,字面意思很好理解了,应该就是就是不会自动追加(补全)的意思,再看前端兄弟用的是OPENSSL_RAW_DATA
。于是替换为OPENSSL_NO_PADDING
后,果然没有了\x10
的内容,问题暂时解决了。
然后我又回头想了一想,为什么OPENSSL_RAW_DATA
会自动追加一个16字节的\x10
呢,这肯定是有原因的。
因为在之前的测试中,我们在调用php的openssl_encrypt
函数之前,已经手动对加密的字符进行了补全,保证其长度是16的倍数。如果不补全会怎样?
我手动试了一下:
<?php
$str = '1234567890'
$add_data_zero_padding = openssl_encrypt($str, 'AES-128-CBC', $key, $options=OPENSSL_ZERO_PADDING, $iv);
$add_data_no_padding = openssl_encrypt($str, 'AES-128-CBC', $key, $options=OPENSSL_NO_PADDING, $iv);
$add_data_raw_data = openssl_encrypt($add_str, 'AES-128-CBC', 'eNg6geeCinee0kee', $options=OPENSSL_RAW_DATA, 'nesejeiP6du0quie');
var_dump($add_data_zero_padding);
var_dump($add_data_no_padding);
var_dump($add_data_raw_data);
echo "base64 encode:\n";
var_dump(base64_encode($add_data_raw_data));
?>
然后输出结果就是:
bool(false)
bool(false)
string(32) "�q$B�7��*���vE0�+��J.8t�[Bt�"
base64 encode:
string(44) "jHEkQrs3hBG+DiqE/4B2RTCUK6wE5r1KLjh03VtCdPs="
果然,如果没有补全,那么OPENSSL_ZERO_PADDING
和OPENSSL_NO_PADDING
会加密失败。而OPENSSL_RAW_DATA
加密的内容,解密后的字节内容是:
b'NulhIKidvmW6jaFK4T9uqJyuwrlEo\x03\x03\x03'
如此一来,其实不用去细看文档也能推理出OPENSSL_RAW_DATA
自动补全的含义了,因为补全的内容最后还需要还原为原始字符串,怎么知道哪些字符是补全上去的,哪些字符是原始字符呢?
php逻辑是这样的,我补全的长度至少是1,最长是16,代表这个长度的数字,正好都可以用一个Unicode字符表示,比如1就是\x01
,16就是\x10
。
如果加密的内容长度是15字节,那么就在最后补全一个\x01
,还原的时候,只需要读取最后一个字节内容,转换为数字,得到1
,就知道加密前只追加了1
个字节,那么就把末尾的1
个字节内容去掉即可。
如果加密的内容长度正好是16字节呢,为了还原,那么就必须要在末尾追加16
个\x10
,还原的时候读取最后一个字节并转换为数字,就知道加密时候追加了16
字节,那么把末尾的16
个字节去掉即可。
其实用python代码表示这个补全和还原的逻辑如下:
BLOCK_SIZE = 16 # 16 Bytes
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * chr(BLOCK_SIZE - len(s) % BLOCK_SIZE) # 至少会追加16字节的内容
unpad = lambda s: s[:-ord(s[len(s) - 1:])]
chr
和 ord
含义如下:
chr(i, /)
Return a Unicode string of one character with ordinal i; 0 <= i <= 0x10ffff.
ord(c, /)
Return the Unicode code point for a one-character string.