Hashing script to seed database

The palace project - the cases forI am trying to create a python script that lets me seed my database with dummy test data. I need to create some accounts but I want to be able to actually log into these accounts which means setting passwords. However, I'm struggling to work out how to set the password in the db. I have tried a few different python scripts (my seeding is produced with python) but not succeeding so far. I have tried to mimic the same method used by better-auth.
import os
import unicodedata
import hashlib
import hmac

# exactly the same params as better-auth
SCRYPT_PARAMS = dict(n=16384, r=16, p=1, dklen=64)

# mirror their maxmem: 128 * N * r * 2
MAXMEM = 128 * SCRYPT_PARAMS["n"] * SCRYPT_PARAMS["r"] * 2

def hash_password(password: str) -> str:
"""
→ salt:key just like better-auth (hex:salt, hex:key)
→ no BETTER_AUTH_SECRET is used here
"""
# 1) Unicode NFKC normalization
normalized = unicodedata.normalize("NFKC", password)
# 2) random 16-byte salt
salt = os.urandom(16)
# 3) scrypt with explicit maxmem
key = hashlib.scrypt(
normalized.encode("utf-8"),
salt=salt,
n=SCRYPT_PARAMS["n"],
r=SCRYPT_PARAMS["r"],
p=SCRYPT_PARAMS["p"],
dklen=SCRYPT_PARAMS["dklen"],
maxmem=MAXMEM,
)
# 4) return hex(salt):hex(key)
return f"{salt.hex()}:{key.hex()}"

pw = "12345678"
h = hash_password(pw)
print("Hash:", h)
import os
import unicodedata
import hashlib
import hmac

# exactly the same params as better-auth
SCRYPT_PARAMS = dict(n=16384, r=16, p=1, dklen=64)

# mirror their maxmem: 128 * N * r * 2
MAXMEM = 128 * SCRYPT_PARAMS["n"] * SCRYPT_PARAMS["r"] * 2

def hash_password(password: str) -> str:
"""
→ salt:key just like better-auth (hex:salt, hex:key)
→ no BETTER_AUTH_SECRET is used here
"""
# 1) Unicode NFKC normalization
normalized = unicodedata.normalize("NFKC", password)
# 2) random 16-byte salt
salt = os.urandom(16)
# 3) scrypt with explicit maxmem
key = hashlib.scrypt(
normalized.encode("utf-8"),
salt=salt,
n=SCRYPT_PARAMS["n"],
r=SCRYPT_PARAMS["r"],
p=SCRYPT_PARAMS["p"],
dklen=SCRYPT_PARAMS["dklen"],
maxmem=MAXMEM,
)
# 4) return hex(salt):hex(key)
return f"{salt.hex()}:{key.hex()}"

pw = "12345678"
h = hash_password(pw)
print("Hash:", h)
The hashing works fine but if I put that in my db I can't log in using 12345678. Don't worry this is all local! Should I be using the BETTER_AUTH_SECRET somewhere? I couldn't see its use in the js library.
1 Reply
insightautomate
insightautomateOP3d ago
For anyone else who comes across this I solved it by having to run a separate node script that the python references.
// hash-password.js
const { scryptAsync } = require('@noble/hashes/scrypt');
const crypto = require('crypto');

// Configuration matching Better Auth exactly
const config = {
N: 16384,
r: 16,
p: 1,
dkLen: 64,
};

async function generateKey(password, salt) {
return await scryptAsync(password.normalize("NFKC"), salt, {
N: config.N,
p: config.p,
r: config.r,
dkLen: config.dkLen,
maxmem: 128 * config.N * config.r * 2,
});
}

async function hashPassword(password) {
// Generate a random salt (16 bytes) and convert to hex
const salt = crypto.randomBytes(16).toString('hex');
const key = await generateKey(password, salt);
return `${salt}:${Buffer.from(key).toString('hex')}`;
}

// Get the password from command line argument
async function main() {
const password = process.argv[2];
if (!password) {
console.error('Please provide a password as an argument');
process.exit(1);
}

try {
const hash = await hashPassword(password);
console.log(hash); // Output the hash to be captured by Python
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
}

main();
// hash-password.js
const { scryptAsync } = require('@noble/hashes/scrypt');
const crypto = require('crypto');

// Configuration matching Better Auth exactly
const config = {
N: 16384,
r: 16,
p: 1,
dkLen: 64,
};

async function generateKey(password, salt) {
return await scryptAsync(password.normalize("NFKC"), salt, {
N: config.N,
p: config.p,
r: config.r,
dkLen: config.dkLen,
maxmem: 128 * config.N * config.r * 2,
});
}

async function hashPassword(password) {
// Generate a random salt (16 bytes) and convert to hex
const salt = crypto.randomBytes(16).toString('hex');
const key = await generateKey(password, salt);
return `${salt}:${Buffer.from(key).toString('hex')}`;
}

// Get the password from command line argument
async function main() {
const password = process.argv[2];
if (!password) {
console.error('Please provide a password as an argument');
process.exit(1);
}

try {
const hash = await hashPassword(password);
console.log(hash); // Output the hash to be captured by Python
} catch (error) {
console.error('Error:', error.message);
process.exit(1);
}
}

main();
which can be called with
import subprocess
import sys

def hash_password(password):
"""
Hash a password using Better Auth's exact implementation via Node.js

Args:
password: The plaintext password to hash

Returns:
Hashed password string in Better Auth format
"""
try:
# Use the full path to node that you provided
node_path = "/Users/myMacBookUsername/.nvm/versions/node/v20.12.2/bin/node"

# Call the Node.js script with the password as an argument
result = subprocess.run(
[node_path, 'hash-password.js', password],
capture_output=True,
text=True,
check=True
)
# Return the output (the hash)
return result.stdout.strip()
except subprocess.CalledProcessError as e:
print(f"Error hashing password: {e.stderr}", file=sys.stderr)
raise
import subprocess
import sys

def hash_password(password):
"""
Hash a password using Better Auth's exact implementation via Node.js

Args:
password: The plaintext password to hash

Returns:
Hashed password string in Better Auth format
"""
try:
# Use the full path to node that you provided
node_path = "/Users/myMacBookUsername/.nvm/versions/node/v20.12.2/bin/node"

# Call the Node.js script with the password as an argument
result = subprocess.run(
[node_path, 'hash-password.js', password],
capture_output=True,
text=True,
check=True
)
# Return the output (the hash)
return result.stdout.strip()
except subprocess.CalledProcessError as e:
print(f"Error hashing password: {e.stderr}", file=sys.stderr)
raise

Did you find this page helpful?