如何用JavaScript Crypto API为多公钥加密数据?RSA-OAEP可行吗?
多公钥加密的可行方案(替代RSA-OAEP直接多密钥加密)
好问题!先直接给结论:Web Crypto API的RSA-OAEP确实无法直接用多个公钥加密同一份数据——你写的那段代码里把ArrayOfPublicKeys传给encrypt方法会直接报错,因为这个API只接受单个CryptoKey对象作为密钥参数,不支持数组。
不过我们可以用「对称加密+多公钥加密对称密钥」的经典方案来实现需求,这也是业界处理多接收方加密的标准做法,具体逻辑如下:
核心思路
- 生成一个随机的对称加密密钥(比如AES-GCM,效率远高于RSA)
- 用这个对称密钥加密原始数据(只需要加密一次)
- 遍历每个接收方的公钥,用RSA-OAEP分别加密这个对称密钥(每个公钥对应一份加密后的对称密钥)
- 把「加密后的原始数据」+「所有公钥加密后的对称密钥集合」一起发给接收方
- 每个接收方用自己的私钥解密对应的对称密钥,再用该密钥解密原始数据
完整测试代码
async function encryptForMultipleRecipients(publicKeys, rawData) { // 1. 生成随机AES-GCM对称密钥 const symmetricKey = await window.crypto.subtle.generateKey( { name: "AES-GCM", length: 256 }, true, // 可导出(后续需要用公钥加密它) ["encrypt", "decrypt"] ); // 2. 用对称密钥加密原始数据 const iv = window.crypto.getRandomValues(new Uint8Array(12)); // AES-GCM标准IV长度 const encryptedData = await window.crypto.subtle.encrypt( { name: "AES-GCM", iv: iv }, symmetricKey, rawData ); // 3. 导出对称密钥为原始二进制,方便用RSA加密 const exportedSymmetricKey = await window.crypto.subtle.exportKey( "raw", symmetricKey ); // 4. 用每个公钥分别加密对称密钥 const encryptedSymmetricKeys = []; for (const pubKey of publicKeys) { const encryptedKey = await window.crypto.subtle.encrypt( { name: "RSA-OAEP" }, pubKey, exportedSymmetricKey ); encryptedSymmetricKeys.push(encryptedKey); } // 返回所有需要传输的数据:IV、加密后的原始数据、每个公钥对应的加密对称密钥 return { iv: iv, encryptedData: encryptedData, encryptedSymmetricKeys: encryptedSymmetricKeys }; } async function decryptWithPrivateKey(privateKey, encryptedPackage, keyIndex) { // 1. 用私钥解密对应的对称密钥 const encryptedSymmetricKey = encryptedPackage.encryptedSymmetricKeys[keyIndex]; const exportedSymmetricKey = await window.crypto.subtle.decrypt( { name: "RSA-OAEP" }, privateKey, encryptedSymmetricKey ); // 2. 导入对称密钥 const symmetricKey = await window.crypto.subtle.importKey( "raw", exportedSymmetricKey, { name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"] ); // 3. 解密原始数据 const decryptedData = await window.crypto.subtle.decrypt( { name: "AES-GCM", iv: encryptedPackage.iv }, symmetricKey, encryptedPackage.encryptedData ); return decryptedData; } // ---------------------- 测试示例 ---------------------- (async () => { // 生成两个测试用的RSA密钥对 const keyPair1 = await window.crypto.subtle.generateKey( { name: "RSA-OAEP", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" }, true, ["encrypt", "decrypt"] ); const keyPair2 = await window.crypto.subtle.generateKey( { name: "RSA-OAEP", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" }, true, ["encrypt", "decrypt"] ); // 原始数据(转为ArrayBuffer) const rawData = new TextEncoder().encode("Hello, multiple recipients!"); // 用两个公钥加密数据 const encryptedPackage = await encryptForMultipleRecipients( [keyPair1.publicKey, keyPair2.publicKey], rawData ); // 测试第一个私钥解密 const decrypted1 = await decryptWithPrivateKey(keyPair1.privateKey, encryptedPackage, 0); console.log("解密结果1:", new TextDecoder().decode(decrypted1)); // 测试第二个私钥解密 const decrypted2 = await decryptWithPrivateKey(keyPair2.privateKey, encryptedPackage, 1); console.log("解密结果2:", new TextDecoder().decode(decrypted2)); })();
为什么RSA-OAEP不支持多公钥直接加密?
RSA-OAEP的加密流程是基于单个公钥的数学运算:它会用公钥的模数和指数对数据(或密钥)进行加密,每个公钥的模数/指数都不同,无法同时适配多个公钥进行一次加密操作。Web Crypto API的设计也严格遵循了这一数学特性,所以只接受单个密钥参数。
内容的提问来源于stack exchange,提问作者Dr.Jack Millan
相关产品推荐
相关产品推荐

