From 0e2a2818fd5d402ccb3c80960e057ccfd1ca6c7b Mon Sep 17 00:00:00 2001 From: Dicken Date: Mon, 1 Jun 2026 10:36:53 +0200 Subject: [PATCH] =?UTF-8?q?DevTools:=20Key-Generator=20(Laravel,=20JWT,=20?= =?UTF-8?q?AES,=20UUID,=20NanoID,=20Passw=C3=B6rter,=20Hex/Base64=20Tokens?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/tools/devtools.jsx | 136 +++++++++++++++++++++++++++++++- 1 file changed, 135 insertions(+), 1 deletion(-) diff --git a/frontend/src/tools/devtools.jsx b/frontend/src/tools/devtools.jsx index 515b5b6..852ecee 100644 --- a/frontend/src/tools/devtools.jsx +++ b/frontend/src/tools/devtools.jsx @@ -786,6 +786,140 @@ function DatetimeRechner({ mobile }) { ); } +// ─────────────────────────────────────────────────────────────── Key-Gen ── + +const KEY_TYPES = [ + { id: 'laravel', label: 'Laravel APP_KEY', desc: 'base64, 32 Byte', gen: () => 'base64:' + btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(32)))) }, + { id: 'jwt_hs256', label: 'JWT Secret (HS256)', desc: '64 Byte hex', gen: () => hex(64) }, + { id: 'jwt_hs512', label: 'JWT Secret (HS512)', desc: '128 Byte hex', gen: () => hex(128) }, + { id: 'aes256', label: 'AES-256 Key', desc: '32 Byte base64', gen: () => b64(32) }, + { id: 'aes128', label: 'AES-128 Key', desc: '16 Byte base64', gen: () => b64(16) }, + { id: 'secret32', label: 'Generic Secret (32B)', desc: '32 Byte hex', gen: () => hex(32) }, + { id: 'secret64', label: 'Generic Secret (64B)', desc: '64 Byte hex', gen: () => hex(64) }, + { id: 'passwd', label: 'Passwort (stark)', desc: '20 Zeichen, alle Typen', gen: () => genPasswd(20) }, + { id: 'passwd16', label: 'Passwort (kurz)', desc: '16 Zeichen, alle Typen', gen: () => genPasswd(16) }, + { id: 'uuid', label: 'UUID v4', desc: 'zufällige UUID', gen: () => crypto.randomUUID() }, + { id: 'nanoid', label: 'NanoID (21)', desc: 'URL-safe, 21 Zeichen', gen: () => nanoid(21) }, + { id: 'nanoid32', label: 'NanoID (32)', desc: 'URL-safe, 32 Zeichen', gen: () => nanoid(32) }, + { id: 'docker_pw', label: 'DB-Passwort (Docker)', desc: '24 Zeichen, alnum+sonder',gen: () => genPasswd(24) }, + { id: 'hex16', label: 'Hex Token (16B)', desc: '32 Hex-Zeichen', gen: () => hex(16) }, + { id: 'hex32', label: 'Hex Token (32B)', desc: '64 Hex-Zeichen', gen: () => hex(32) }, + { id: 'b64_32', label: 'Base64 Token (32B)', desc: '32 Byte base64', gen: () => b64(32) }, + { id: 'b64_64', label: 'Base64 Token (64B)', desc: '64 Byte base64', gen: () => b64(64) }, + { id: 'b64url_32', label: 'Base64url Token (32B)', desc: '32 Byte base64url', gen: () => b64url(32) }, +]; + +function hex(bytes) { + return Array.from(crypto.getRandomValues(new Uint8Array(bytes))).map(b=>b.toString(16).padStart(2,'0')).join(''); +} +function b64(bytes) { + return btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(bytes)))); +} +function b64url(bytes) { + return b64(bytes).replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,''); +} +function nanoid(len) { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; + const rnd = crypto.getRandomValues(new Uint8Array(len)); + return Array.from(rnd).map(b => chars[b % 64]).join(''); +} +function genPasswd(len) { + const sets = ['ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz','0123456789','!@#$%^&*()-_=+[]{}']; + const all = sets.join(''); + const rnd = crypto.getRandomValues(new Uint8Array(len + 8)); + let result = sets.map(s => s[rnd[sets.indexOf(s)] % s.length]); // guarantee 1 from each set + let idx = 4; + while (result.length < len) { result.push(all[rnd[idx++ % rnd.length] % all.length]); } + // shuffle + for (let i = result.length - 1; i > 0; i--) { + const j = rnd[(i * 3) % rnd.length] % (i + 1); + [result[i], result[j]] = [result[j], result[i]]; + } + return result.join(''); +} + +function KeyGen({ toast }) { + const [keys, setKeys] = useState({}); + const [copied, setCopied] = useState(null); + + function generate(id) { + const kt = KEY_TYPES.find(k => k.id === id); + if (!kt) return; + setKeys(p => ({ ...p, [id]: kt.gen() })); + } + + function generateAll() { + const all = {}; + KEY_TYPES.forEach(kt => { all[kt.id] = kt.gen(); }); + setKeys(all); + } + + function clearAll() { setKeys({}); } + + async function copyKey(id, val) { + try { + await navigator.clipboard.writeText(val); + setCopied(id); + setTimeout(() => setCopied(null), 1800); + } catch { toast('Kopieren fehlgeschlagen'); } + } + + return ( +
+
+ + {!!Object.keys(keys).length && ( + + )} +
+ +
+ {KEY_TYPES.map(kt => ( +
+
+
+ {kt.label} + {kt.desc} +
+ + {!!keys[kt.id] && ( + + )} +
+ {!!keys[kt.id] && ( +
copyKey(kt.id, keys[kt.id])} title="Klicken zum Kopieren"> + + {keys[kt.id]} + + + {copied===kt.id ? '✓' : '⎘'} + +
+ )} +
+ ))} +
+
+ Alle Keys werden lokal im Browser generiert (Web Crypto API) · Nichts wird gespeichert · Beim Seitenwechsel verschwinden sie +
+
+ ); +} + const TOOL_DEFS = [ {id:'cron', label:'⏱ Crontab', desc:'Crontab erklären und erstellen', ready:true, tabs:[{id:'explain',label:'Erklären'},{id:'build',label:'Erstellen'}], @@ -797,7 +931,7 @@ const TOOL_DEFS = [ {id:'diff', label:'± Text Diff', desc:'Texte vergleichen', ready:false}, {id:'jwt', label:'🔑 JWT', desc:'JWT dekodieren', ready:false}, {id:'datetime', label:'📅 Datetime', desc:'Zeiten umrechnen · Geburtstag · Differenz', ready:true, component:DatetimeRechner}, - {id:'color', label:'🎨 Farben', desc:'HEX/RGB/HSL umrechnen', ready:false}, + {id:'keygen', label:'🔐 Key-Gen', desc:'Sichere Keys & Secrets generieren', ready:true, component:KeyGen}, ]; export default function DevTools({ toast, mobile }) {