馃 distributed transcription service
thistle.dunkirk.sh
1/**
2 * Client-side password hashing using PBKDF2.
3 * Uses aggressive iteration count to waste client CPU instead of server CPU.
4 * Server will apply lightweight Argon2 on top for storage.
5 */
6
7const ITERATIONS = 1_000_000; // ~1-2 seconds on modern devices
8
9/**
10 * Hash password client-side using PBKDF2.
11 * @param password - Plaintext password
12 * @param email - Email address (used as salt)
13 * @returns Hex-encoded hash
14 */
15export async function hashPasswordClient(
16 password: string,
17 email: string,
18): Promise<string> {
19 const encoder = new TextEncoder();
20
21 // Import password as key
22 const keyMaterial = await crypto.subtle.importKey(
23 "raw",
24 encoder.encode(password),
25 { name: "PBKDF2" },
26 false,
27 ["deriveBits"],
28 );
29
30 // Use email as salt (deterministic, unique per user)
31 const salt = encoder.encode(email.toLowerCase());
32
33 // Derive 256 bits using PBKDF2
34 const hashBuffer = await crypto.subtle.deriveBits(
35 {
36 name: "PBKDF2",
37 salt,
38 iterations: ITERATIONS,
39 hash: "SHA-256",
40 },
41 keyMaterial,
42 256, // 256 bits = 32 bytes
43 );
44
45 // Convert to hex string
46 const hashArray = Array.from(new Uint8Array(hashBuffer));
47 return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
48}