无头浏览器中利用Web Crypto API实现数据源头加密的完整方案
2026/7/6 7:13:33 网站建设 项目流程

1. 项目概述:为什么要在无头浏览器里搞加密?

最近在做一个自动化数据采集和处理的脚本,遇到了一个挺有意思的需求:从目标网站上抓取到的数据,有些是敏感信息,比如用户的部分脱敏标识、交易哈希值或者一些配置参数。这些数据在传输回我的处理服务器之前,如果能在采集端(也就是浏览器环境里)先加密一把,那安全性就能提升一个档次。毕竟,谁也不能保证网络链路或者中间处理环节百分百可靠。

这时候,Lightpanda这个无头浏览器框架就进入了我的视野。它基于 Chromium,能提供一个完整的、可编程的浏览器环境。而现代浏览器都内置了一个强大的武器——Web Crypto API。这玩意儿可不是玩具,它提供的是底层密码学原语的标准化接口,比如 AES-GCM、RSA-OAEP、SHA-256 这些,跟你在 Node.js 里用的crypto模块或者 OpenSSL 库是同一个级别的。这意味着,我们可以在 Lightpanda 控制的浏览器实例中,直接调用这些原生的、高性能的加密功能,实现“采集即加密”,数据从源头开始就是密文。

听起来很美好,对吧?但实际操作起来,坑一点不少。Web Crypto API 本身设计得比较底层和严谨,它的所有操作都是异步的(返回 Promise),并且只在安全上下文(HTTPS 或 localhost)中可用。在无头环境下,我们还得处理密钥的生成、存储、派生以及如何安全地传递加密数据给后端等一系列问题。这篇文章,我就把自己趟过的路、踩过的坑,以及最终跑通的一套在 Lightpanda 里使用 Web Crypto API 进行安全数据加密的完整方案分享出来。无论你是做数据爬虫、自动化测试,还是需要在前端环境预处理敏感信息的开发者,这套思路应该都能给你一些参考。

2. 核心思路与架构设计

2.1 为什么选择 Web Crypto API 而非 Node.js crypto?

首先得想明白,为什么非得在浏览器环境里做加密,而不是把原始数据抓回来,在更“强大”的 Node.js 后端用crypto模块加密?这里有几个关键的考量点:

  1. 减少敏感数据暴露面:这是最核心的原因。如果明文数据离开了浏览器环境,在你的自动化脚本进程、网络传输、甚至临时的日志文件中,都可能留下痕迹。在数据产生的源头(浏览器页面内)立即加密,意味着敏感信息从未以明文形式存在于你的采集管道中,这符合“最小化攻击面”的安全原则。
  2. 利用隔离的沙箱环境:Lightpanda 启动的每个浏览器实例都是一个独立的沙箱。加密操作在这个沙箱内完成,私钥或对称密钥可以仅存在于这个沙箱的内存中,生命周期随浏览器实例结束而销毁。这比在常驻的 Node.js 进程中管理密钥要更清晰、更隔离。
  3. 与目标网站逻辑保持一致:有些网站的前端本身就会对某些数据进行加密后再发送。为了能正确模拟用户行为或处理这些数据,我们必须在同样的浏览器上下文中拥有相同的加密能力。
  4. 性能与原生优势:Web Crypto API 是浏览器原生实现,通常比用 JavaScript 纯算法库(如asmCrypto.js)要快得多,而且更可靠。对于大量数据的加密,这个优势很明显。

当然,代价就是复杂性增加了。你需要在无头浏览器中执行 JavaScript,并设计一套通信机制来获取加密结果。

2.2 整体方案设计

我的方案核心流程如下图所示(此处用文字描述):

  1. 初始化与密钥准备:Lightpanda 启动一个无头浏览器实例,并导航到一个空白页或安全的本地页面。在这个页面上下文中,使用 Web Crypto API 生成或导入加密所需的密钥。对于对称加密(如 AES),我推荐在每次任务开始时生成一个新密钥;对于非对称加密(如 RSA),可以预先生成密钥对,将公钥嵌入脚本,私钥由后端安全保存。
  2. 数据获取与加密:浏览器执行采集任务,从目标页面获取到需要加密的原始数据(字符串、JSON 对象等)。紧接着,在同一个页面脚本中,调用 Web Crypto API,使用上一步准备的密钥,对数据进行加密。输出通常是ArrayBuffer或经过 Base64 编码的字符串。
  3. 密文提取与传输:加密后的数据(密文)需要从浏览器页面上下文传递到我们的 Node.js 主控脚本。这里通过 Lightpanda 提供的页面评估方法(如page.evaluate)将密文作为返回值取出。然后,主控脚本将密文(以及必要的初始向量 IV 或密钥标识)通过安全的信道(如 HTTPS)发送到后端服务器。
  4. 后端解密与处理:后端服务器持有对应的解密密钥(对称加密的密钥,或非对称加密的私钥),对接收到的密文进行解密,还原出原始明文数据,再进行后续的业务处理。

