PHP加密存SQL数据库后Node.js解密的最优算法及适配问题
跨PHP和Node.js的加密解密方案:解决你遇到的openssl_encrypt与aes-js不兼容问题
嘿,你遇到的情况太常见了——不是加密算法选得不对,而是PHP的openssl_encrypt和Node.js的aes-js在参数配置上没对齐(比如加密模式、填充方式、密钥/IV的处理、输出编码这些细节)。下面给你推荐两种可靠的方案,附完整的可运行代码,直接抄就能用:
优先选:AES-256-GCM(带认证的加密,更安全)
GCM模式自带数据完整性校验,能防止数据被篡改,而且不需要额外处理填充问题,跨语言适配起来最省心。
PHP 加密代码(存到varchar字段)
<?php // 注意:密钥必须是32字节(256位),建议随机生成后存在配置文件里,别硬写! $secretKey = 'your-32-byte-secret-key-1234567890abcdef'; // GCM模式推荐用12字节的随机IV,安全又高效 $iv = random_bytes(12); $rawData = '要加密的敏感内容,比如用户手机号、邮箱'; // 加密:用OPENSSL_RAW_DATA输出二进制,之后转base64方便存在varchar $encryptedData = openssl_encrypt($rawData, 'aes-256-gcm', $secretKey, OPENSSL_RAW_DATA, $iv, $authTag); // 把IV、认证标签、加密数据拼在一起,转base64后存储——这三个都是解密必需的 $storedValue = base64_encode($iv . $authTag . $encryptedData); // 现在$storedValue就是可以直接存到varchar字段的文本啦 echo "存入数据库的值:" . $storedValue; ?>
Node.js 解密代码
const aesjs = require('aes-js'); // 和PHP端完全一致的32字节密钥 const secretKey = aesjs.utils.utf8.toBytes('your-32-byte-secret-key-1234567890abcdef'); // 从数据库取出的base64字符串 const storedValue = '这里替换成从数据库拿到的加密值'; // 解码base64,拆分出IV、认证标签、加密数据 const decodedBuffer = aesjs.utils.hex.toBytes(aesjs.utils.base64.toHex(storedValue)); const iv = decodedBuffer.slice(0, 12); // 前12字节是IV const authTag = decodedBuffer.slice(12, 28); // 接下来16字节是GCM的认证标签 const encryptedBytes = decodedBuffer.slice(28); // 剩下的是加密后的内容 // 初始化GCM解密器,解密同时验证标签(防止数据被篡改) const gcmMode = new aesjs.ModeOfOperation.gcm(secretKey, iv); const decryptedBytes = gcmMode.decrypt(encryptedBytes, authTag); // 转成可读的字符串 const decryptedText = aesjs.utils.utf8.fromBytes(decryptedBytes); console.log("解密后的内容:", decryptedText);
备选:AES-256-CBC(兼容性更广,适合老环境)
如果你的服务器环境不支持GCM模式,可以用CBC,但要注意填充方式的一致性(PHP默认用PKCS7填充,Node.js的aes-js需要手动处理去填充)。
PHP 加密代码
<?php $secretKey = 'your-32-byte-secret-key-1234567890abcdef'; // CBC模式需要16字节的随机IV $iv = random_bytes(16); $rawData = '要加密的敏感内容'; // 加密:默认PKCS7填充,输出二进制后转base64 $encryptedData = openssl_encrypt($rawData, 'aes-256-cbc', $secretKey, OPENSSL_RAW_DATA, $iv); // 拼接IV和加密数据,转base64存储 $storedValue = base64_encode($iv . $encryptedData); echo "存入数据库的值:" . $storedValue; ?>
Node.js 解密代码
const aesjs = require('aes-js'); const secretKey = aesjs.utils.utf8.toBytes('your-32-byte-secret-key-1234567890abcdef'); const storedValue = '从数据库拿到的加密值'; // 解码并拆分IV和加密数据 const decodedBuffer = aesjs.utils.hex.toBytes(aesjs.utils.base64.toHex(storedValue)); const iv = decodedBuffer.slice(0, 16); const encryptedBytes = decodedBuffer.slice(16); // 初始化CBC解密器 const cbcMode = new aesjs.ModeOfOperation.cbc(secretKey, iv); // 解密后的数据带PKCS7填充,需要手动去除 const decryptedWithPadding = cbcMode.decrypt(encryptedBytes); // 实现PKCS7去填充 function removePkcs7Padding(data) { const paddingLength = data[data.length - 1]; return data.slice(0, data.length - paddingLength); } const decryptedBytes = removePkcs7Padding(decryptedWithPadding); const decryptedText = aesjs.utils.utf8.fromBytes(decryptedBytes); console.log("解密后的内容:", decryptedText);
必须注意的几个关键点
- 密钥要一致且安全:两边必须用完全相同的密钥,密钥长度要和算法匹配(AES-256要32字节,AES-128要16字节),别把密钥硬写在代码里,存到环境变量或配置文件里。
- IV必须随机且一起存储:每次加密都要生成随机IV,不能固定IV,IV不需要保密,但必须和加密时的一致才能解密,所以要和加密数据一起存在数据库里。
- 二进制转文本存储:加密后的二进制数据一定要转成base64或hex格式再存到varchar字段,不然会出现乱码或数据丢失。
- 填充方式对齐:CBC模式下PHP默认用PKCS7填充,Node.js的aes-js不会自动处理,所以要手动实现去填充逻辑,这也是很多人踩坑的地方。
内容的提问来源于stack exchange,提问作者Debashish Kumar
相关产品推荐
相关产品推荐

