如何基于Javascript/NodeJS与PHP实现端到端加密(E2EE)?
基础端到端加密(E2EE)实现方案:JS/Node.js + PHP
我来帮你梳理这个场景的实现思路和代码示例,刚好之前做过类似需求,咱们一步步来:
核心逻辑
端到端加密的关键是密钥完全由客户端掌控——服务器全程只跟密文打交道,根本碰不到你的明文数据和密钥,这才是真正的E2EE。整体流程:
- 客户端生成加密密钥,仅存在本地
- 客户端加密明文后,将密文+初始化向量(IV)发送给服务器存储
- 客户端从服务器拉取密文+IV后,用本地密钥解密得到明文
1. 客户端加密(JavaScript)
咱们用浏览器原生的Web Crypto API实现,比第三方库更安全可靠,不用额外引入依赖。
生成并存储密钥(仅客户端执行)
// 生成AES-GCM算法的密钥(可导出,方便存在localStorage) async function generateKey() { const key = await window.crypto.subtle.generateKey( { name: "AES-GCM", length: 256 }, true, // 是否允许导出密钥 ["encrypt", "decrypt"] // 密钥用途 ); // 把密钥导出成JWK格式,存在localStorage const exportedKey = await window.crypto.subtle.exportKey("jwk", key); localStorage.setItem("e2ee-key", JSON.stringify(exportedKey)); return key; }
结合表单的加密+发送示例
对应你提到的表单场景,完整代码如下:
<form id="message-form"> <input type="text" name="message" placeholder="输入要加密的内容"> <button type="submit">加密发送</button> </form> <script> document.getElementById('message-form').addEventListener('submit', async (e) => { e.preventDefault(); const plaintext = document.querySelector('input[name="message"]').value; // 获取密钥:本地有就复用,没有就生成 let key; const storedKey = localStorage.getItem("e2ee-key"); if (storedKey) { key = await window.crypto.subtle.importKey( "jwk", JSON.parse(storedKey), { name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"] ); } else { key = await generateKey(); } // 生成随机IV(AES-GCM必须用随机IV,不能重复,否则会泄露明文) const iv = window.crypto.getRandomValues(new Uint8Array(12)); // 把明文转成ArrayBuffer供加密使用 const encoder = new TextEncoder(); const data = encoder.encode(plaintext); // 执行加密 const encryptedData = await window.crypto.subtle.encrypt( { name: "AES-GCM", iv: iv }, key, data ); // 把密文和IV转成Base64,方便网络传输 const ciphertextBase64 = btoa(String.fromCharCode(...new Uint8Array(encryptedData))); const ivBase64 = btoa(String.fromCharCode(...iv)); // 发送到服务器 await fetch('/save-message.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ciphertext: ciphertextBase64, iv: ivBase64 }) }); document.querySelector('input[name="message"]').value = ''; alert('消息已加密发送!'); }); </script>
2. 服务器存储(PHP)
服务器只需要做「存储工具人」,接收密文和IV直接存数据库就行,完全不用碰加密逻辑:
<?php // save-message.php header('Content-Type: application/json'); header('Access-Control-Allow-Origin: *'); // 开发环境用,生产环境请指定具体域名 // 接收客户端的JSON数据 $data = json_decode(file_get_contents('php://input'), true); if (!$data || !isset($data['ciphertext'], $data['iv'])) { echo json_encode(['status' => 'error', 'message' => '参数缺失']); exit; } // 连接数据库(用PDO防止SQL注入) try { $pdo = new PDO('mysql:host=localhost;dbname=your_db;charset=utf8mb4', 'db_user', 'db_pwd'); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // 插入密文和IV到数据库 $stmt = $pdo->prepare('INSERT INTO messages (ciphertext, iv, created_at) VALUES (:ciphertext, :iv, NOW())'); $stmt->execute([ ':ciphertext' => $data['ciphertext'], ':iv' => $data['iv'] ]); echo json_encode(['status' => 'success']); } catch (PDOException $e) { echo json_encode(['status' => 'error', 'message' => $e->getMessage()]); } ?>
3. 客户端解密(JavaScript)
从服务器拉取密文后,用本地密钥解密:
async function fetchAndDecryptMessages() { // 从服务器获取加密后的消息列表 const response = await fetch('/get-messages.php'); const messages = await response.json(); // 获取本地存储的密钥 const storedKey = localStorage.getItem("e2ee-key"); if (!storedKey) { alert('未找到密钥,无法解密!'); return; } const key = await window.crypto.subtle.importKey( "jwk", JSON.parse(storedKey), { name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"] ); // 逐个解密消息 const decryptedMessages = await Promise.all(messages.map(async (msg) => { // 把Base64转成ArrayBuffer const ciphertext = Uint8Array.from(atob(msg.ciphertext), c => c.charCodeAt(0)); const iv = Uint8Array.from(atob(msg.iv), c => c.charCodeAt(0)); // 执行解密 const decryptedData = await window.crypto.subtle.decrypt( { name: "AES-GCM", iv: iv }, key, ciphertext ); // 转成明文 const decoder = new TextDecoder(); return decoder.decode(decryptedData); })); // 把解密后的消息渲染到页面(示例:打印到控制台) console.log('解密后的消息:', decryptedMessages); } // 调用函数获取并解密 fetchAndDecryptMessages();
对应的PHP查询接口get-messages.php:
<?php // get-messages.php header('Content-Type: application/json'); header('Access-Control-Allow-Origin: *'); try { $pdo = new PDO('mysql:host=localhost;dbname=your_db;charset=utf8mb4', 'db_user', 'db_pwd'); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // 查询所有加密消息 $stmt = $pdo->query('SELECT ciphertext, iv FROM messages ORDER BY created_at DESC'); $messages = $stmt->fetchAll(PDO::FETCH_ASSOC); echo json_encode($messages); } catch (PDOException $e) { echo json_encode(['status' => 'error', 'message' => $e->getMessage()]); } ?>
关键注意事项
- 密钥安全:示例用
localStorage存密钥,适合基础场景;如果是敏感数据,建议结合浏览器的安全存储机制,绝对不能把密钥发送到服务器! - 加密算法:一定要用
AES-GCM(带认证的加密),它能同时保证机密性和完整性,防止密文被篡改;绝对不要用ECB模式(完全不安全)。 - IV的必要性:IV必须随机生成,每次加密都要用新的IV,重复IV会导致明文泄露。
- 传输安全:即使是E2EE,数据传输也要用HTTPS,防止密文被中间人拦截或篡改(AES-GCM能检测篡改,但HTTPS能提前规避风险)。
- Node.js适配:如果是Node.js客户端(比如Electron),可以用Node.js内置的
crypto模块替代Web Crypto API,逻辑完全一致,仅API细节略有不同。
内容的提问来源于stack exchange,提问作者nsylke
相关产品推荐
相关产品推荐

