# 策展 · X (Twitter) 🔥

> 作者：Daniel Nguyen (@daniel_nguyenx) · 平台：X (Twitter) · 日期：2026-03-26

> 原始來源：https://x.com/daniel_nguyenx/status/2037112278336872783

## 中文摘要

# 我找到了在網路上保護 API keys 的完美方法

我最近將 BoltAI 移植到網路平台，並立即遇到一個技術挑戰：如何安全地儲存 API keys？

如果您不熟悉我的工作，BoltAI 是一個 BYOK (bring-your-own-key) 的 AI 聊天應用程式。在尊重使用者隱私並維持良好 UX 的同時，安全地儲存使用者的 API keys 至關重要。

對於原生應用程式 (mac/行動裝置) 來說，這相當直接，但對於網路應用程式來說，卻出乎意料地具有挑戰性。

在這篇短文中，我將分享我最終採用的方法，以及為什麼我認為它是目前最佳的實用解決方案。

> TL;DR：我使用 WebAuthn passkeys 作為本地解密閘門，而不是作為登入方法。這為 BoltAI 提供了一個實用的 BYOK 模型，其中加密 key 留在使用者的裝置上，並且請求直接發送給 AI 供應商。

## 問題

對於像 BoltAI 這樣的 BYOK AI 聊天客戶端，安全地儲存使用者的 API keys 至關重要，同時要尊重您的隱私並維持良好的 UX。這表示：

- 沒有代理伺服器。客戶端必須與 AI 供應商的推論伺服器保持直接連線。

- 這些機密只能由 BoltAI 應用程式存取。任何來自其他應用程式的嘗試都會立即觸發管理員密碼對話框。

- 良好的 UX。使用者不必每次都重新輸入機密。

## 解決方案

對於原生應用程式來說，這相當直接。我們可以將機密儲存在 Apple 的 Keychain (Mac/iOS) 或 Android 的 KeyStore 系統 (Android 應用程式) 中。

我使用使用者的密碼短語加密他們的 API keys，並將 DEK (data encryption key) 儲存在安全儲存中。這是一個既安全又方便的絕佳設定。由於 keys 是端對端加密的，因此將加密資料同步到雲端及其他裝置是安全的。

![](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/1774543457289-iaHEUSgySbYAEVTOFjpg.jpg)

在網路上，這就不那麼簡單了。沒有等效的安全儲存。讓我們探討一下選項：

1.  最佳 UX。我們將 API keys 解密並以 plaintext 形式儲存在 local storage 中。這不是很安全，但非常方便，因為使用者不必在每次聊天會話中重新輸入他們的密碼短語。

2.  最安全。在每次聊天會話中，我們提示使用者輸入密碼短語，解密 API keys 並僅儲存在記憶中。安全，但可能相當惱人。

在我看來，完美的解決方案應該介於兩者之間：既安全又方便使用者。但這樣的解決方案存在嗎？

是的！WebAuthn 來拯救了。

Passkey/WebAuthn 通常用於身份驗證，並且需要後端。但在這種情況下，我們只需要使用 WebAuthn PRF 擴充功能作為本地密碼學原語：它衍生出一個與 credential 綁定的 secret，BoltAI for Web 使用它來解鎖加密的 API keys，而無需將 passkey 視為登入方法。

## 運作方式

這是心智模型。

![](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/1774543457280-iaHEU77ztbYAA6Axsjpg.jpg)

在網路上，BoltAI 儲存一個加密的 envelope，其中包含：

-   加密的 API key payload

-   一個基於密碼短語的 DEK wrap

-   (可選) 一個基於 passkey(PRF) 的 DEK wrap

伺服器只看到 ciphertext。unwrap keys 保持在本地。

然後，當 BoltAI 需要使用 API key 時：

```
                           unlock time
                           -----------

load encrypted envelope
        |
        +--> try passkey PRF wrap
        |       |
        |       +--> WebAuthn returns credential-bound secret
        |       +--> recover DEK
        |
        +--> else ask for passphrase
                |
                +--> derive key with scrypt
                +--> recover DEK
```

DEK → 在記憶中解密 API key → 直接向供應商發送請求

## 程式碼片段

這是 WebAuthn 註冊流程的簡化版本。

```typescript
const prfSalt = await randomBytes(32);

const credential = await navigator.credentials.create({
  publicKey: {
    challenge: toArrayBuffer(await randomBytes(32)),
    rp: {
      name: "BoltAI",
      id: location.hostname,
    },
    user: {
      id: toArrayBuffer(stringToBytes(`boltai:${userId}`)),
      name: `boltai-local-unlock-${userId}`,
      displayName: "BoltAI Passkey Unlock",
    },
    userVerification: "required",
    extensions: {
      prf: {
        eval: {
          first: toArrayBuffer(prfSalt),
        },
      },
    },
  },
});

const result = credential.getClientExtensionResults?.();
const passkeyKey = new Uint8Array(result?.prf?.results?.first!);

```

該 passkeyKey 並非直接用於加密 API key payload。相反地，它 wrap 了 DEK：

```typescript
const dek = await randomKey();

const encryptedPayload = encryptAESGCM(
  dek,
  payloadNonce,
  stringToBytes(apiKey),
  AAD.PAYLOAD
);

const passWrap = await buildPassWrap(dek, passphrase);

const passkeyWrappedDEK = encryptAESGCM(
  passkeyKey,
  passkeyNonce,
  dek,
  AAD.WRAP_PASSKEY
);

envelope.wraps = {
  pass: passWrap,
  passkey: {
    credentialId,
    prfSalt,
    nonce: bytesToBase64url(passkeyNonce),
    ct: bytesToBase64url(passkeyWrappedDEK),
  },
};

```

當解鎖時，BoltAI 會依序嘗試可用的 unwrap 路徑：

```typescript
let dek: Uint8Array | null = null;

// try passkey PRF output first
if (activePasskeyWrap && envelope.wraps.passkey) {
  dek = decryptAESGCM(
    activePasskeyWrap.key,
    passkeyNonce,
    passkeyCiphertext,
    AAD.WRAP_PASSKEY
  );
}

// fall back to the passphrase
if (!dek && passphrase) {
  dek = decryptPassphraseWrap(envelope, passphrase);
}

if (!dek) {
  throw new Error("Failed to unwrap DEK");
}

```

結果就是我想要的完美 UX：

-   使用者不必在每個會話中輸入他們的密碼短語

-   BoltAI 仍然直接與 AI 供應商通訊

-   我的伺服器永遠不會成為機密代理

-   密碼短語仍然是備用/復原路徑

我對這個解決方案非常滿意。

如果您是 BoltAI 客戶，請在 https://chat.boltai.com 試用 BoltAI 網路測試版。

祝您開發愉快 ✌️

## 標籤

教學資源, 資安, Web, BoltAI