整个过程中,密钥管理是最需要精心设计的一环。我的原则是:对称密钥尽量做到“一次一密”(或一个会话一密),使用后即弃;非对称加密的公钥可以公开,但私钥必须严格保存在后端,绝不发送到浏览器端。

注意:Web Crypto API 的SubtleCrypto接口(通过crypto.subtle访问)仅在工作在安全上下文中的页面中可用。这意味着你的 Lightpanda 页面地址必须是https://http://localhostfile://(但file://协议可能有其他限制)。对于采集外部网站,我们通常是在那个网站的上下文中执行脚本,该网站本身是 HTTPS 的话,API 就是可用的。如果需要在“空白”新标签页使用,可以考虑导航到about:blank然后通过page.evaluateOnNewDocument注入脚本,但更稳妥的方式是启动一个本地 HTTP 服务器,服务一个简单的 HTTPS(或 localhost)页面作为工作环境。

3. 核心工具与环境搭建

3.1 Lightpanda 基础配置

Lightpanda 并不是一个广为人知的库,它可能是一个特定项目或较小众的工具。为了普适性,我这里假设我们使用更主流的PuppeteerPlaywright作为无头浏览器控制库,因为它们原理相通,且社区活跃,遇到问题更容易找到解决方案。两者的 API 非常相似,本文示例将使用 Playwright,因为它对多浏览器的支持更好,且 API 设计更现代。

首先,初始化一个 Node.js 项目并安装依赖:

npm init -y npm install playwright # 如果需要特定浏览器,比如 Chromium npx playwright install chromium

创建一个基本的启动脚本encrypt-demo.js

