Code
const b64 = (b) => btoa(String.fromCharCode(...new Uint8Array(b)));
const unb64 = (s) => Uint8Array.from(atob(s), (c) => c.charCodeAt(0));
const derive = async (pw, salt, iterations) => {
const key = await crypto.subtle.importKey('raw', new TextEncoder().encode(pw), 'PBKDF2', false, ['deriveBits']);
return crypto.subtle.deriveBits({ name: 'PBKDF2', hash: 'SHA-256', salt, iterations }, key, 256);
};
// Format: iter$salt$hash
const hashPassword = async (pw, iter = 600_000) => {
const salt = crypto.getRandomValues(new Uint8Array(16));
return `${iter}$${b64(salt)}$${b64(await derive(pw, salt, iter))}`;
};
const verifyPassword = async (pw, stored) => {
const [iter, salt, hash] = stored.split('$');
const a = new Uint8Array(await derive(pw, unb64(salt), +iter));
const b = unb64(hash);
// zeitkonstanter Vergleich
return a.length === b.length && a.reduce((d, x, i) => d | (x ^ b[i]), 0) === 0;
};
await hashPassword('geheim') // "600000$kJ3v...==$Qx8f...="
await verifyPassword('geheim', h) // true
await verifyPassword('falsch', h) // false