如何用JavaScript及密码解密AES-GCM加密文本?密钥生成问题解析
问题根源分析
原代码解密失败的核心原因是:generateAESKeyFromPassword函数每次调用都会生成新的随机16字节salt,但加密时这个salt没有和IV、密文一起保存。PBKDF2算法中,salt是派生密钥的必要参数,不同salt会生成完全不同的AES密钥,导致解密时用新salt派生的密钥和加密密钥不匹配,最终解密失败。
解决方案
要完成解密,必须使用加密时生成的那个salt来派生AES密钥。以下是具体步骤:
1. 修改密钥派生函数,支持传入指定salt
首先调整generateAESKeyFromPassword,让它可以接收外部传入的salt,而非每次生成随机值:
async function generateAESKeyFromPassword(password, salt) { const encoder = new TextEncoder(); const passwordKey = await crypto.subtle.importKey( 'raw', encoder.encode(password), 'PBKDF2', false, ['deriveKey'] ); return await crypto.subtle.deriveKey( { name: 'PBKDF2', salt: salt, // 使用传入的固定salt,而非随机生成 iterations: 100000, hash: 'SHA-256', }, passwordKey, { name: 'AES-GCM', length: 256, }, true, ['encrypt', 'decrypt'] ); }
2. 获取加密时的salt
如果密文是你自己生成的,需要找到加密过程中生成的那个16字节salt(比如加密时通过console.log输出并保存);如果是他人提供的密文,必须向对方索要对应的salt。
3. 使用指定salt派生密钥并解密
将获取到的salt传入修改后的函数,派生正确的AES密钥后执行解密:
// 替换为加密时实际使用的16字节salt数组 const encryptionSalt = new Uint8Array([/* 示例:1,2,3,...16个字节值 */]); const encryptedTextBase64 = "Jkf7KDJjnxwYapwGyiaVltQyIdw5whGrKSAo2qeLqYNd8aXwMeIzsFRkz4Z4yZlNfhKw+moqnST6xOuK8lR41vPAhKuv0jfWtmGwmGZGIRIDFxJSCZCElvfANw=="; const password = "mypassword"; generateAESKeyFromPassword(password, encryptionSalt) .then(key => decryptText(key, encryptedTextBase64)) .then(decryptedText => console.log("SUCCESS:", decryptedText)) .catch(error => console.error("ERROR:", error));
补充:修正加密逻辑(避免后续再出现此类问题)
如果需要加密新内容,必须把salt和IV、密文一起存储,修改后的加密函数如下:
async function encryptText(password, plaintext) { const encoder = new TextEncoder(); const data = encoder.encode(plaintext); // 生成salt并单独保存,不再在密钥派生函数内生成 const salt = crypto.getRandomValues(new Uint8Array(16)); const key = await generateAESKeyFromPassword(password, salt); const iv = crypto.getRandomValues(new Uint8Array(12)); const ciphertext = await crypto.subtle.encrypt( { name: 'AES-GCM', iv: iv }, key, data ); // 组合顺序:salt(16字节) + IV(12字节) + 密文 const combined = new Uint8Array(16 + 12 + ciphertext.byteLength); combined.set(salt); combined.set(iv, 16); combined.set(new Uint8Array(ciphertext), 28); return btoa(String.fromCharCode(...combined)); }
对应的解密函数需要先提取salt:
async function decryptText(password, combinedBase64) { const combinedArray = new Uint8Array( atob(combinedBase64).split('').map(char => char.charCodeAt(0)) ); const salt = combinedArray.slice(0, 16); const iv = combinedArray.slice(16, 28); const ciphertextArray = combinedArray.slice(28); const key = await generateAESKeyFromPassword(password, salt); const decryptedData = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: iv }, key, ciphertextArray ); return new TextDecoder().decode(decryptedData); }
内容的提问来源于stack exchange,提问作者accountnujen
相关产品推荐
相关产品推荐

