Staging — 僅供部落格預覽。
跳至主要內容

自動鎖定是記憶體衛生問題

排程中

閱讀時間 7 分鐘 作者 NT²

若解密後的金鑰控制代碼活得比已解鎖工作階段還久,靜止加密就不完整。NT² 把自動鎖定 當成記憶體衛生:不可匯出的 CryptoKey、只存在記憶體、在閒置、重新整理與關閉分頁時清除。

自動鎖定是記憶體衛生問題

主張:已解鎖的保管箱是暫時例外

NT² Vault 在裝置上加密結構化項目。這句話正確,但不完整。

磁碟上的密文只在保管箱鎖定時保護你。解鎖期間,產品握有可即時使用的密碼學控制代碼:能解開項目金鑰、解密內容,並在啟用雲端功能時,解開用來證明保管箱身分的簽署材料。這個控制代碼比畫面上任何單一密碼欄位都更敏感。若它能熬過重新整理、關閉的分頁,或咖啡館裡安靜的五分鐘,「本機優先加密」就變成「瀏覽器行程裡黏住的祕密」。

因此主張很明確:自動鎖定不是便利計時器。它是記憶體衛生。

保管箱 AES 金鑰——以及依賴已解鎖工作階段的其他執行時期金鑰——必須以不可匯出(non-extractable)的 CryptoKey 物件,存在應用程式的記憶體狀態中。它們不可寫入 localStorage、IndexedDB、service worker 快取或 URL 片段。強制重新整理後必須是 null。關閉分頁必須結束工作階段。大約五分鐘沒有指標、鍵盤或觸控活動,必須清除同一份狀態。解鎖是刻意的例外;鎖定再次成為預設。

這聽起來理所當然——直到你注意到有多少網頁應用把「記住我」與「還原工作階段」當成產品美德。對密碼保管箱而言,那些美德就是攻擊面。

限制:瀏覽器不善於保管祕密,而 XSS 會讀記憶體

三道限制迫使我們這樣設計。

第一,Web Crypto 給你的是控制代碼,不是可攜的金鑰位元組——除非你要求可匯出金鑰。 Web Cryptography API 讓應用呼叫 crypto.subtle.deriveKeyimportKey,取得 CryptoKey。若 extractabletrue,後續程式可呼叫 exportKey,把原始金鑰材料複製成 JavaScript 字串或 typed array。若 extractablefalse,瀏覽器拒絕匯出。能觸及頁面的惡意腳本仍可在金鑰尚在記憶體時使用它——XSS 仍是嚴重缺陷——但它無法輕易把 AES 金鑰傾印成位元組,以便外流或離線重用。不可匯出金鑰縮小爆炸半徑。它們並不寬恕第三方腳本、分析 SDK,或「就這一筆除錯日誌」。

第二,任何跨導覽持久化的東西,都會活得比使用者「要解鎖」的意圖更久。 Service worker、磁碟型儲存,以及「還原先前工作階段」路徑對購物車很棒,對保管箱金鑰很糟。重新整理不是解鎖儀式。它是新文件載入。若金鑰在沒有主密碼(或等價的本機解鎖路徑)的情況下重新出現,產品就發明了靜默再進入。能重啟頁面的攻擊者——或以為鎖定就是鎖定、卻走開的使用者——繼承了這項發明。

第三,閒置時間就是實體暴露時間。 共享辦公室裡開著的保管箱筆電、朝上放置的手機,或作業系統未鎖定就休眠的筆電,仍有已解鎖的瀏覽器脈絡。若下一個碰觸觸控板的人仍能使用金鑰控制代碼,密碼學幫不上忙。閒置清除不是偏執表演。它限制「我走開一下」還能等於「任何人都能讀保管箱」的時間長度。

這些限制與熟悉的 SPA 直覺衝突:把昂貴的衍生狀態保持溫熱,好讓 UI 感覺即時。推導或重建保管箱金鑰是刻意的工作。跨重新載入快取結果感覺像效能。對 NT² 而言,那種快取會是掛著 UX 故事的安全缺陷。

