将crypto.createCipher迁移至crypto.createCipheriv并兼容历史加密数据
迁移废弃crypto.createCipher至createCipheriv并兼容历史数据
问题核心
原来的crypto.createCipher/createDecipher已被废弃,它们内部通过EVP_BytesToKey算法(默认MD5哈希、1次迭代),从输入的salt(实际是密码)生成AES-256-CBC的32字节密钥和16字节IV。新的createCipheriv要求显式传入密钥和IV,直接传null或随机IV会不兼容旧数据,必须先还原旧算法的密钥/IV生成逻辑。
解决方案代码
1. 兼容旧数据的解密函数(替代原decrypt)
模拟createCipher内部的密钥和IV生成逻辑,再用createDecipheriv解密:
const crypto = require('crypto'); // 模拟createCipher内部的EVP_BytesToKey逻辑,生成密钥和IV function generateKeyAndIV(password, algorithm = 'aes-256-cbc', hash = 'md5') { const keySize = algorithm === 'aes-256-cbc' ? 32 : 16; // AES-256用32字节密钥,AES-128用16字节 const ivSize = 16; // CBC模式IV固定16字节 let key = Buffer.alloc(0); let iv = Buffer.alloc(0); let data = Buffer.from(password, 'utf8'); let hashBuffer; while (key.length < keySize || iv.length < ivSize) { hashBuffer = crypto.createHash(hash).update(data).digest(); key = Buffer.concat([key, hashBuffer], keySize); iv = Buffer.concat([iv, hashBuffer.slice(0, ivSize - iv.length)], ivSize); data = Buffer.concat([hashBuffer, Buffer.from(password, 'utf8')]); } return { key, iv }; } // 兼容旧数据的解密函数 function decryptLegacy(text, salt = 'mydefault-password') { const { key, iv } = generateKeyAndIV(salt); const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv); try { let dec = decipher.update(text, 'hex', 'utf8'); dec += decipher.final('utf8'); return dec; } catch (err) { return false; } }
2. 新的安全加密函数(符合现代标准)
新加密使用随机IV,并将IV与密文绑定存储(IV无需保密,只需和密文关联):
// 新的加密函数 function encryptNew(text, salt = 'mydefault-password') { // 用PBKDF2从密码生成密钥(比旧EVP_BytesToKey更安全,迭代次数建议>=10000) const iv = crypto.randomBytes(16); // 生成随机IV const key = crypto.pbkdf2Sync(salt, iv, 10000, 32, 'sha256'); const cipher = crypto.createCipheriv('aes-256-cbc', key, iv); let crypted = cipher.update(text, 'utf8', 'hex'); crypted += cipher.final('hex'); // 返回IV(十六进制)+ 密文,用冒号分隔 return `${iv.toString('hex')}:${crypted}`; } // 新的解密函数(对应encryptNew) function decryptNew(text, salt = 'mydefault-password') { const [ivHex, ciphertext] = text.split(':'); if (!ivHex || !ciphertext) return false; const iv = Buffer.from(ivHex, 'hex'); const key = crypto.pbkdf2Sync(salt, iv, 10000, 32, 'sha256'); const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv); try { let dec = decipher.update(ciphertext, 'hex', 'utf8'); dec += decipher.final('utf8'); return dec; } catch (err) { return false; } }
3. 统一加解密入口(自动兼容新旧数据)
通过格式判断自动识别新旧数据,无需业务层区分:
// 统一解密函数:自动识别新旧数据 function decrypt(text, salt = 'mydefault-password') { // 新数据含冒号分隔IV与密文,旧数据无 if (text.includes(':')) { return decryptNew(text, salt); } else { return decryptLegacy(text, salt); } } // 统一加密函数:默认生成新格式数据 function encrypt(text, salt = 'mydefault-password') { return encryptNew(text, salt); }
迁移步骤
- 先部署兼容解密逻辑:替换原有
decrypt函数为上述统一decrypt或decryptLegacy,确保旧数据正常解密。 - 逐步迁移旧数据:在业务低峰时段,批量读取旧格式加密数据,解密后用
encryptNew重新加密并存储为新格式。 - 完全切换新加密:待所有旧数据迁移完成后,可移除旧解密逻辑,仅保留新的加解密函数。
关键注意事项
- 禁止修改原有
salt(密码),否则旧数据将无法解密。 - 新加密用PBKDF2生成密钥时,迭代次数建议不低于10000,哈希算法优先选择SHA256/SHA512,安全性远高于旧的MD5。
- 新数据必须绑定IV存储,IV随机生成无需保密,但必须与对应密文一一对应。
内容的提问来源于stack exchange,提问作者crankshaft
相关产品推荐
相关产品推荐

