Migration from SQL DB to PostgreSQL DB Password Verification Error

Trying to migrate users/accounts from my SQL DB (pre better-auth implementation) to a PostgreSQL DB (better-auth implementation). However, when hashing the passwords with Scrypt.NET and trying to login I get a
ts│# SERVER_ERROR: Error: hex string expected, got undefined^CTerminate batch job (Y/N)? Terminate batch job (Y/N)?
│ at hexToBytes (../src/utils.ts:164:37)
ts│# SERVER_ERROR: Error: hex string expected, got undefined^CTerminate batch job (Y/N)? Terminate batch job (Y/N)?
│ at hexToBytes (../src/utils.ts:164:37)
error. It seems like the verification of the password algorithm expects the password in a feasible manner. Any guides on how to do that? From better-auth code I can see:
export const verifyPassword = async ({
hash,
password,
}: { hash: string; password: string }) => {
const [salt, key] = hash.split(":");
const targetKey = await generateKey(password, salt!);
return constantTimeEqual(targetKey, hexToBytes(key));
};
export const verifyPassword = async ({
hash,
password,
}: { hash: string; password: string }) => {
const [salt, key] = hash.split(":");
const targetKey = await generateKey(password, salt!);
return constantTimeEqual(targetKey, hexToBytes(key));
};
The key is probably undefined since it splits the hash based on a : and my hash does not contain it. I get something like $s2$16384$8$1$J9a1G+QkkzGODG1ZixgwsLTe7Q5t4QCH2LEQFFISGKs=$BG546zv4T7hkLPo0FPeP1eXIzhyyjnpLyOkInM9oCMo= when hashing it via. the Scrypt.NET library.
Solution:
Implemented the functions like the following ```ts import { scrypt, randomBytes } from "crypto"; const scryptAsync = ( password: string | Buffer,...
Jump to solution
3 Replies
Timur
TimurOP2mo ago
Looks like I can tweak the hashing and verification by implementing custom functions https://www.better-auth.com/docs/authentication/email-password#configuration
Solution
Timur
Timur2mo ago
Implemented the functions like the following
import { scrypt, randomBytes } from "crypto";
const scryptAsync = (
password: string | Buffer,
salt: string | Buffer,
keylen: number,
options?: any
): Promise<Buffer> => {
return new Promise((resolve, reject) => {
scrypt(password, salt, keylen, options, (err, derivedKey) => {
if (err) reject(err);
else resolve(derivedKey);
});
});
};
export const hashPassword = async (password: string): Promise<string> => {
const salt = randomBytes(32);
const N = 16384;
const r = 8;
const p = 1;
const keylen = 32;
const derivedKey = await scryptAsync(password, salt, keylen, { N, r, p });
const saltB64 = salt.toString("base64");
const hashB64 = (derivedKey as Buffer).toString("base64");
return `$s2$${N}$${r}$${p}$${saltB64}$${hashB64}`;
};

export const verifyPassword = async ({
hash,
password,
}: {
hash: string;
password: string;
}): Promise<boolean> => {
const parts = hash.split("$");
if (parts.length !== 7 || parts[1] !== "s2") {
throw new Error("Invalid hash format");
}
const N = parseInt(parts[2] || "16384");
const r = parseInt(parts[3] || "8");
const p = parseInt(parts[4] || "1");
const saltStr = parts[5];
const hashStr = parts[6];

if (!saltStr || !hashStr) {
throw new Error("Invalid hash format: missing salt or hash");
}
const salt = Buffer.from(saltStr, "base64");
const storedHash = Buffer.from(hashStr, "base64");
const keylen = 32;

const derivedKey = await scryptAsync(password, salt, keylen, {
N,
r,
p,
});
const derivedKeyBuffer = derivedKey as Buffer;

if (derivedKeyBuffer.length !== storedHash.length) {
return false;
}
let result = 0;
for (let i = 0; i < derivedKeyBuffer.length; i++) {
result |= derivedKeyBuffer[i]! ^ storedHash[i]!;
}

const isValid = result === 0;

if (isValid) {
return true;
}

return false;
};
import { scrypt, randomBytes } from "crypto";
const scryptAsync = (
password: string | Buffer,
salt: string | Buffer,
keylen: number,
options?: any
): Promise<Buffer> => {
return new Promise((resolve, reject) => {
scrypt(password, salt, keylen, options, (err, derivedKey) => {
if (err) reject(err);
else resolve(derivedKey);
});
});
};
export const hashPassword = async (password: string): Promise<string> => {
const salt = randomBytes(32);
const N = 16384;
const r = 8;
const p = 1;
const keylen = 32;
const derivedKey = await scryptAsync(password, salt, keylen, { N, r, p });
const saltB64 = salt.toString("base64");
const hashB64 = (derivedKey as Buffer).toString("base64");
return `$s2$${N}$${r}$${p}$${saltB64}$${hashB64}`;
};

export const verifyPassword = async ({
hash,
password,
}: {
hash: string;
password: string;
}): Promise<boolean> => {
const parts = hash.split("$");
if (parts.length !== 7 || parts[1] !== "s2") {
throw new Error("Invalid hash format");
}
const N = parseInt(parts[2] || "16384");
const r = parseInt(parts[3] || "8");
const p = parseInt(parts[4] || "1");
const saltStr = parts[5];
const hashStr = parts[6];

if (!saltStr || !hashStr) {
throw new Error("Invalid hash format: missing salt or hash");
}
const salt = Buffer.from(saltStr, "base64");
const storedHash = Buffer.from(hashStr, "base64");
const keylen = 32;

const derivedKey = await scryptAsync(password, salt, keylen, {
N,
r,
p,
});
const derivedKeyBuffer = derivedKey as Buffer;

if (derivedKeyBuffer.length !== storedHash.length) {
return false;
}
let result = 0;
for (let i = 0; i < derivedKeyBuffer.length; i++) {
result |= derivedKeyBuffer[i]! ^ storedHash[i]!;
}

const isValid = result === 0;

if (isValid) {
return true;
}

return false;
};
Timur
TimurOP2mo ago
And now it works

Did you find this page helpful?