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

JavaScript中能否实现双密钥加密:任一密钥均可解密同一密文?

在JavaScript中实现双密钥任意解密的加密方案

当然可以实现这种1-out-of-2双密钥加密方案——即两个密钥中的任意一个都能单独解密密文。下面是两种实用的实现思路,结合浏览器原生Web Crypto API(安全性更高,无需第三方库)给出代码示例:

方案一:对称加密+双公钥加密会话密钥

这是最常用且安全的方案,核心逻辑是:用随机生成的对称密钥加密明文,再用两个独立的非对称公钥分别加密这个对称密钥。解密时,用任意一个对应的私钥解密出对称密钥,即可解密密文。

代码实现

// 生成AES-GCM对称密钥并加密明文
async function encryptWithSessionKey(plaintext) {
  const encoder = new TextEncoder();
  const data = encoder.encode(plaintext);
  
  // 生成256位AES-GCM密钥(支持加密和解密)
  const aesKey = await window.crypto.subtle.generateKey(
    { name: "AES-GCM", length: 256 },
    true,
    ["encrypt", "decrypt"]
  );
  
  // 生成12字节随机IV(AES-GCM推荐长度)
  const iv = window.crypto.getRandomValues(new Uint8Array(12));
  
  // 加密明文
  const ciphertext = await window.crypto.subtle.encrypt(
    { name: "AES-GCM", iv: iv },
    aesKey,
    data
  );
  
  // 导出AES密钥为原始二进制数据,用于后续非对称加密
  const exportedAesKey = await window.crypto.subtle.exportKey("raw", aesKey);
  
  return { ciphertext, iv, exportedAesKey };
}

// 生成RSA-OAEP密钥对(用于加密/解密会话密钥)
async function generateRSAKeyPair() {
  return window.crypto.subtle.generateKey(
    {
      name: "RSA-OAEP",
      modulusLength: 2048,
      publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
      hash: "SHA-256",
    },
    true,
    ["encrypt", "decrypt"]
  );
}

// 创建支持双密钥解密的加密包
async function createDualKeyEncryptedPackage(plaintext) {
  // 生成会话密钥和明文密文
  const { ciphertext, iv, exportedAesKey } = await encryptWithSessionKey(plaintext);
  
  // 生成两个独立的RSA密钥对
  const keyPair1 = await generateRSAKeyPair();
  const keyPair2 = await generateRSAKeyPair();
  
  // 用两个公钥分别加密会话密钥
  const encryptedKey1 = await window.crypto.subtle.encrypt(
    { name: "RSA-OAEP" },
    keyPair1.publicKey,
    exportedAesKey
  );
  const encryptedKey2 = await window.crypto.subtle.encrypt(
    { name: "RSA-OAEP" },
    keyPair2.publicKey,
    exportedAesKey
  );
  
  // 将二进制数据转为Base64,方便存储/传输
  return {
    ciphertext: btoa(String.fromCharCode(...new Uint8Array(ciphertext))),
    iv: btoa(String.fromCharCode(...new Uint8Array(iv))),
    encryptedKey1: btoa(String.fromCharCode(...new Uint8Array(encryptedKey1))),
    encryptedKey2: btoa(String.fromCharCode(...new Uint8Array(encryptedKey2))),
    // 注意:私钥需安全存储(如浏览器IndexedDB+加密,或系统密钥链)
    privateKey1: keyPair1.privateKey,
    privateKey2: keyPair2.privateKey
  };
}

// 使用任意一个私钥解密
async function decryptWithEitherKey(encryptedPackage, privateKey) {
  const decoder = new TextDecoder();
  
  // 将Base64转回二进制数据
  const ciphertext = Uint8Array.from(atob(encryptedPackage.ciphertext), c => c.charCodeAt(0));
  const iv = Uint8Array.from(atob(encryptedPackage.iv), c => c.charCodeAt(0));
  
  // 匹配对应私钥的加密会话密钥
  const targetEncryptedKey = privateKey === encryptedPackage.privateKey1 
    ? Uint8Array.from(atob(encryptedPackage.encryptedKey1), c => c.charCodeAt(0))
    : Uint8Array.from(atob(encryptedPackage.encryptedKey2), c => c.charCodeAt(0));
  
  // 解密会话密钥
  const exportedAesKey = await window.crypto.subtle.decrypt(
    { name: "RSA-OAEP" },
    privateKey,
    targetEncryptedKey
  );
  
  // 导入AES密钥
  const aesKey = await window.crypto.subtle.importKey(
    "raw",
    exportedAesKey,
    { name: "AES-GCM", length: 256 },
    true,
    ["decrypt"]
  );
  
  // 解密密文得到明文
  const decryptedData = await window.crypto.subtle.decrypt(
    { name: "AES-GCM", iv: iv },
    aesKey,
    ciphertext
  );
  
  return decoder.decode(decryptedData);
}

方案二:基于ECC的密钥拆分(进阶)

如果你想更精简的实现,可以用椭圆曲线加密(ECC)的特性:生成一个主私钥,派生出两个子私钥,每个子私钥都能独立解密用主公钥加密的内容。不过这种方案需要对ECC密钥派生有一定了解,适合有密码学基础的场景。

关键注意事项

  • 优先使用浏览器原生Web Crypto API,避免第三方库的潜在安全风险。
  • 私钥必须安全存储:禁止明文存储,可使用浏览器的Web Crypto将私钥标记为不可提取,或结合IndexedDB加密存储。
  • 加密算法选择:AES-GCM提供内置的完整性校验,比ECB/CBC更安全;RSA-OAEP比RSA-PKCS#1 v1.5更抗攻击。

内容的提问来源于stack exchange,提问作者MrRav3n

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 11:55:25