import crypto from 'node:crypto' // 운영자 비밀번호 저장/검증. 외부 의존성 없이 Node 내장 scrypt 사용. // 저장 형식: `scrypt$$`. 검증은 항상 상수시간 비교. // 기존 account.json 의 평문 비밀번호는 verifyPassword 가 그대로 검증할 수 있고, // 로그인 성공 시 호출측에서 hashPassword 로 재저장(자동 업그레이드)한다. const SCHEME = 'scrypt' const KEY_LEN = 32 const SALT_LEN = 16 export function hashPassword(plain: string): string { const salt = crypto.randomBytes(SALT_LEN) const hash = crypto.scryptSync(plain, salt, KEY_LEN) return `${SCHEME}$${salt.toString('hex')}$${hash.toString('hex')}` } export function isHashed(stored: string): boolean { return typeof stored === 'string' && stored.startsWith(`${SCHEME}$`) } export function verifyPassword(plain: string, stored: string): boolean { if (typeof stored !== 'string' || stored.length === 0) return false if (isHashed(stored)) { const parts = stored.split('$') if (parts.length !== 3) return false let salt: Buffer let expected: Buffer try { salt = Buffer.from(parts[1], 'hex') expected = Buffer.from(parts[2], 'hex') } catch { return false } if (expected.length === 0) return false let derived: Buffer try { derived = crypto.scryptSync(plain, salt, expected.length) } catch { return false } return derived.length === expected.length && crypto.timingSafeEqual(derived, expected) } // 레거시 평문: 길이 노출을 피하려 양쪽을 sha256 으로 고정 길이화한 뒤 상수시간 비교. const a = crypto.createHash('sha256').update(plain, 'utf8').digest() const b = crypto.createHash('sha256').update(stored, 'utf8').digest() return crypto.timingSafeEqual(a, b) }