Skip to content

Commit 6bd2515

Browse files
committed
fix(pwa): reject malformed password hashes
1 parent dfcb8c1 commit 6bd2515

2 files changed

Lines changed: 28 additions & 1 deletion

File tree

apps/pwa/src/lib/crypto.mjs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,14 @@ export function hashPassword(password) {
1111

1212
export function verifyPassword(password, stored) {
1313
if (!stored || !stored.startsWith("scrypt$")) return false;
14-
const [, saltHex, hashHex] = stored.split("$");
14+
const parts = stored.split("$");
15+
if (parts.length !== 3) return false;
16+
const [, saltHex, hashHex] = parts;
17+
if (!/^[0-9a-f]+$/i.test(saltHex) || !/^[0-9a-f]+$/i.test(hashHex)) return false;
18+
if (saltHex.length % 2 !== 0 || hashHex.length % 2 !== 0) return false;
1519
const salt = Buffer.from(saltHex, "hex");
1620
const expected = Buffer.from(hashHex, "hex");
21+
if (salt.length === 0 || expected.length === 0) return false;
1722
const dk = crypto.scryptSync(String(password), salt, expected.length);
1823
return dk.length === expected.length && crypto.timingSafeEqual(dk, expected);
1924
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import assert from "node:assert/strict";
2+
import test from "node:test";
3+
import { hashPassword, verifyPassword } from "../src/lib/crypto.mjs";
4+
5+
test("verifyPassword accepts a matching scrypt password hash", () => {
6+
const stored = hashPassword("correct horse");
7+
8+
assert.equal(verifyPassword("correct horse", stored), true);
9+
assert.equal(verifyPassword("wrong horse", stored), false);
10+
});
11+
12+
test("verifyPassword rejects malformed scrypt hashes", () => {
13+
for (const stored of [
14+
"scrypt$00$",
15+
"scrypt$00$nothex",
16+
"scrypt$zz$aa",
17+
"scrypt$0$aa",
18+
"scrypt$00$aa$extra",
19+
]) {
20+
assert.equal(verifyPassword("anything", stored), false, stored);
21+
}
22+
});

0 commit comments

Comments
 (0)