Files
AriWeb-Suite/lib/crypto.mjs
T

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');