TL;DR
我從醫療資訊系統跨足做個人理財桌面應用 Kaizei。表面是換主題,實質是把同一套「資料隱私先行」的思維帶到不同領域。在這個過程中我具體實作了一套零知識加密 vault ── server 物理上無法解密使用者資料 ── 並理解這個架構的真實代價。
為什麼跨領域
很多人問我:「你已經有兩個醫療系統,為什麼還要去做個人理財?」
短答:因為我想驗證一個假設 ── 「資料隱私先行」是不是只在醫療場景有意義?
長答要回到我做 ExClinCalc 時的觀察。當我用 PostgreSQL RLS 把權限下放到資料庫層、寫了 那篇關於 RLS 的 blog,我意識到這個思維可以推廣:
不只醫療資料 ── 任何「server 不需要看到原始內容」的資料,都應該在 server 層級就保證它讀不到。
個人理財的 API key、交易紀錄、隱私備註 ── 都符合這個條件。
從 RLS 到零知識的差距
ExClinCalc 的 RLS 解的是「多角色之間互相隔離」 ── 醫師 A 看不到醫師 B 的病人。但 Supabase 自己的 service_role 仍能看所有資料。我相信 Supabase 不會亂看,但**「我必須相信 vendor」這個假設本身就是缺陷**。
零知識架構的目標是把這個假設拿掉:
RLS 模型(ExClinCalc):
使用者間隔離 ✅
Server / vendor 看得到全部 ⚠️
零知識模型(Kaizei):
使用者間隔離 ✅
Server 物理上無法解密 ✅
「物理上無法」這四個字是關鍵。不是 server 答應不看 ── 是 server 即使想看,沒有解密金鑰,也讀不出來。
怎麼實作
步驟 1:雙金鑰衍生
使用者輸入主密碼。透過 PBKDF2(310,000 iterations,OWASP 2023 建議下限)+ 兩組獨立 salt 衍生兩把金鑰:
const authKey = await PBKDF2(password, salt_auth, 310_000)
const encKey = await PBKDF2(password, salt_enc, 310_000)
兩把金鑰雖然來自同一密碼,但因為 salt 不同,互相無法推導。
步驟 2:authKey 上 server,encKey 留本機
// 上 server
await fetch('/auth/login', {
body: JSON.stringify({
email,
authKey, // 給 server 驗身份用
})
})
// encKey:永遠留在裝置 RAM 內,不寫盤、不送網路
Server 收到 authKey,再做 SHA-256(authKey + server pepper) 後存入 DB。即使 DB 整個被脫庫,攻擊者拿到的也只是 hash + pepper,要 brute force 主密碼幾乎不可能。
步驟 3:本機 AES-256-GCM 加密 vault
const ciphertext = await AES_GCM_encrypt(vaultData, encKey)
await fetch('/vault', {
method: 'PUT',
body: ciphertext // 送出去的是密文
})
Server 收到的是已加密的 binary blob。Server 看到的是 random bytes。沒有 encKey,server 即使想解也解不開。
步驟 4:取回時反過來
const ciphertext = await fetch('/vault').then(r => r.arrayBuffer())
const vaultData = await AES_GCM_decrypt(ciphertext, encKey)
// encKey 仍在本機 RAM 中
整個流程 server 只看過密文。
真實代價:「無後門」就是無後門
零知識架構聽起來很美,但代價是真實的:
代價 1:忘記主密碼 = 資料無法救回
我前述設計沒有「忘記密碼」恢復路徑。如果使用者忘記主密碼,Kaizei 的 server 也救不了他(server 沒有 decrypt key)。
對使用者教育要做足。Kaizei 在註冊時強制使用者:
- 設定強密碼
- 寫下備份(紙本 / 1Password)
- 「你忘記密碼 = 資料無法救回」要連續確認 3 次
代價 2:每次解鎖延遲 200–400ms
PBKDF2 310,000 iterations 在桌面端跑大約 200–400ms。這是設計如此 ── 越慢的 KDF 越難 brute force。
對使用者體驗的影響:開啟 app 後等 0.3 秒才能用。可接受,但要 UX 上表現「正在解鎖」而不是裝呆。
代價 3:跨機器同步衝突解決受限
Server 看不懂內容 → 看不懂哪個欄位被誰改了 → 沒辦法做欄位級 merge。只能整包覆蓋(last-write-wins)。
對使用者:A 機器加了一筆交易、B 機器加了另一筆,如果不及時 sync,可能會丟其中一筆。
我的對策:
- 每次寫入前先 fetch server 版本,做本機 merge 後再寫回
- 寫入時帶 optimistic lock(version number),衝突時 fail back
- 顯眼提示「有版本衝突,請手動處理」
代價 4:無法分析使用行為
Server 看不懂內容 → 沒辦法做「最常用功能 / 用戶旅程」之類分析。
對 Kaizei 我認為這代價值得付。對其他應用未必:消費級社交 / 內容平台靠分析做產品決策,零知識會讓它們失能。
跟「終端對終端加密」的差別
很多人會問:「這跟 Signal / WhatsApp 的終端對終端加密(E2EE)有什麼不一樣?」
兩者是相關但不同的概念:
| 概念 | 解決什麼 |
|---|---|
| 零知識(Zero Knowledge) | Server 不能讀內容(單一使用者場景) |
| 終端對終端加密(E2EE) | 任何中間節點不能讀內容(多使用者通訊場景) |
Kaizei 是單一使用者多裝置同步,所以零知識是對的模型。Signal 是兩個使用者通訊,需要 E2EE + Forward Secrecy + Diffie-Hellman 等更複雜協定。
我做的不是 E2EE。這個誠實要說清楚。
跨領域帶來的副效果
做完 Kaizei 後回頭看醫療作品,有幾個有意思的影響:
影響 1:對「server 知道太多」更敏感
Kaizei 之後我重新檢查 ClinCalc 的隱私架構。發現:
- ClinCalc 把使用者體檢數值傳給 Cloudflare Worker、Worker 再呼叫 Gemini API
- 雖然「規則先跑、LLM 只看結構化結果」已經降低風險
- 但 ClinCalc 的 Worker server 仍看到原始數值
有沒有可能 ClinCalc 也走零知識路線? ── 體檢數值在使用者瀏覽器加密後再上傳?技術上可行(Web Crypto),但會犧牲影像 OCR(影像必須交給 Gemini 解碼)。不是純好的折衷。
這個議題沒有完美解,但 Kaizei 讓我意識到:我把「server 必看到原始」當成預設了,而這個預設本身值得質疑。
影響 2:理解「規範強制」與「技術強制」的差別
醫療場域有 HIPAA、GDPR、台灣個資法強制資料保護 ── 但這是規範,不是技術。違反了會被罰,但 server 仍然技術上看得到。
零知識是技術強制。即使 server 想違反,也物理上做不到。這兩個層級的保護是疊加的,不是互相取代。
我做完 Kaizei 後在讀 ExClinCalc 的稽核機制:稽核能告訴你「誰看了什麼」,但無法防止「能看的人看了不該看的」。真正的隱私保護要把「能看到」這件事本身拿掉。
開放問題
問題 1:醫療系統能走零知識嗎?
部分可以。例如儲存的歷史記錄可以零知識化。但「即時診斷」需要 server 看數值才能算 ── 不能完全零知識。
或許答案是「分區零知識」:高頻寫入的歷史 vault 走零知識、即時計算用標準加密 + RLS。這是個值得設計的中間地帶。
問題 2:忘記密碼恢復可以怎麼做而不破壞零知識?
幾個方向:
- Shamir Secret Sharing 三選二恢復:把主密碼拆 3 份,2 份夠救
- 社交恢復(social recovery):指定 3 個朋友,2 個 attestation 後解鎖
- 硬體 token + 主密碼 雙因素:丟其中一個還活著
每個都有自己的安全 / UX 取捨。我目前的看法:純零知識的 Kaizei 留給高安全需求使用者,恢復機制是另一個產品階段的設計題。
給跨領域的人
跨領域不是換主題,是把同一套思維框架套到新場域驗證。我的「資料隱私先行」思維在醫療上是 RLS,在個人理財上是零知識加密。核心觀念相同,技術實現不同。
如果你也在跨領域中:
- 找你「離不開」的一條核心思維 ── 對我是隱私 / 安全。對你可能是性能 / 可解釋 / 可組合
- 把那個思維套到新場域 ── 看會發生什麼有趣事情
- 新場域回頭批判舊場域 ── Kaizei 之後我才看出 ClinCalc 預設的 server-trust 假設
跨領域的價值不在「會兩件事」,在「用一件事看另一件事」。
延伸閱讀
- Kaizei Case Study ── 完整零知識架構 + 桌面端深層防護
- 為什麼我選擇 PostgreSQL Row Level Security ── RLS 是醫療場景的隱私基礎
- Kaizei 官方網站 ── 含 PBKDF2 → AES-256-GCM 流程互動圖