PCH-02-005-legacy-pbkdf2-doc.patch.md
PCH-02-005 — Document the legacy PBKDF2 import KDF + recommend re-export
Severity: Informational
Target files: src/shared/backup.ts:146 (derivePeachExportKey),
src/popup/views/Import.tsx (re-export notice)
Report: peach-code-review.md, Finding PCH-02-005
Correction to the fix brief
The brief's comment text says "100,000 iterations". The code (and the report)
say 250,000 (backup.ts:166). The comment below uses the value the code
actually enforces — a comment that misstates its own parameters is worse than
no comment.
Edit 1 — KDF comment at backup.ts:146
// BEFORE (backup.ts:146-150)
export async function derivePeachExportKey(
password: string,
salt: Uint8Array,
opts: { raw?: boolean } = {}
): Promise<Uint8Array> {
// AFTER
/**
* LEGACY IMPORT KDF (PCH-02-005):
* This path derives with PBKDF2-SHA256 (250,000 iterations) for backwards
* compatibility with `.json` exports sealed before the Argon2id migration —
* the encrypted-JSON export UI was removed in v2.5.0 (see header), so this
* KDF is import-only (plus the test fixture generator above).
*
* PBKDF2 has no memory-hardness and is materially weaker than the current
* Argon2id envelope (m=64 MiB, t=3, p=4 — peachPayload.ts) against GPU/ASIC
* attackers at equal wall-clock cost. Do NOT use this helper for any new
* format. Users importing an old `.json` backup are prompted to export a
* fresh backup afterwards (Import.tsx legacy-KDF notice).
*/
export async function derivePeachExportKey(
password: string,
salt: Uint8Array,
opts: { raw?: boolean } = {}
): Promise<Uint8Array> {
(The existing LV-1 NFC comment inside the function body stays as is.)
Edit 2 — post-import re-export notice in Import.tsx
Verified: there is currently no user-facing recommendation after a
successful PBKDF2 (peach-json) import. The import success path keeps the
view mounted and already surfaces a post-import duplicateWarning notice
(Import.tsx:790) — the legacy-KDF notice mirrors that exact mechanism.
2a. Add a signal next to duplicateWarning (Import.tsx:~425):
// AFTER the existing line:
const [duplicateWarning, setDuplicateWarning] = createSignal<number>(0);
// ADD:
// PCH-02-005: set when a legacy PBKDF2-encrypted `.json` backup imports
// successfully — prompts the user to re-export under the Argon2id format.
const [legacyKdfNotice, setLegacyKdfNotice] = createSignal(false);
2b. In handleImport, alongside setDuplicateWarning(dupes) (the
success tail, Import.tsx:~604):
// BEFORE
setDuplicateWarning(dupes);
await props.onImport();
// AFTER
setDuplicateWarning(dupes);
// PCH-02-005: "peach-json" is the legacy PBKDF2-SHA256 envelope; every
// other recognised format is plaintext-source (no KDF) or current-format.
setLegacyKdfNotice(currentPreview.format === "peach-json");
await props.onImport();
Also reset it where the other notices reset (handleFileSelect, alongside
setError(null) / setAwaitingReplaceConfirm(false)):
setLegacyKdfNotice(false);
2c. Render next to the duplicate-warning block (Import.tsx:~790, same
<Show> pattern and styling class as the sibling notice):
<Show when={legacyKdfNotice() && !importing()}>
<div class="peach-settings-group">
<div class="peach-restore-warning">
This backup was created with Peach's older encryption format
(PBKDF2). It imported fine — but we recommend exporting a fresh
backup now so your paper/file backups use the current, stronger
format.
</div>
</div>
</Show>
(Class choice: peach-restore-warning is the existing soft-warning surface
used three blocks above; no new CSS.)
Tests
UI-notice coverage belongs in src/popup/views/contracts.test.tsx style; the
notice is a pure render-state change. Suggested single contract case (optional,
not counted as a security regression test): mock parseImportSource to return
format: "peach-json", drive handleImport, assert the notice text renders.
The KDF comment itself needs no test.
Save message (integration)
fix: PCH-02-005 document legacy PBKDF2 import path + recommend re-export after legacy import