零知識行銷常停在「伺服器永遠看不到密碼」。那是必要條件,但不充分。密碼一開始就不可離開瀏覽器,而衍生金鑰在解鎖後也不可變成耐用的瀏覽器祕密。記憶體衛生是同一承諾的客戶端半邊。

設計:不可匯出金鑰、Svelte 記憶體、明確清除路徑

NT² Vault 的工作階段模型刻意狹窄。

解鎖在裝置上執行。主密碼在本機輸入。金鑰推導與保管箱金鑰重建使用頁面中的 Web Crypto。得到的保管箱 AES 金鑰以 extractable: false 建立或匯入。控制代碼存在全域 Svelte 記憶體狀態中,供已解鎖工作階段使用——連同 UI 需要的作用中保管箱身分——而不是耐用儲存。解密後的項目內容只在你檢視或編輯時存在元件記憶體。它們不會以明文寫回。

鎖定是反向操作:丟掉 CryptoKey 參照(設為 null)、清除相關的已解開工作階段材料,並讓 UI 回到鎖定表面。磁碟上的密文仍是密文。若還需要例外,再解鎖一次。

觸發條件刻意無聊:

事件工作階段金鑰
明確鎖定清成 null
約 5 分鐘無輸入清成 null
強制重新整理/完整導離隨文件消失;狀態從空白開始
關閉分頁/beforeunload清除,使金鑰不比分頁活得更久

活動監聽器(指標、鍵盤、點擊、觸控)在你工作時刷新閒置計時器。沉默則否。整套政策就是:使用延續例外;靜止結束例外。

flowchart TD
  Locked[已鎖定:CryptoKey 為 null]
  Unlock[在裝置上解鎖]
  Mem[記憶體內不可匯出的 CryptoKey]
  Idle[閒置約 5 分鐘]
  Refresh[重新整理或關閉分頁]
  LockBtn[使用者鎖定]

  Locked --> Unlock
  Unlock --> Mem
  Mem --> Idle
  Mem --> Refresh
  Mem --> LockBtn
  Idle --> Locked
  Refresh --> Locked
  LockBtn --> Locked

圖解之外,還有兩個同樣重要的性質。

依建構即不可匯出。 執行時期保管箱金鑰的推導或匯入方式,讓瀏覽器無法匯出它們。我們不為日誌、遙測或「臨時」卻不知何故上線的除錯掛勾,另存一份「方便」的可匯出複本。若開發者需要檢查密文格式,他們處理密文——而不是匯出的 AES 金鑰位元組。

重新整理意味著 null。 重新載入後,沒有從磁碟靜默還原保管箱金鑰。本機保管箱設定檔仍保存再次解鎖所需的鹽值、包裝材料與驗證器——這正是本機優先解鎖不必詢問伺服器的方式——但重建活的 CryptoKey 必須走解鎖路徑。重新載入不是繞過主密碼的捷徑。

這與堆疊其餘部分搭配,卻不假裝 Worker 或 OPFS 會「握住工作階段金鑰」。SQLite 與附件 blob 存放密文。UI 執行緒(以及擁有解鎖的工作階段狀態)握住金鑰控制代碼。控制代碼消失後,Worker 仍可開啟檔案;若應用程式已不再持有金鑰,它無法把信封變成明文。

取捨:每一次暫停邊緣上的摩擦

記憶體衛生要付出舒適成本。

上下文切換六分鐘卻讓保管箱開著的使用者會被鎖定。編輯中途重新整理的使用者要再解鎖。多分頁工作流程仍需要擁有寫入權的分頁裡有一段已解鎖的 Writer 工作階段;關閉那個分頁結束的不只是某個檢視。想要「瀏覽器關掉前一直保持解鎖」的進階使用者,會覺得產品很固執。

我們接受這份摩擦。

