MySQL升级到8并迁移服务器后PHP openssl_decrypt解密失败,求助解密数据库现有加密数据
首先,结合你MySQL升级+服务器迁移的背景,再加上遇到的两个解密错误,大概率是加密数据存储过程中被篡改或者密钥/环境兼容性出了问题,给你一步步排查和解决的思路:
一、先排查最可能的元凶:数据库存储的加密数据被损坏了
你的加密函数返回的是二进制数据($iv + $hmac + $ciphertext_raw都是二进制流),如果原来的EmployeeId字段用的是CHAR/VARCHAR这类字符串类型,而非BLOB/BINARY二进制类型,那么在MySQL从5.7升级到8、字符集切换到utf8mb4的过程中,二进制数据会被当作字符串进行编码转换,导致部分字节被替换成无效字符(比如�),最终读取出来的字符串和原始加密数据完全不一致。
验证&修复方法:
- 检查字段类型:先确认原数据库中
EmployeeId的字段类型,如果是字符串类型,立刻改成BLOB(或MEDIUMBLOB,根据数据长度调整)。 - 尝试恢复原始字节:如果没有备份,试试在查询时将数据转回到原来的编码(比如原来用的是
latin1):
用这个转换后的数据去解密,因为// 修改查询语句,强制用latin1编码读取EmployeeId $sql = " SELECT NetId, CONVERT(EmployeeId USING latin1) AS EmployeeId, PrimaryRole, FirstName, ... FROM Employee WHERE NetId = :field1 ";latin1是单字节编码,能完整保留原始二进制字节的信息,转成utf8mb4时被破坏的数据可能会恢复。 - 检查数据长度:在解密函数里加调试代码,确认读取到的加密数据长度是否符合预期:
如果实际长度比预期小,或者截取后的IV/HMAC长度不对,那肯定是数据被截断或篡改了。public function decrypt($encrypted_string) { $cipher = 'AES-256-CBC'; $ivlen = openssl_cipher_iv_length($cipher); $sha2len = 32; // 输出调试信息到错误日志 error_log("读取到的加密数据长度:" . strlen($encrypted_string)); error_log("预期最小长度(IV+HMAC):" . ($ivlen + $sha2len)); error_log("实际截取的IV长度:" . strlen(substr($encrypted_string, 0, $ivlen))); error_log("实际截取的HMAC长度:" . strlen(substr($encrypted_string, $ivlen, $sha2len))); // 原解密代码... }
二、检查密钥长度的兼容性问题
你用的是AES-256-CBC,它要求密钥必须是32字节(256位),新服务器的PHP/OpenSSL版本可能对密钥长度的检查更严格:旧环境可能会自动截断/补全不符合长度的密钥,而新环境直接抛出invalid key length错误。
验证&修复方法:
- 确认密钥实际字节长度:在加密和解密函数里,先输出密钥的字节数:
如果不是32字节,要么调整密钥到32字节,要么用哈希函数把密钥转换成标准的32字节(推荐这种,兼容性更好):error_log("密钥字节长度:" . strlen($this->encryption_key));
注意:这个修改要同时应用到加密和解密函数,确保新旧数据的密钥处理逻辑一致。// 在加密和解密时,统一用sha256哈希生成32字节的密钥 $aes_key = hash('sha256', $this->encryption_key, true); // 然后把$aes_key传给openssl_encrypt/openssl_decrypt $ciphertext_raw = openssl_encrypt($pure_string, $cipher, $aes_key, $options, $iv); // 解密时同理 $original_plaintext = openssl_decrypt($ciphertext_raw, $cipher, $aes_key, $options, $iv);
三、检查PHP/OpenSSL版本差异
新服务器的PHP版本、OpenSSL版本可能和旧服务器不同,比如某些旧版本的OpenSSL允许非标准密钥长度,新版本则严格遵循规范。你可以在新旧服务器上分别执行phpinfo(),对比OpenSSL扩展的版本和配置。
如果是版本差异导致的,除了调整密钥长度,还可以尝试降级/升级OpenSSL版本(不过更推荐调整密钥逻辑,毕竟版本升级是趋势)。
四、临时修复:尝试恢复损坏的数据
如果数据已经被编码破坏,还可以试试用字符编码转换工具把读取到的字符串转成原始二进制:
// 把UTF-8编码的字符串转成latin1,恢复原始字节 $fixed_encrypted_string = mb_convert_encoding($encrypted_string, 'ISO-8859-1', 'UTF-8'); // 用转换后的数据解密 $EmployeeId = $OpensslEncryption->decrypt($fixed_encrypted_string);
最后提醒你:以后存储加密数据一定要用二进制字段(BLOB/BINARY),避免字符集转换带来的数据损坏;另外,加密函数最好返回十六进制字符串(就是你注释掉的bin2hex),这样存储在字符串字段里也不会有问题,兼容性更强。
备注:内容来源于stack exchange,提问作者ddavidd

