You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 23:09:53