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

前端侧加密:会话全程中密码应存储于何处?

针对你这个无账户、零知识加密存储的需求,我分享一套经过实践验证的方案,核心是让密钥完全掌握在用户手里,后端只做「存管」不碰明文:

核心原则:零知识加密

要实现「应用维护者无法解密」,必须遵循零知识架构:

  • 用户的加密密钥完全在前端生成、使用,绝不传输到后端
  • 后端仅存储加密后的密文、加密辅助信息(如盐、IV),永远接触不到明文数据或密钥
具体实现步骤

1. 密钥生成:从用户密码派生加密密钥

因为没有账户系统,密钥必须由用户输入的密码派生而来,这里一定要用慢哈希算法防止暴力破解:

  • 推荐用 PBKDF2(浏览器兼容性好)或 Argon2(更安全,需要前端库支持)
  • 生成密钥时要加随机盐(每次派生都用新盐,盐和密文一起存在后端)
  • 迭代次数要足够高(PBKDF2至少10万次,Argon2用官方推荐参数)

前端示例代码(用Web Crypto API):

async function getAesKeyFromPassword(userPassword) {
  const encoder = new TextEncoder();
  const passwordBytes = encoder.encode(userPassword);
  // 生成随机盐,后续要和密文一起存储
  const salt = crypto.getRandomValues(new Uint8Array(16));
  
  // 导入密码作为密钥材料
  const keyMaterial = await crypto.subtle.importKey(
    "raw", passwordBytes, { name: "PBKDF2" }, false, ["deriveKey"]
  );
  
  // 派生AES-GCM密钥(256位,支持加密解密)
  const aesKey = await crypto.subtle.deriveKey(
    { name: "PBKDF2", salt, iterations: 100000, hash: "SHA-256" },
    keyMaterial, { name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"]
  );
  
  return { aesKey, salt: btoa(String.fromCharCode(...salt)) };
}

2. 前端加密流程

JS图形编辑器生成JSON后,在前端完成加密再传给后端:

  • 用AES-GCM算法(自带完整性校验,防止密文被篡改)
  • 每次加密生成随机IV(初始化向量,12字节是GCM推荐长度)
  • 将密文、IV、盐一起转成Base64(方便传输和存储)

前端加密示例:

async function encryptGraphJson(graphData, aesKey) {
  const encoder = new TextEncoder();
  const dataBytes = encoder.encode(JSON.stringify(graphData));
  const iv = crypto.getRandomValues(new Uint8Array(12));
  
  // 执行加密
  const encryptedBuffer = await crypto.subtle.encrypt(
    { name: "AES-GCM", iv }, aesKey, dataBytes
  );
  
  // 转成Base64字符串
  const ciphertext = btoa(String.fromCharCode(...new Uint8Array(encryptedBuffer)));
  const ivBase64 = btoa(String.fromCharCode(...iv));
  
  return { ciphertext, iv: ivBase64 };
}

3. Rails后端的职责

后端只需要提供简单的存储接口,完全不涉及加密解密逻辑:

  • 创建一个encrypted_graphs表,字段包括:ciphertext:text(存加密后的JSON)、iv:string(加密用的IV)、salt:string(密码派生用的盐)、unique_identifier:string(可选,用户用来标识自己的图形,比如随机生成的ID)
  • 提供创建、查询、更新接口,接收前端传来的加密字段并存储

Rails模型示例:

# app/models/encrypted_graph.rb
class EncryptedGraph < ApplicationRecord
  validates :ciphertext, :iv, :salt, presence: true
  # 可以加唯一索引防止重复,或者用uuid作为主键
end

4. 数据恢复流程

用户下次访问时,输入相同密码:

  1. 前端用密码+后端返回的盐重新派生AES密钥
  2. 从后端拉取对应图形的密文和IV
  3. 前端解密得到原始JSON,传给图形编辑器加载

前端解密示例:

async function decryptGraphJson(ciphertext, ivBase64, aesKey) {
  const decoder = new TextDecoder();
  const iv = new Uint8Array(atob(ivBase64).split('').map(char => char.charCodeAt(0)));
  const encryptedBuffer = new Uint8Array(atob(ciphertext).split('').map(char => char.charCodeAt(0)));
  
  // 执行解密
  const decryptedBuffer = await crypto.subtle.decrypt(
    { name: "AES-GCM", iv }, aesKey, encryptedBuffer
  );
  
  return JSON.parse(decoder.decode(decryptedBuffer));
}
关键注意事项
  • 绝对不要把密码或密钥传给后端:所有密钥派生、加密解密操作都在前端完成,后端只处理密文
  • 避免XSS风险:密钥存在浏览器内存中,要及时清除(比如操作完成后将密钥对象设为null),同时确保页面没有XSS漏洞,防止密钥被窃取
  • 密码强度提醒:必须提示用户使用强密码(长度≥12位,包含大小写、数字、特殊字符),否则即使加密了也可能被暴力破解
  • 备份提示:因为没有账户,用户忘记密码就永远无法恢复数据,要在界面上提示用户备份自己的密码,或者提供密钥分片备份功能(比如让用户下载密钥分片,自己存储)

内容的提问来源于stack exchange,提问作者A. Neumann

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 11:43:24