const CHARSET = { uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', lowercase: 'abcdefghijklmnopqrstuvwxyz', numbers: '0123456789', symbols: '!@#$%^&*()-_=+[]{}|;:,.<>?', hex: '0123456789abcdef', base62: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', }; export interface PasswordOpts { length: number; uppercase: boolean; lowercase: boolean; numbers: boolean; symbols: boolean; } export interface ApiKeyOpts { length: number; format: 'hex' | 'base62' | 'base64url'; prefix: string; } export interface HashOpts { algorithm: 'SHA-1' | 'SHA-256' | 'SHA-512'; input: string; } export interface TokenOpts { bytes: number; format: 'hex' | 'base64url'; } function randomBytes(n: number): Uint8Array { const arr = new Uint8Array(n); crypto.getRandomValues(arr); return arr; } function toHex(bytes: Uint8Array): string { return Array.from(bytes) .map((b) => b.toString(16).padStart(2, '0')) .join(''); } function toBase64url(bytes: Uint8Array): string { return btoa(String.fromCharCode(...bytes)) .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=/g, ''); } export function generatePassword(opts: PasswordOpts): string { let charset = ''; if (opts.uppercase) charset += CHARSET.uppercase; if (opts.lowercase) charset += CHARSET.lowercase; if (opts.numbers) charset += CHARSET.numbers; if (opts.symbols) charset += CHARSET.symbols; if (!charset) charset = CHARSET.lowercase + CHARSET.numbers; const bytes = randomBytes(opts.length * 4); let result = ''; let i = 0; while (result.length < opts.length && i < bytes.length) { const idx = bytes[i] % charset.length; result += charset[idx]; i++; } return result.slice(0, opts.length); } export function passwordEntropy(opts: PasswordOpts): number { let size = 0; if (opts.uppercase) size += 26; if (opts.lowercase) size += 26; if (opts.numbers) size += 10; if (opts.symbols) size += CHARSET.symbols.length; if (size === 0) size = 36; return Math.round(Math.log2(size) * opts.length); } export function generateUUID(): string { return crypto.randomUUID(); } export function generateApiKey(opts: ApiKeyOpts): string { const bytes = randomBytes(opts.length * 2); let key: string; switch (opts.format) { case 'hex': key = toHex(bytes).slice(0, opts.length); break; case 'base64url': key = toBase64url(bytes).slice(0, opts.length); break; case 'base62': { const cs = CHARSET.base62; key = Array.from(bytes) .map((b) => cs[b % cs.length]) .join('') .slice(0, opts.length); break; } } return opts.prefix ? `${opts.prefix}_${key}` : key; } export async function generateHash(opts: HashOpts): Promise { const data = opts.input.trim() || toHex(randomBytes(32)); const encoded = new TextEncoder().encode(data); const hashBuffer = await crypto.subtle.digest(opts.algorithm, encoded); return toHex(new Uint8Array(hashBuffer)); } export function generateToken(opts: TokenOpts): string { const bytes = randomBytes(opts.bytes); return opts.format === 'hex' ? toHex(bytes) : toBase64url(bytes); }