在 JavaScript 中,公私钥的保存位置取决于生成方式和用途。

1. 使用 Web Crypto API 生成密钥

        
const keyPair = await crypto.subtle.generateKey(
  {
    name: "RSA-PSS",
    modulusLength: 2048,
    publicExponent: new Uint8Array([1, 0, 1]),
    hash: "SHA-256"
  },
  false, // 私钥不可导出
  ["sign", "verify"]
);
        
      

默认情况

  • privateKey:只存在于当前页面内存中的 CryptoKey 对象里

  • publicKey:也存在内存中,可导出后发送给服务器

  • 页面刷新或关闭后:内存中的密钥会丢失

        
const { privateKey, publicKey } = keyPair;
        
      

2. 持久化保存到 IndexedDB

CryptoKey 可以保存到 IndexedDB:

        
const dbRequest = indexedDB.open("keyDB", 1);

dbRequest.onupgradeneeded = () => {
  dbRequest.result.createObjectStore("keys");
};

dbRequest.onsuccess = () => {
  const db = dbRequest.result;
  const tx = db.transaction("keys", "readwrite");

  tx.objectStore("keys").put(keyPair.privateKey, "privateKey");
  tx.objectStore("keys").put(keyPair.publicKey, "publicKey");
};
        
      

一般建议:

  • 私钥保存为不可导出密钥:extractable: false

  • 私钥可保存到 IndexedDB,但只能被同源页面使用

  • 不要把私钥直接转成字符串放进 localStorage

  • 不要把私钥放进 Cookie、URL 或发送到服务器

3. 导出为 JWK 或 PEM

如果生成密钥时允许导出:

        
const jwk = await crypto.subtle.exportKey("jwk", privateKey);
        
      

此时私钥可能被保存到:

  • localStorage

  • sessionStorage

  • IndexedDB

  • 浏览器扩展存储

  • 服务器端

但不建议把私钥保存到 localStorage,因为一旦发生 XSS,脚本可能直接读取它。

4. 如果使用 WebAuthn / Passkey

WebAuthn 的私钥通常保存在:

  • 手机或电脑的安全硬件

  • 操作系统凭据管理器

  • 浏览器关联的 Passkey 存储

  • 外部安全密钥

JavaScript 无法直接读取 WebAuthn 私钥。JS 只能调用:

        
navigator.credentials.create()
navigator.credentials.get()
        
      

服务器保存的是公钥和凭据 ID。

// 创建凭据(注册)
const credential = await navigator.credentials.create({
  publicKey: {
    challenge: crypto.getRandomValues(new Uint8Array(32)),
    rp: { name: "示例网站", id: location.hostname },
    user: {
      id: new TextEncoder().encode("user-123"),
      name: "alice@example.com",
      displayName: "Alice"
    },
    pubKeyCredParams: [
      { type: "public-key", alg: -7 },   // ES256
      { type: "public-key", alg: -257 }  // RS256
    ],
    authenticatorSelection: {
      userVerification: "preferred"
    },
    timeout: 60000,
    attestation: "none"
  }
});

console.log(credential);
// 获取凭据(登录)
const credential = await navigator.credentials.get({
  publicKey: {
    challenge: crypto.getRandomValues(new Uint8Array(32)),
    rpId: location.hostname,
    userVerification: "preferred",
    timeout: 60000
  }
});

console.log(credential);

总结

场景

私钥保存位置

JS 变量

当前页面内存

Web Crypto + IndexedDB

浏览器 IndexedDB

WebAuthn / Passkey

系统或硬件安全存储,JS 不可读取

导出为 JWK/PEM

由应用自行保存,风险较高

公钥

通常发送并保存到服务器

如果是浏览器端登录认证,优先考虑 WebAuthn/Passkey,不要在前端自行保存可导出的私钥。