const { chromium } = require('playwright'); (async () => { // 1. 启动浏览器,推荐无头模式(生产环境),调试时可设为 false const browser = await chromium.launch({ headless: true }); // 2. 创建浏览器上下文,可以设置视口、User-Agent等 const context = await browser.newContext(); // 3. 打开新页面 const page = await context.newPage(); // 这里将进行我们的加密操作 // ... // 4. 操作完成后,关闭浏览器 await browser.close(); })();

3.2 理解 Web Crypto API 的核心接口:crypto.subtle

window.crypto.subtle是我们所有加密操作的入口点。它提供了以下主要功能:

  • 密钥生成generateKey用于生成对称或非对称密钥。
  • 密钥导入/导出importKey/exportKey用于将密钥从外部格式(如 JWK, Raw)导入到 CryptoKey 对象,或反之。
  • 加密/解密encrypt/decrypt
  • 签名/验证sign/verify
  • 摘要计算digest(哈希)。
  • 密钥派生deriveKey/deriveBits

所有方法都返回Promise,操作是异步的。密钥和算法参数都有严格的定义。接下来,我们将深入最常用的对称加密(AES-GCM)和非对称加密(RSA-OAEP)的实战。

4. 实战一:使用 AES-GCM 进行对称加密

AES-GCM(Galois/Counter Mode)是目前推荐使用的对称加密算法。它同时提供了保密性(加密)和完整性认证(防止密文被篡改),而且性能不错。

4.1 在页面中生成密钥并加密数据

我们写一个函数,在浏览器页面环境中执行,完成密钥生成、数据加密,并返回密文和必要的参数。

// 这是在浏览器页面内执行的代码 async function encryptWithAESGCM(plaintext) { // 1. 生成一个 AES-GCM 密钥,密钥长度 256 位 const key = await window.crypto.subtle.generateKey( { name: "AES-GCM", length: 256, // 可以是 128, 192, 256 }, true, // 是否可导出(这里设为 true,方便我们演示导出传输) ["encrypt", "decrypt"] // 密钥用途 ); // 2. 生成一个随机的 96 位(12字节)初始化向量 (IV),每次加密都必须使用新的 IV const iv = window.crypto.getRandomValues(new Uint8Array(12)); // 3. 将明文转换为 ArrayBuffer const encoder = new TextEncoder(); const data = encoder.encode(plaintext); // 4. 执行加密 const ciphertext = await window.crypto.subtle.encrypt( { name: "AES-GCM", iv: iv, // 必须提供 // 可选:指定附加认证数据 (AAD),这里不用 // additionalData: ... // 可选:指定认证标签长度,默认 128 位 // tagLength: 128 }, key, // 上面生成的密钥 data // 明文数据 ); // 5. 将密钥导出为 Raw 格式(二进制),方便传输给后端(实际生产中,密钥分发是另一个安全课题) const exportedKey = await window.crypto.subtle.exportKey("raw", key); // 6. 将 ArrayBuffer 结果转换为 Base64 字符串,便于 JSON 传输 function arrayBufferToBase64(buffer) { const bytes = new Uint8Array(buffer); let binary = ''; for (let i = 0; i < bytes.byteLength; i++) { binary += String.fromCharCode(bytes[i]); } return window.btoa(binary); } return { ciphertext: arrayBufferToBase64(ciphertext), iv: arrayBufferToBase64(iv.buffer), exportedKey: arrayBufferToBase64(exportedKey) }; }

4.2 通过 Playwright 调用并获取结果

现在,我们在 Node.js 主控脚本中,导航到一个安全页面(例如本地服务器或about:blank),然后注入并执行上面的函数。

const { chromium } = require('playwright'); const https = require('https'); // 用于安全传输 (async () => { const browser = await chromium.launch({ headless: true }); const context = await browser.newContext(); const page = await context.newPage(); // 方法A:导航到一个本地安全页面(需要先启动一个本地服务器) // await page.goto('http://localhost:3000/dummy.html'); // 方法B:更简单,导航到 about:blank,然后直接注入脚本 await page.goto('about:blank'); // 要加密的原始数据 const sensitiveData = JSON.stringify({ userId: 'user_12345', timestamp: Date.now(), action: 'view_profile' }); // 在页面上下文中执行加密函数 const encryptionResult = await page.evaluate(async (dataToEncrypt) => { // 这里直接定义并调用上面的 encryptWithAESGCM 函数 // 注意:evaluate 内的函数不能直接引用外部变量,需要完整定义或作为参数传入 const encryptFunc = async (plaintext) => { // ... 将上面 encryptWithAESGCM 函数的完整代码复制到这里 ... }; return await encryptFunc(dataToEncrypt); }, sensitiveData); // 将数据作为参数传入 console.log('加密完成,结果已从浏览器中取出:'); console.log('IV (Base64):', encryptionResult.iv); console.log('Ciphertext (Base64):', encryptionResult.ciphertext); console.log('Exported Key (Base64):', encryptionResult.exportedKey); // 在实际应用中,你需要将 ciphertext, iv 和 exportedKey 安全地传输到后端 // 这里模拟一个 HTTPS POST 请求 const postData = JSON.stringify({ data: encryptionResult.ciphertext, iv: encryptionResult.iv, key: encryptionResult.exportedKey // 注意:实际生产环境,对称密钥不应这样传输! }); const options = { hostname: 'your-backend.com', port: 443, path: '/api/decrypt', method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': postData.length } }; // 发送请求(此处省略请求代码) // const req = https.request(options, (res) => { ... }); await browser.close(); })();

4.3 后端解密示例(Node.js crypto 模块)

后端收到密文、IV 和密钥后,需要用同样的算法解密。这里用 Node.js 的crypto模块演示:

const crypto = require('crypto'); function decryptAESGCM(ciphertextBase64, ivBase64, keyBase64) { // 1. 将 Base64 字符串转换回 Buffer const ciphertext = Buffer.from(ciphertextBase64, 'base64'); const iv = Buffer.from(ivBase64, 'base64'); const key = Buffer.from(keyBase64, 'base64'); // 2. 创建解密器 const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv); // 3. 在 GCM 模式下,需要设置认证标签(authentication tag)。 // Web Crypto API 生成的密文,认证标签默认附加在密文末尾(16字节)。 const tagLength = 16; const tag = ciphertext.subarray(-tagLength); // 取出最后16字节作为tag const encryptedData = ciphertext.subarray(0, -tagLength); // 剩余部分是真正的加密数据 decipher.setAuthTag(tag); // 设置认证标签 // 4. 执行解密 let decrypted = decipher.update(encryptedData); decrypted = Buffer.concat([decrypted, decipher.final()]); // 5. 返回明文字符串 return decrypted.toString('utf8'); } // 使用示例 const result = decryptAESGCM( encryptionResult.ciphertext, encryptionResult.iv, encryptionResult.exportedKey ); console.log('解密结果:', result);

关键心得:AES-GCM 的 IV 不需要保密,但必须唯一且不可预测。每次加密都使用crypto.getRandomValues生成新的 IV 是绝对必要的。另外,exportedKey在演示中为了方便传输给了后端,但在真实场景中,对称密钥的分发是一个重大挑战。你不能每次都把新密钥通过网络发送。更常见的做法是:使用一个预先共享的密钥加密钥(KEK)来加密每次的数据加密密钥(DEK),然后将加密后的 DEK 和密文一起发送,这就是“信封加密”模式。或者,直接使用下一节的非对称加密来传输对称密钥。

5. 实战二:使用 RSA-OAEP 进行非对称加密

非对称加密适合这样的场景:你希望任何人都能加密数据,但只有持有私钥的你才能解密。在爬虫场景中,你可以将公钥硬编码在脚本里,用于加密采集到的数据;私钥则安全地存储在后端服务器上。

5.1 生成 RSA 密钥对

首先,我们需要生成一对 RSA 密钥。这个操作通常只需要做一次,你可以用 OpenSSL 命令行工具生成,或者在 Node.js 中生成后保存。这里演示在 Node.js 中生成并导出公钥,以便嵌入到浏览器脚本中。

// generate-keys.js - 在 Node.js 环境中运行,生成密钥对 const { webcrypto } = require('crypto'); const { writeFileSync } = require('fs'); const crypto = webcrypto; (async () => { // 生成 RSA-OAEP 密钥对,2048 位是当前推荐的最小长度 const keyPair = await crypto.subtle.generateKey( { name: "RSA-OAEP", modulusLength: 2048, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // 65537 hash: "SHA-256", }, true, // 是否可导出 ["encrypt", "decrypt"] // 公钥加密,私钥解密 ); // 导出公钥为 JWK (JSON Web Key) 格式,方便在浏览器中导入 const publicKeyJwk = await crypto.subtle.exportKey("jwk", keyPair.publicKey); // 导出私钥为 PKCS#8 格式(PEM),保存到文件,务必保密! const privateKeyPem = await crypto.subtle.exportKey("pkcs8", keyPair.privateKey) .then(keyBuffer => { const keyBase64 = Buffer.from(keyBuffer).toString('base64'); return `-----BEGIN PRIVATE KEY-----\n${keyBase64.match(/.{1,64}/g).join('\n')}\n-----END PRIVATE KEY-----\n`; }); writeFileSync('public-key.json', JSON.stringify(publicKeyJwk, null, 2)); writeFileSync('private-key.pem', privateKeyPem); console.log('公钥已保存至 public-key.json'); console.log('私钥已保存至 private-key.pem (请妥善保管!)'); })();

运行后,你会得到public-key.jsonprivate-key.pem两个文件。public-key.json的内容类似这样:

{ "kty": "RSA", "n": "vV7l5z...很长的一个Base64字符串...", "e": "AQAB", "alg": "RSA-OAEP-256", "ext": true }

5.2 在浏览器中使用公钥加密

现在,我们将公钥 JWK 嵌入到 Lightpanda/Playwright 的脚本中,在页面里导入它并加密数据。

// 这是要在浏览器页面中执行的加密函数 async function encryptWithRSAOAEP(plaintext, publicKeyJwk) { // 1. 从 JWK 格式导入公钥 const publicKey = await window.crypto.subtle.importKey( "jwk", // 格式 publicKeyJwk, // 上面生成的 JWK 对象 { name: "RSA-OAEP", hash: "SHA-256" }, true, // 是否可导出(公钥通常可以) ["encrypt"] // 密钥用途 ); // 2. 编码明文 const encoder = new TextEncoder(); const data = encoder.encode(plaintext); // 3. RSA-OAEP 加密有长度限制,明文不能太长。 // 对于 2048 位密钥,使用 SHA-256 哈希,最大明文长度约为 190 字节。 // 如果要加密更长的数据,需要采用“混合加密”:用 RSA 加密一个随机的对称密钥,再用该对称密钥加密数据。 if (data.length > 190) { throw new Error('Plaintext too long for RSA-OAEP with 2048-bit key. Consider hybrid encryption.'); } // 4. 执行加密 const ciphertext = await window.crypto.subtle.encrypt( { name: "RSA-OAEP" }, publicKey, data ); // 5. 转换为 Base64 return arrayBufferToBase64(ciphertext); // 复用之前定义的转换函数 }

在 Playwright 脚本中调用:

const publicKeyJwk = require('./public-key.json'); // 导入之前生成的公钥 const encryptionResult = await page.evaluate(async ([dataToEncrypt, pubKey]) => { const encryptFunc = async (plaintext, keyJwk) => { // ... 将上面 encryptWithRSAOAEP 函数的完整代码复制到这里 ... }; return await encryptFunc(dataToEncrypt, pubKey); }, [sensitiveData, publicKeyJwk]); // 将数据和公钥作为参数传入 console.log('RSA 加密后的密文 (Base64):', encryptionResult);

5.3 后端使用私钥解密

后端使用保存的 PEM 格式私钥进行解密。

const crypto = require('crypto'); const fs = require('fs'); function decryptRSAOAEP(ciphertextBase64) { // 1. 读取私钥 const privateKeyPem = fs.readFileSync('private-key.pem', 'utf8'); // 2. 将 Base64 密文转为 Buffer const ciphertext = Buffer.from(ciphertextBase64, 'base64'); // 3. 使用私钥解密 const decrypted = crypto.privateDecrypt( { key: privateKeyPem, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256' }, ciphertext ); // 4. 返回明文 return decrypted.toString('utf8'); } // 使用示例 const plaintext = decryptRSAOAEP(encryptionResult); console.log('RSA 解密结果:', plaintext);

注意事项:RSA 加密速度慢,且对明文长度有严格限制。因此,它不适合直接加密大量数据。标准的做法是混合加密:在浏览器端生成一个随机的 AES 密钥(会话密钥),用 AES-GCM 加密实际数据,再用 RSA-OAEP 加密这个 AES 密钥。将加密后的 AES 密钥和加密后的数据一起发送给后端。后端先用私钥解密出 AES 密钥,再用它解密数据。这样结合了非对称加密的密钥分发优势和对称加密的效率优势。

6. 密钥管理与安全实践

在无头浏览器环境中管理密钥,比在服务器端更棘手,因为浏览器实例可能随时被销毁。

6.1 密钥的生命周期与存储

  • 临时会话密钥:对于 AES 对称加密,最佳实践是为每个浏览器会话(或每次采集任务)生成一个全新的密钥。这个密钥保存在浏览器内存中,随着页面关闭或浏览器实例退出而消失。你只需要将加密后的数据和安全导出的密钥(或加密后的密钥)传回后端。绝对不要将长期使用的固定对称密钥硬编码在客户端脚本中。
  • 非对称密钥对:公钥可以安全地嵌入脚本或从后端动态获取。私钥永远不能出现在客户端。应该在后端安全地生成和存储(如使用硬件安全模块 HSM、云 KMS 服务,或至少是加密的配置文件)。
  • 密钥导出格式exportKey支持多种格式:raw(原始二进制)、pkcs8(私钥)、spki(公钥)、jwk(JSON)。jwk格式便于 JSON 传输和存储,raw格式最紧凑。选择哪种取决于你的传输和存储需求。

6.2 安全上下文与 HTTPS

这是 Web Crypto APIsubtle属性使用的硬性要求。确保你的脚本执行环境是安全的:

  • 如果采集目标网站是 HTTPS,那么在其页面上下文中直接使用 API 是没问题的。
  • 如果你需要在“自己控制”的页面中执行加密(比如先导航到一个本地页面),那么这个页面的源必须是https://http://localhostfile://(注意file://可能有跨域限制)。对于自动化脚本,启动一个简单的本地 HTTPS 服务器是最可靠的方式。可以使用mkcert工具为 localhost 生成受信任的证书,然后用 Node.js 的https模块提供服务。

6.3 错误处理与日志

加密操作可能因各种原因失败(不支持的算法、无效的密钥、数据过长等)。务必用try...catch包裹你的encryptdecryptgenerateKey等调用。在无头环境中,错误信息可能不易获取,建议将错误信息通过page.evaluate的返回值或console.log(Playwright 可以监听console事件)传递出来,方便调试。

page.on('console', msg => console.log('浏览器日志:', msg.text())); const result = await page.evaluate(async () => { try { // ... 加密操作 ... return { success: true, data: ciphertext }; } catch (error) { return { success: false, error: error.message }; } });

7. 常见问题与排查实录

在实际操作中,我遇到了不少问题,这里记录下最典型的几个及其解决方法。

7.1 错误:crypto.subtleis undefined

问题描述:在页面评估中调用window.crypto.subtle返回undefined原因与解决

  1. 非安全上下文:这是最常见的原因。检查页面 URL 是否以https://http://localhost开头。如果是about:blank,在某些浏览器版本中可能也被视为安全上下文,但为了稳定,建议导航到http://localhost下的一个页面。
  2. 浏览器版本过旧:确保你使用的 Chromium 版本支持 Web Crypto API。Puppeteer/Playwright 自带的 Chromium 通常都是较新版本,支持良好。

7.2 错误:The operation failed for an operation-specific reason

问题描述:调用encryptgenerateKey时抛出此错误,信息不明确。原因与解决

  1. 算法参数不匹配:仔细检查算法名称、密钥长度、哈希函数等参数是否与密钥生成时指定的完全一致。例如,用{name: "RSA-OAEP", hash: "SHA-256"}生成的密钥,在加密时必须使用完全相同的算法描述对象。
  2. 密钥用途不正确:生成密钥时指定的usages数组必须包含你将要进行的操作。例如,用["encrypt"]生成的密钥不能用于decrypt
  3. 数据格式错误:确保明文数据是ArrayBufferTypedArray类型。使用TextEncoder().encode()进行转换。

7.3 错误:Data provided to an operation does not meet requirements

问题描述:在 RSA 加密时出现。原因与解决

  1. 明文超长:RSA-OAEP 有严格的明文长度限制。对于 2048 位密钥和 SHA-256,最大明文长度约为 190 字节。解决方案是采用混合加密:生成一个随机的 AES 密钥,用它加密长数据,再用 RSA 加密这个 AES 密钥。

7.4 后端解密失败

问题描述:浏览器端加密成功,但后端 Node.jscrypto模块解密失败或得到乱码。原因与解决

  1. IV 或认证标签处理不当:对于 AES-GCM,密文末尾包含了 16 字节的认证标签。Web Crypto API 的encrypt结果包含了它,而 Node.js 的createDecipheriv需要你手动用setAuthTag()设置它。确保你正确地将 IV 和认证标签从加密结果中分离并传递给解密函数。
  2. Base64 编码/解码不一致:确保前后端使用相同的 Base64 编码/解码方式。浏览器端使用btoa/atob(针对二进制字符串),Node.js 使用Buffer.from(str, 'base64')。注意btoa不能直接处理非 Latin1 字符,所以我们对ArrayBuffer做了转换。
  3. 算法或参数不匹配:再次确认前后端使用的算法名称、密钥长度、模式(如 GCM)、填充方式(如 OAEP)完全一致。一个字母都不能差。

7.5 性能问题

问题描述:加密大量数据时脚本执行缓慢。原因与解决

  1. 避免使用 RSA 加密大数据:牢记 RSA 只适合加密小块数据(如一个密钥)。对于任何超过几百字节的数据,都应采用混合加密方案。
  2. 批量处理:如果有多条独立数据需要加密,可以考虑在页面内批量处理,减少与 Node.js 主进程的通信次数,而不是每条数据都调用一次page.evaluate
  3. Worker 优化:对于计算密集型加密任务,可以考虑在浏览器中启用 Web Worker,但无头环境下支持度需要测试。

8. 完整示例:混合加密实战

最后,给出一个更贴近生产环境的混合加密示例,它结合了 AES 的高效和 RSA 的安全密钥交换。

浏览器端流程

  1. 生成一个随机的 AES-GCM 密钥(DEK,数据加密密钥)。
  2. 使用 AES-GCM 和随机 IV 加密实际数据。
  3. 导入固定的 RSA 公钥。
  4. 使用 RSA-OAEP 加密上一步生成的 AES 密钥(Raw 格式)。
  5. 将以下内容打包发送给后端:
    • encryptedData(AES 加密后的数据,含认证标签)
    • iv(AES 使用的 IV)
    • encryptedKey(RSA 加密后的 AES 密钥)

后端流程

  1. 使用 RSA 私钥解密encryptedKey,得到 AES 密钥。
  2. 使用该 AES 密钥和收到的iv,解密encryptedData

这样,即使公钥暴露,攻击者也无法解密数据(因为没有私钥)。而每次会话都使用不同的 AES 密钥,提供了前向安全性。

这个方案的代码较长,但结构清晰。核心在于浏览器端需要顺序执行两个加密操作,并妥善打包所有必要的组件。通过这样的设计,我们就在 Lightpanda 这样的无头浏览器环境中,构建了一套从源头保障数据机密性的安全加密流程。它不仅适用于数据采集,任何需要在浏览器沙箱内预处理敏感信息的自动化任务,都可以借鉴这套模式。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询