PHP中特定长度输入的AES-192加解密失败问题求助
PHP自定义AES-192加解密异常问题排查与解决
我在PHP中使用自定义AES实现:
$Cipher = new AESCipher(AES::AES192);
问题
两个相似输入表现不一致:
CvwCvw-2-2@{}#%~[]=,-_!(23字节):加解密完全正常CTPView-2-2@{}#%~[]=,-_!(24字节):加密成功,但解密失败
现象观察
AES采用16字节块加密,剩余字节会填充补全为完整块:
- 23字节输入:填充9字节以凑齐32字节(2个16字节块)
- 24字节输入:填充8字节以凑齐32字节
仅填充量不同,但结果却出现明显差异
已尝试的无效操作
- 切换加密算法为AES-256
- 使用base64_encode/decode处理密文
- 使用rawurlencode/rawurldecode处理密文
- 修剪输入内容、移除隐藏字符
- 验证加密密码在INI文件中的存储与读取逻辑
相关代码
加密代码
<?php include_once('/var/www/.scripts/AES.class.php'); include_once('/var/www/.scripts/AESCipher.class.php'); $password = "___getting from user____"; $Cipher = new AESCipher(AES::AES192); $cryptext = @$Cipher->encrypt($password, $username); $cryptext = rawurlencode($cryptext); print $cryptext;
解密代码
<?php include_once('/var/www/.scripts/AES.class.php'); include_once('/var/www/.scripts/AESCipher.class.php'); $Cipher = new AESCipher(AES::AES192); $ini_array = parse_ini_file("example.ini"); $password = $ini_array['password']; $username = $ini_array['username']; $cryptext = rawurldecode($password); $decrypt_pass = @$Cipher->decrypt($cryptext, "db_name"); print $decrypt_pass; $this->connection = pg_connect("user=$username password=$decrypt_pass dbname=db_name") or die("Could not connect to postgresql");
问题根源与解决建议
1. 修复填充逻辑(核心问题)
问题大概率出在自定义AES实现的填充/去填充逻辑上。标准PKCS#7填充规则明确:如果输入长度刚好是块大小的整数倍,需要额外填充一个完整块的填充字节(而非与剩余长度相等的字节数)。
比如24字节输入(刚好是1.5个16字节块),按标准应该填充16字节的0x10,而非8字节的0x08。如果你的实现未遵循此规则,解密时会错误地截断原始输入的最后8字节,导致解密失败。
替换成标准PKCS#7填充/去填充实现:
// 填充函数 function pkcs7_pad($data, $block_size) { $pad_length = $block_size - (strlen($data) % $block_size); // 若输入已是块大小整数倍,填充一个完整块 if ($pad_length == 0) { $pad_length = $block_size; } return $data . str_repeat(chr($pad_length), $pad_length); } // 去填充函数 function pkcs7_unpad($data) { $pad_length = ord(substr($data, -1)); // 校验填充长度合法性 if ($pad_length < 1 || $pad_length > 16) { return false; } return substr($data, 0, -$pad_length); }
2. 修正加密解密的参数一致性
注意到加密时第二个参数是$username,但解密时硬编码为"db_name":
- 加密:
$Cipher->encrypt($password, $username); - 解密:
$Cipher->decrypt($cryptext, "db_name");
如果该参数是密钥推导的一部分(盐或密钥生成因子),参数不一致会导致加密解密密钥完全不同,必然解密失败。修改解密代码,使用读取到的$username作为参数:
$decrypt_pass = $Cipher->decrypt($cryptext, $username);
3. 移除错误抑制符@
代码中的@会屏蔽所有错误信息,导致无法定位具体问题。去掉@并开启PHP错误提示,能直接看到填充错误、密钥不匹配等具体异常。
4. 验证密文完整性
为避免密文在存储/读取过程中被篡改,可在加密后生成密文的哈希值,与密文一起存储,解密前先校验哈希:
// 加密时生成哈希 $hash = hash('sha256', $cryptext); // 将$cryptext和$hash一同写入INI文件 // 解密时校验 $stored_hash = $ini_array['password_hash']; if (hash('sha256', $cryptext) !== $stored_hash) { die("密文已被篡改"); }
内容的提问来源于stack exchange,提问作者Balaji Sekar
相关产品推荐
相关产品推荐

