Skip to content

perf(core): cache unlock-time master key to skip per-save Argon2id (#397) - #412

Open
mashfromband wants to merge 1 commit into
mainfrom
worktree-agent-a635ab42a3103cbbe
Open

mashfromband wants to merge 1 commit into
mainfrom
worktree-agent-a635ab42a3103cbbe

Conversation

@mashfromband

Copy link
Copy Markdown
Contributor

概要 / Closes #397

PasswordBox::save は毎 mutation ごとに Argon2id フル導出(KDF_MEMORY_KIB=65536=64MiB, time_cost=3, parallelism=4、実測数百 ms 級)を実行していた。既にアンロック済み(平文 payload がメモリ常駐)状態でのセーブなので、鍵再導出のセキュリティ利得は乏しく、1 操作あたり数百 ms のブロッキング UX コストだけが残っていた。

アンロック中に導出済みの MasterKey を PasswordBox にキャッシュし、save で再利用する。

実装

  • PasswordBox に cached_key: Option<CachedMasterKey> を追加。CachedMasterKey は鍵と導出に用いた KDF 入力(salt / memory_kib / time_cost / parallelism)をタグ付けして保持する。
  • open(): ヘッダの(crypto-agility を尊重した)パラメータで導出した鍵をそのパラメータでタグ付けしてキャッシュ。
  • create(): 初回 save が導出+キャッシュ。
  • save(): master_key_for_save が「キャッシュのタグがこれから書き込むヘッダのパラメータ(現行デフォルト)+ salt と一致する場合のみ再利用」。不一致なら再導出しキャッシュを更新。
  • lock(): キャッシュを None に(MasterKey は ZeroizeOnDrop なのでドロップ=ゼロ化)。

セキュリティ判断(鍵をメモリ保持する是非)

  • 追加露出は限定的: アンロック中は既に平文 payload(全パスワード)がメモリに存在する。同じ生存期間で導出済み鍵を保持しても攻撃面は実質増えない。
  • ゼロ化厳守(fix(core): PasswordBox の平文シークレットが Drop 時にゼロ化されない(lock() のみ・#313 の drop パス残件) #391 と整合): MasterKey は Zeroize + ZeroizeOnDrop。lock() でキャッシュを破棄(ゼロ化)、PasswordBox の Drop でも鍵フィールドがドロップされゼロ化。再導出時の旧鍵も Option 置換で即ドロップ=ゼロ化。
  • crypto-agility を破壊しない: save は常に現行デフォルトのヘッダを書き、そのパラメータで導出した鍵で暗号化する必要がある。旧パラメータのファイルを開いた箱はキャッシュのタグが不一致になり、再導出(=再キー) されるため、オンディスクのヘッダと暗号文が乖離しない。次回以降の save は fast-path に戻る。
  • オンディスク形式・FILE_VERSION に不変: キャッシュは純粋にメモリ内最適化。ファイルフォーマットへの影響なし。
  • 再ロック後の再導出: lock() でキャッシュ破棄 → 次の open() で必ず再導出。

テスト(cargo test -p passshare-core green / 全 53 tests)

  • open_caches_key_matching_fresh_derivation: キャッシュ鍵が新規導出とバイト一致(再利用が導出と等価)。
  • save_reuses_cached_key_verbatim: キャッシュを別パスワード由来の鍵で汚染 → save が引数を無視してキャッシュ鍵で暗号化することを証明(= 実際にキャッシュ参照している)。
  • save_rederives_when_cached_params_mismatch: パラメータ不一致タグ時は再導出され、正しいパスワードで復号可能・キャッシュがデフォルトへ更新(crypto-agility 再キー)。
  • repeated_saves_stay_decryptable: 連続 save(fast-path)でも毎回再オープン復号可能(ヘッダと暗号文が乖離しない)。
  • lock_clears_cached_master_key: lock() でキャッシュがゼロ化・破棄される。

注記

現状コードベースにマスターパスワード変更フローは存在せず、save の全呼び出し元はアンロック時と同一パスワードを渡す。将来パスワード変更 API を追加する場合は変更時に再導出(キャッシュ無効化)が必要である旨をコメントに明記済み。

PasswordBox::save re-ran the deliberately-heavy Argon2id KDF (64 MiB /
t=3 / p=4, hundreds of ms) on every mutation even though the box is
already unlocked and the plaintext payload is resident in memory. Retain
the derived MasterKey while unlocked and reuse it on save.

The cache is tagged with the exact KDF inputs it was derived from. save
always writes the current default params to the header, so it reuses the
cached key only when the tags match those defaults + salt; otherwise it
re-derives (first save after create, or a box opened with older cost
params being re-keyed for crypto-agility) and refreshes the cache. The
cache never affects the on-disk format or FILE_VERSION.

Security: the MasterKey is ZeroizeOnDrop; lock() drops the cache
(zeroizing it) and Drop tears it down, consistent with #391's secret
scrubbing. Holding the key while unlocked adds no meaningful exposure
since the decrypted payload already lives in memory during that window.

Closes #397
@vercel

vercel Bot commented Jul 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pass-share-frontend Ready Ready Preview, Comment Jul 11, 2026 4:29pm

Request Review

This branch was previously deployed

1 inactive deployment
Preview — 895ec716 Deployed Jul 11, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf(core): マスター鍵を毎 save で Argon2id 再導出(アンロック中はキャッシュ可能)

1 participant