PHP openssl_decrypt遇无效密钥未返回false的密钥轮换问题
加密密钥轮换时openssl_decrypt()错误密钥不返回false的问题及解决方法
问题原因
openssl_decrypt()仅在解密过程出现底层错误(如IV长度不匹配、加密算法不支持、内存分配失败等)时才会返回false。而用错误密钥解密AES密文属于合法操作:AES会用错误密钥对密文执行逆运算,输出一段随机乱码字符串——只要这段乱码不是空字符串,!$decryptedValue的判断就会失效,导致无法触发旧密钥的回退逻辑。
加上你使用了固定IV,小概率下错误密钥解密后的乱码刚好是非空值,就会出现你遇到的 fallback 失效情况。
解决方案
要实现可靠的密钥轮换,核心是要能验证解密结果的有效性,而不是依赖openssl_decrypt()的返回值是否为false。以下是两种可行方案:
方案1:使用带内置认证的加密模式
改用支持**消息认证码(MAC)**的AES模式,比如aes-128-gcm或aes-256-gcm。这类模式在加密时会生成一个认证标签(tag),解密时必须验证标签的有效性——如果密钥错误,标签验证失败,openssl_decrypt()会直接返回false。
示例代码:
// 新旧密钥 define('KEY_1', 'v3pQz7gtWaGF8N62EFK3VUv0m233ATde3c77z6oC5dk='); define('KEY_2', 'YDqviTmcH1iQJ1KdZXnOD8JILeqOV18t4T2t029Zp3s='); $testString = '1996'; // 加密(用KEY_1) $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-128-gcm')); $tag = ''; $encrypted = openssl_encrypt( $testString, 'aes-128-gcm', base64_decode(KEY_1), OPENSSL_RAW_DATA, $iv, $tag ); // 存储时需保存密文、IV、tag $storedData = base64_encode($encrypted) . '|' . base64_encode($iv) . '|' . base64_encode($tag); // 解密流程(先试KEY_2,失败则用KEY_1) list($encryptedB64, $ivB64, $tagB64) = explode('|', $storedData); $encrypted = base64_decode($encryptedB64); $iv = base64_decode($ivB64); $tag = base64_decode($tagB64); $decrypted = openssl_decrypt( $encrypted, 'aes-128-gcm', base64_decode(KEY_2), OPENSSL_RAW_DATA, $iv, $tag ); if (!$decrypted) { $decrypted = openssl_decrypt( $encrypted, 'aes-128-gcm', base64_decode(KEY_1), OPENSSL_RAW_DATA, $iv, $tag ); } echo $decrypted; // 正确输出'1996'
方案2:手动添加HMAC验证(兼容现有加密数据)
如果无法修改现有加密算法,可在加密时对密文+IV生成HMAC,存储密文时一并保存HMAC值。解密时先验证HMAC:只有HMAC匹配,才认为解密结果有效;否则直接尝试旧密钥。
示例代码:
// 新旧密钥 define('KEY_1', 'v3pQz7gtWaGF8N62EFK3VUv0m233ATde3c77z6oC5dk='); define('KEY_2', 'YDqviTmcH1iQJ1KdZXnOD8JILeqOV18t4T2t029Zp3s='); // 额外的HMAC密钥(可单独生成,也可用加密密钥的衍生值) define('HMAC_KEY', 'your-hmac-secret-key'); $testString = '1996'; $iv = base64_decode('kMg6JRRCq/jPcIQWzE+44Q=='); // 加密(用KEY_1) $encrypted = openssl_encrypt( $testString, 'aes128', base64_decode(KEY_1), OPENSSL_RAW_DATA, $iv ); // 生成HMAC:对密文+IV进行签名 $hmac = hash_hmac('sha256', $encrypted . $iv, HMAC_KEY, true); // 存储时保存密文、IV、HMAC $storedData = base64_encode($encrypted) . '|' . base64_encode($iv) . '|' . base64_encode($hmac); // 解密流程 list($encryptedB64, $ivB64, $hmacB64) = explode('|', $storedData); $encrypted = base64_decode($encryptedB64); $iv = base64_decode($ivB64); $expectedHmac = base64_decode($hmacB64); // 先尝试用KEY_2解密 $decrypted = openssl_decrypt( $encrypted, 'aes128', base64_decode(KEY_2), OPENSSL_RAW_DATA, $iv ); // 验证HMAC:只有解密后的结果对应的HMAC匹配,才认为有效 $calculatedHmac = hash_hmac('sha256', $encrypted . $iv, HMAC_KEY, true); if (!hash_equals($calculatedHmac, $expectedHmac)) { // HMAC不匹配,用KEY_1重试 $decrypted = openssl_decrypt( $encrypted, 'aes128', base64_decode(KEY_1), OPENSSL_RAW_DATA, $iv ); } echo $decrypted; // 正确输出'1996'
额外建议:不要使用固定IV
固定IV在AES-CBC等模式下是严重的安全漏洞:相同明文+相同IV+相同密钥会生成完全相同的密文,攻击者可通过密文重复推断明文内容。正确的做法是每次加密生成独立的IV,并将IV与密文一起存储(IV无需保密)。
内容的提问来源于stack exchange,提问作者Michael David
相关产品推荐
相关产品推荐

