Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
30 lines
2.1 KiB
JavaScript
30 lines
2.1 KiB
JavaScript
/**
|
|
* lib/crypto.mjs — 비밀번호 해시·토큰·HMAC 공통.
|
|
*
|
|
* - 비밀번호: scrypt(N=2^15) + 16바이트 salt. Webmaker 와 같은 계열의 알고리즘이라 SSO 계정 동기화 때 혼동이 없다.
|
|
* - 토큰: 32바이트 무작위 base64url. 저장은 항상 sha256 해시만(원문은 발급 응답에 한 번).
|
|
* - HMAC: Agent 요청 서명·SSO 티켓 서명. 비교는 항상 timingSafeEqual.
|
|
*/
|
|
import { scryptSync, randomBytes, createHash, createHmac, timingSafeEqual } from 'node:crypto';
|
|
|
|
/** 비밀번호 해시. @returns {{salt:string, hash:string}} */
|
|
export function hashPassword(password) { const salt = randomBytes(16).toString('hex'); return { salt, hash: scryptSync(String(password), salt, 64, { N: 2 ** 14, r: 8, p: 1, maxmem: 64 * 1024 * 1024 }).toString('hex') }; }
|
|
/** 비밀번호 검증(상수 시간). */
|
|
export function verifyPassword(password, salt, hash) {
|
|
if (!salt || !hash) return false;
|
|
const a = scryptSync(String(password), salt, 64, { N: 2 ** 14, r: 8, p: 1, maxmem: 64 * 1024 * 1024 }); const b = Buffer.from(hash, 'hex');
|
|
return a.length === b.length && timingSafeEqual(a, b);
|
|
}
|
|
/** 무작위 토큰(원문). bytes 기본 32. */
|
|
export const randomToken = (bytes = 32) => randomBytes(bytes).toString('base64url');
|
|
/** 짧은 식별자(ULID 대용): 시각 정렬 가능한 16진 26자 — 앞 10자는 ms 시각. */
|
|
export function newId(prefix = '') { const t = Date.now().toString(16).padStart(12, '0'); return prefix + t + randomBytes(7).toString('hex'); }
|
|
/** 토큰 저장용 해시 */
|
|
export const sha256 = (s) => createHash('sha256').update(String(s)).digest('hex');
|
|
/** HMAC-SHA256 (base64url) */
|
|
export const hmac = (secret, data) => createHmac('sha256', secret).update(String(data)).digest('base64url');
|
|
/** 문자열 상수 시간 비교 */
|
|
export function safeEqual(a, b) { const x = Buffer.from(String(a || '')); const y = Buffer.from(String(b || '')); return x.length === y.length && x.length > 0 && timingSafeEqual(x, y); }
|
|
/** 6자리 숫자 코드(이메일 확인·MFA 복구 등) */
|
|
export const numericCode = (digits = 6) => String(parseInt(randomBytes(4).toString('hex'), 16) % 10 ** digits).padStart(digits, '0');
|