feat: add Random Generator tool
Cryptographically secure generator for 5 types: - Password: configurable charset + entropy strength meter - UUID: crypto.randomUUID() - API Key: hex/base62/base64url with optional prefix - Hash: SHA-1/256/512 of custom input or random data - Token: variable byte-length in hex or base64url All using Web Crypto API — nothing leaves the browser. Registered in lib/tools.tsx with RandomIcon (dice). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
118
lib/random/generators.ts
Normal file
118
lib/random/generators.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
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<string> {
|
||||
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);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ColorIcon, UnitsIcon, ASCIIIcon, MediaIcon, FaviconIcon, QRCodeIcon, AnimateIcon, CalculateIcon } from '@/components/AppIcons';
|
||||
import { ColorIcon, UnitsIcon, ASCIIIcon, MediaIcon, FaviconIcon, QRCodeIcon, AnimateIcon, CalculateIcon, RandomIcon } from '@/components/AppIcons';
|
||||
|
||||
export interface Tool {
|
||||
/** Short display name (e.g. "Color") */
|
||||
@@ -97,9 +97,20 @@ export const tools: Tool[] = [
|
||||
icon: AnimateIcon,
|
||||
badges: ['CSS', 'Tailwind v4', '20+ Presets'],
|
||||
},
|
||||
{
|
||||
shortTitle: 'Random',
|
||||
title: 'Random Generator',
|
||||
navTitle: 'Random Generator',
|
||||
href: '/random',
|
||||
description: 'Generate cryptographically secure passwords, UUIDs, API keys, hashes and tokens.',
|
||||
summary:
|
||||
'Cryptographically secure random generator. Create passwords, UUIDs, API keys, SHA hashes, and secure tokens — all using the browser Web Crypto API, nothing leaves your machine.',
|
||||
icon: RandomIcon,
|
||||
badges: ['Web Crypto', 'Passwords', 'UUID', 'Hashes'],
|
||||
},
|
||||
{
|
||||
shortTitle: 'Calculate',
|
||||
title: 'Calculator & Grapher',
|
||||
title: 'Calculator',
|
||||
navTitle: 'Calculator',
|
||||
href: '/calculate',
|
||||
description: 'Advanced expression evaluator with interactive function graphing.',
|
||||
|
||||
Reference in New Issue
Block a user