換來的是符合人們談論「鎖定」方式的工作階段:保管箱鎖定時,活的金鑰就沒了。我們不販售把 AES 材料重新水合的「記住此裝置三十天」cookie。我們不把金鑰位元組藏在資料庫旁的 OPFS 裡「好讓下次解鎖更快」。跳過密碼的更快解鎖,只是未經同意的解鎖。

有一個誠實界限要說清楚。不可匯出金鑰與閒置清除降低風險;它們並未在每個瀏覽器裡發明安全飛地。解鎖期間的 XSS 仍可用活的控制代碼呼叫 Web Crypto。解鎖期間的實體存取仍可讀螢幕。JavaScript 執行環境無法像硬體模組那樣提供密碼學抹除保證。自動鎖定縮短窗口。它並不刪除對已鎖定作業系統、謹慎擴充功能,以及不與外洩資料庫重複使用之主密碼的需求。

團隊有時嘗試更軟的中間路線:記憶體中的可匯出金鑰,或「關閉分頁就清」的 sessionStorage 金鑰。sessionStorage 仍是同源腳本可見的字串形祕密存放處。可匯出金鑰邀請匯出。兩種模式都為便利優化,並在有人把金鑰貼進日誌或支援包的第一次就通不過衛生測驗。

我們拒絕什麼

把拒絕寫清楚時,架構更清楚。

我們拒絕在已解鎖工作階段使用可匯出的保管箱 AES 金鑰。 若瀏覽器能匯出金鑰,與頁面共用 JavaScript 世界的任何東西也能。

我們拒絕跨重新整理持久化活的金鑰控制代碼。 耐用儲存保存密文與解鎖原料——不是可靜默重新開啟保管箱、可即用的 CryptoKey 替代品。

我們拒絕「PWA 已安裝就永遠保持解鎖」。 安裝不等於持續信任。PWA 仍是有記憶體生命週期的文件脈絡。

我們拒絕在保管箱來源放入會擴大 XSS 爆炸半徑的第三方腳本。 不可匯出金鑰是一項緩解;把分析與小工具 CDN 送進已解鎖保管箱,就是在打敗它。

我們拒絕把自動鎖定當成可選的外觀。 閒置清除、離開時鎖定、重新整理後為 null,是工作階段政策,不是給「偏好安全」之人的設定新奇物。

這些拒絕是邊緣拒絕密碼神諭的客戶端孿生。伺服器不可測試猜密碼;瀏覽器也不可在使用者停止使用保管箱後仍保留解密金鑰。

金鑰不應比工作階段活得更久

本機優先保管箱仍需要普通的產品美德:感覺刻意的解鎖、感覺終結的鎖定,以及不會靜默再進入的頁面重新載入。那些美德不是來自祈願瀏覽器變成硬體錢包。它們來自不可匯出的 CryptoKey 控制代碼、記憶體內工作階段狀態,以及把那些控制代碼設回 null 的清除路徑。

這與把 KDF 鹽值留在裝置上、區分本機解鎖與雲端驗證、拒絕提供者重設密碼、用 PWA 承載沉重保管箱而非託管伺服器,以及把 SQLite 移出 UI 執行緒好讓解鎖期間的工作階段 UI 保持回應、儲存保持誠實——是同一道邊界故事。鹽值放置、解鎖儀式與記憶體衛生是一套架構——不是三項可選升級。

解鎖原料放在哪裡,請讀KDF 鹽值留在你的裝置上。為什麼開啟保管箱不是「登入雲端」,見解鎖本機保管箱,不等於登入雲端。當我們無法重設你的密碼時隨之而來的復原誠實,見無法重設密碼,是刻意的設計。圍繞客戶端握有工作階段的產品形態,見為什麼選擇 PWA 本地優先、零伺服器的 Vault。解鎖期間儲存工作如何遠離幀迴圈,見SQLite 放在 Web Worker 裡,不是選配

若這種工作階段模型符合你想要的私人保管箱運作方式,可試用 NT² Vault,或到 nt2.me 了解更多。

最後更新 2026-09-19

相關故事