DevTools: Key-Generator (Laravel, JWT, AES, UUID, NanoID, Passwörter, Hex/Base64 Tokens)

This commit is contained in:
2026-06-01 10:36:53 +02:00
parent 0dcd9f3543
commit 0e2a2818fd

View File

@@ -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 (
<div>
<div style={{display:'flex',gap:8,marginBottom:20,flexWrap:'wrap'}}>
<button style={{...S.btn('#4ecdc4',false), fontSize:11, padding:'7px 16px'}}
onClick={generateAll}> Alle generieren</button>
{!!Object.keys(keys).length && (
<button style={{...S.btn('#ff6b9d',false), fontSize:11, padding:'7px 16px'}}
onClick={clearAll}> Alle leeren</button>
)}
</div>
<div style={{display:'flex',flexDirection:'column',gap:8}}>
{KEY_TYPES.map(kt => (
<div key={kt.id} style={{
borderRadius:9, background:'rgba(255,255,255,0.03)',
border:'1px solid rgba(255,255,255,0.07)', overflow:'hidden'}}>
<div style={{display:'flex',alignItems:'center',gap:10,padding:'8px 12px',
borderBottom: keys[kt.id] ? '1px solid rgba(255,255,255,0.06)' : 'none'}}>
<div style={{flex:1,minWidth:0}}>
<span style={{color:'#fff',fontFamily:'monospace',fontSize:12,fontWeight:600}}>{kt.label}</span>
<span style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:10,marginLeft:8}}>{kt.desc}</span>
</div>
<button style={{...S.btn('#4ecdc4',false), fontSize:10, padding:'4px 12px', flexShrink:0}}
onClick={()=>generate(kt.id)}> Neu</button>
{!!keys[kt.id] && (
<button style={{...S.btn('#ff6b9d',false), fontSize:10, padding:'4px 10px', flexShrink:0}}
onClick={()=>setKeys(p=>({...p,[kt.id]:undefined}))}></button>
)}
</div>
{!!keys[kt.id] && (
<div style={{display:'flex',alignItems:'center',gap:8,padding:'8px 12px',cursor:'pointer'}}
onClick={()=>copyKey(kt.id, keys[kt.id])} title="Klicken zum Kopieren">
<code style={{
flex:1,minWidth:0,
color: copied===kt.id ? '#4ecdc4' : 'rgba(255,255,255,0.85)',
fontFamily:"'Courier New',monospace", fontSize:11,
wordBreak:'break-all', lineHeight:1.5}}>
{keys[kt.id]}
</code>
<span style={{
color: copied===kt.id ? '#4ecdc4' : 'rgba(255,255,255,0.35)',
fontSize:16, flexShrink:0, transition:'color 0.2s'}}>
{copied===kt.id ? '✓' : '⎘'}
</span>
</div>
)}
</div>
))}
</div>
<div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:10,marginTop:14,lineHeight:1.7}}>
Alle Keys werden lokal im Browser generiert (Web Crypto API) · Nichts wird gespeichert · Beim Seitenwechsel verschwinden sie
</div>
</div>
);
}
const TOOL_DEFS = [ const TOOL_DEFS = [
{id:'cron', label:'⏱ Crontab', desc:'Crontab erklären und erstellen', ready:true, {id:'cron', label:'⏱ Crontab', desc:'Crontab erklären und erstellen', ready:true,
tabs:[{id:'explain',label:'Erklären'},{id:'build',label:'Erstellen'}], 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:'diff', label:'± Text Diff', desc:'Texte vergleichen', ready:false},
{id:'jwt', label:'🔑 JWT', desc:'JWT dekodieren', 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:'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 }) { export default function DevTools({ toast, mobile }) {