我如何使用 Next.js 和 Web Crypto API 构建零知识秘密共享者

发布: (2026年1月15日 GMT+8 17:20)
4 min read
原文: Dev.to

Source: Dev.to

封面图片:如何使用 Next.js 和 Web Crypto API 构建零知识秘密共享工具

大多数“安全”共享工具都要求你信任服务器。你粘贴密码,服务器对其加密并存储。但如果服务器记录了请求,或者数据库泄漏,你的秘密就不复存在。

我想要一个工具,让 (开发者)即使想读也读不到数据。

于是我构建了 Nixhttps://nix.jaid.dev),一个开源、零知识的秘密共享应用。下面是它的技术实现细节,使用 AES‑GCMURL 哈希片段

架构

核心约束是 Zero Knowledge —— 服务器绝不能收到解密密钥。

  1. Alice 在浏览器中生成随机密钥。
  2. Alice 在客户端加密数据。
  3. Alice 只发送密文(包装在 JSON 信封中)到服务器(Supabase)。
  4. 浏览器构造链接:https://nix.jaid.dev/view/[ID]#[KEY]
  5. Bob 点击链接。他的浏览器请求该 ID,从 URL 中提取 #[KEY](该密钥从未发送到服务器),并在本地解密数据。

技术栈

  • 前端: Next.js 16 (App Router)
  • 数据库: Supabase (Postgres)
  • 加密: 原生 Web Crypto API (window.crypto.subtle)
  • 样式: Tailwind CSS

硬核部分:Web Crypto API

Web Crypto API 功能强大但代码冗长。下面是加密流程的关键步骤。

生成密钥

我们需要一个密码学上安全的随机密钥。SubtleCrypto API 能保证安全生成。

// Generate a secure AES‑GCM key
const key = await window.crypto.subtle.generateKey(
  { name: "AES-GCM", length: 256 },
  true,
  ["encrypt", "decrypt"]
);

// Export to raw bytes (and then to Base64) for the URL
const exported = await window.crypto.subtle.exportKey("raw", key);

加密(AES‑GCM)

AES‑GCM 同时提供机密性和完整性。每次加密操作都必须生成唯一的 IV。

async function encrypt(content, key) {
  const encoder = new TextEncoder();
  const encodedContent = encoder.encode(content);

  // 96‑bit IV for GCM
  const iv = window.crypto.getRandomValues(new Uint8Array(12));

  const encryptedContent = await window.crypto.subtle.encrypt(
    {
      name: "AES-GCM",
      iv: iv,
    },
    key,
    encodedContent
  );

  // Return serialized JSON with IV and ciphertext
  return JSON.stringify({
    iv: Array.from(iv),
    data: Array.from(new Uint8Array(encryptedContent)),
  });
}

URL 哈希技巧

当浏览器访问 example.com/page#secret123 时,服务器只会看到 GET /page# 之后的内容永远不会发送到服务器,这让我们可以安全地在 URL 中传递解密密钥。

// On the client (e.g., inside useEffect)
useEffect(() => {
  const hash = window.location.hash; // "#5f3a..."
  if (hash) {
    const keyString = hash.substring(1); // Remove '#'
    // Trigger decryption...
  }
}, []);

数据库与过期(Supabase)

由于服务器只存储加密后的二进制数据,Supabase 配合行级安全(RLS)负责存储。客户端实现“读取即焚毁”和过期机制:

  1. 获取: 读取加密记录。
  2. 检查过期:expires_at 与当前时间比较;如果已过期,则视为失效并删除该密文。
  3. 读取即焚毁: 若元数据标记为 “Burn on Read”,在成功获取后立即向 Supabase 发起删除请求。

教训

  • Hydration Errors: Next.js Server Components 没有 window。只能在 useEffect 或事件处理函数中调用 window.crypto
  • Encoding Hell:ArrayBufferUint8Array 与字符串之间相互转换非常繁琐。TextEncoderTextDecoder 是必不可少的工具。
  • Trust: 透明度对安全工具至关重要。该仓库从一开始就是开源的,因为我自己也不会使用闭源方案。

试用

我正在寻求对加密实现和用户体验的反馈。

  • Live Demo:
  • Repo: (感谢星标!)
Back to Blog

相关文章

阅读更多 »