Cordova Vue加密钱包助记词Salt安全实现代码审计请求
现有实现的核心问题
1. 硬编码全局盐严重削弱安全性
当前代码中const salt是固定硬编码值,所有用户共用同一个盐。盐的核心作用是防止彩虹表攻击,固定盐会让攻击者针对性生成彩虹表,一旦泄露,所有用户的加密数据都面临被破解风险,完全失去盐的安全价值。
2. 加密函数参数错误,密码未实际参与加密
createKey中调用encryptMnemonic(seedPhrase, pincode, password),但encryptMnemonic仅定义了两个参数,第三个参数password被直接忽略,实际加密仅使用pincode + salt作为密钥。这意味着用户设置的密码完全没起到加密作用,PIN码本身通常是短数字,极易被暴力破解。
3. 未使用密钥派生函数(KDF),密钥强度不足
直接将PIN/密码与盐拼接作为AES密钥,没有经过PBKDF2、Argon2等专业KDF处理。这类函数通过多次迭代计算,能大幅提升暴力破解成本,尤其是针对PIN这类短输入,KDF是必不可少的安全环节。
4. AES加密未使用随机IV,存在重复密文风险
CryptoJS的AES.encrypt默认使用CBC模式,但如果不指定随机IV,相同的明文+密钥会生成完全相同的密文。攻击者可通过密文重复推断明文信息,甚至实施重放攻击。
5. 明文返回助记词,加密完全失效
createKey的返回值中包含seed: seedPhrase,直接暴露明文助记词。无论加密逻辑多么完善,只要明文助记词泄露,钱包安全性就彻底丧失。
6. 以太坊地址未做校验和处理
当前生成的以太坊地址是纯小写格式,不符合以太坊校验和规范,用户后续使用中容易因输入错误导致资产损失。
针对性优化方案
1. 为每个用户生成独立随机盐
删除硬编码全局盐,创建钱包时生成16字节以上的随机盐,并将盐与加密后的助记词一起存储(盐无需保密,只需唯一)。示例:
// 生成随机盐 const generateSalt = () => CryptoJS.lib.WordArray.random(128/8).toString();
2. 修复加密逻辑,让PIN+密码共同参与加密
修改encryptMnemonic函数,接收PIN和密码,组合后通过KDF生成密钥:
function encryptMnemonic(mnemonic, pincode, password, salt) { // 组合PIN与密码,避免单一因素泄露风险 const combinedInput = `${pincode}:${password}`; // 使用PBKDF2派生密钥,迭代次数建议10万次以上 const key = CryptoJS.PBKDF2(combinedInput, salt, { keySize: 256/32, iterations: 100000, hasher: CryptoJS.algo.SHA256 }); // 生成随机IV const iv = CryptoJS.lib.WordArray.random(128/8); // 执行AES加密 const encrypted = CryptoJS.AES.encrypt(mnemonic, key, { iv: iv }); // 返回密文、IV、盐(三者缺一不可解密) return { ciphertext: encrypted.ciphertext.toString(), iv: iv.toString(), salt: salt }; }
3. 完善解密逻辑并验证结果
解密时恢复明文后,需验证助记词有效性,避免无效数据导致后续错误:
function decryptMnemonic(ciphertext, iv, salt, pincode, password) { const combinedInput = `${pincode}:${password}`; const key = CryptoJS.PBKDF2(combinedInput, salt, { keySize: 256/32, iterations: 100000, hasher: CryptoJS.algo.SHA256 }); const decryptedBytes = CryptoJS.AES.decrypt( { ciphertext: CryptoJS.enc.Hex.parse(ciphertext) }, key, { iv: CryptoJS.enc.Hex.parse(iv) } ); const mnemonic = decryptedBytes.toString(CryptoJS.enc.Utf8); // 验证助记词有效性 if (!bip39.validateMnemonic(mnemonic)) { throw new Error("解密失败,助记词无效"); } return mnemonic; }
4. 删除明文助记词的返回
修改createKey的返回值,移除seed: seedPhrase,绝对不能在任何环节暴露明文助记词:
return { success: true, key: encrypted, // 存储包含密文、IV、盐的对象 btc: address, eth: ethAddress }
5. 优化以太坊地址生成
将生成的以太坊地址转换为校验和格式:
const ethAddress = ethUtil.toChecksumAddress(hexlify(tvt));
6. 升级依赖库版本
当前使用的ethers v5.5.4版本较旧,存在潜在安全漏洞,建议升级到最新稳定版(如v6.x),同时确保bip39、bitcoinjs-lib等其他依赖为最新安全版本。
7. 离线存储的适配
将加密后的助记词、IV、盐一起存储在本地(如localStorage或IndexedDB),确保离线状态下可正常读取并解密,无需依赖后端服务。
内容的提问来源于stack exchange,提问作者Eugeny

