Ariweb Suite 0.1.1 기반: 공통 라이브러리(db·http·세션·작업 엔진·할당/예약·알림·감사·보관소), 포털 뼈대, Webmaker/AriMail 도입 소스와 manifest
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
9ca4cee341
commit
c7aea8d419
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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');
|
||||
Reference in New Issue
Block a user