- 1<?php
- 2/*
- 3 * File: generatepasswd.php for Paste
- 4 * Just a little Bootstrap 5 tool for the registration page.
- 5 * Fully client side generation.
- 6 * License: GPLv3
- 7 */
- 8?><!DOCTYPE html>
- 9<html lang="en">
- 10<head>
- 11 <meta charset="utf-8">
- 12 <title>Password Generator</title>
- 13 <meta name="viewport" content="width=device-width, initial-scale=1">
- 14 <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
- 15 <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet">
- 16
- 17 <style>
- 18 /* Keep styles minimal*/
- 19 :root {
- 20 --brand-blue: #399BFF;
- 21 --card-bg: rgba(255,255,255,0.04);
- 22 --text-muted: #9aa4ad;
- 23 }
- 24 @media (prefers-color-scheme: light) {
- 25 :root {
- 26 --card-bg: #ffffff;
- 27 --text-muted: #6c757d;
- 28 }
- 29 }
- 30
- 31 body {
- 32 font-family: "Fira Sans", system-ui, -apple-system, Segoe UI, Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
- 33 min-height: 100vh;
- 34 display: flex;
- 35 flex-direction: column;
- 36 background:
- 37 radial-gradient(1200px 600px at 10% -10%, rgba(57,155,255,.20), transparent 60%),
- 38 radial-gradient(1200px 600px at 110% 10%, rgba(57,155,255,.15), transparent 60%);
- 39 }
- 40
- 41 .page-wrap { flex: 1 0 auto; }
- 42 .hero {
- 43 text-align: center;
- 44 margin-top: 2rem;
- 45 margin-bottom: 1.25rem;
- 46 }
- 47 .hero h1 {
- 48 font-weight: 700;
- 49 letter-spacing: .2px;
- 50 }
- 51 .hero p {
- 52 color: var(--text-muted);
- 53 margin: .5rem 0 0;
- 54 }
- 55
- 56 .generator-card {
- 57 background: var(--card-bg);
- 58 border: 1px solid rgba(255,255,255,0.08);
- 59 border-radius: 1rem;
- 60 backdrop-filter: saturate(120%) blur(6px);
- 61 }
- 62
- 63 .password-box {
- 64 font-family: "Fira Code", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
- 65 font-size: 1.125rem;
- 66 user-select: all;
- 67 letter-spacing: .2px;
- 68 }
- 69
- 70 .btn-perky {
- 71 --bs-btn-bg: var(--brand-blue);
- 72 --bs-btn-border-color: var(--brand-blue);
- 73 --bs-btn-hover-bg: #2f8ae5;
- 74 --bs-btn-hover-border-color: #2f8ae5;
- 75 --bs-btn-color: #fff;
- 76 }
- 77
- 78 .entropy-badge {
- 79 font-variant-numeric: tabular-nums;
- 80 }
- 81
- 82 .footer-note {
- 83 color: var(--text-muted);
- 84 font-size: .9rem;
- 85 }
- 86
- 87 .range-output {
- 88 min-width: 2.5ch;
- 89 text-align: right;
- 90 display: inline-block;
- 91 }
- 92 .form-check .form-text {
- 93 margin-left: 1.65rem;
- 94 margin-top: .25rem;
- 95 }
- 96 </style>
- 97</head>
- 98<body>
- 99 <main class="page-wrap">
- 100 <div class="container">
- 101 <div class="hero">
- 102 <h1>Generate a Secure Password</h1>
- 103 <p>Client-side generator — No data leaves your browser</p>
- 104 </div>
- 105
- 106 <div class="row justify-content-center">
- 107 <div class="col-12 col-lg-8">
- 108 <div class="generator-card shadow-sm p-3 p-md-4">
- 109 <!-- Output -->
- 110 <div class="mb-3">
- 111 <label class="form-label fw-semibold">Password</label>
- 112 <div class="input-group">
- 113 <input id="out" type="text" class="form-control password-box" readonly aria-describedby="copyBtn">
- 114 <button id="regenBtn" class="btn btn-outline-secondary" type="button" title="Regenerate">
- 115 <i class="bi bi-arrow-repeat"></i>
- 116 </button>
- 117 <button id="copyBtn" class="btn btn-perky fw-semibold" type="button" title="Copy to clipboard">
- 118 <i class="bi bi-clipboard-check"></i> Copy
- 119 </button>
- 120 </div>
- 121 <div class="d-flex align-items-center gap-3 mt-2">
- 122 <span class="badge text-bg-secondary entropy-badge" id="entropyBadge" title="Estimated entropy in bits">— bits</span>
- 123 <span id="strengthLabel" class="small"></span>
- 124 </div>
- 125 </div>
- 126
- 127 <hr class="my-4">
- 128
- 129 <!-- Controls -->
- 130 <form class="row gy-3">
- 131 <div class="col-12 col-md-6">
- 132 <label for="len" class="form-label fw-semibold">Length:
- 133 <span class="range-output" id="lenOut">16</span>
- 134 </label>
- 135 <input id="len" type="range" class="form-range" min="8" max="64" step="1" value="16" aria-describedby="lenHelp">
- 136 <div id="lenHelp" class="form-text">Longer is stronger; 16–24 is a great default.</div>
- 137 </div>
- 138
- 139 <div class="col-12 col-md-6">
- 140 <div class="row">
- 141 <div class="col-6">
- 142 <div class="form-check">
- 143 <input class="form-check-input" type="checkbox" id="lower" checked>
- 144 <label class="form-check-label" for="lower">Lowercase (a–z)</label>
- 145 </div>
- 146 <div class="form-check">
- 147 <input class="form-check-input" type="checkbox" id="upper" checked>
- 148 <label class="form-check-label" for="upper">Uppercase (A–Z)</label>
- 149 </div>
- 150 </div>
- 151 <div class="col-6">
- 152 <div class="form-check">
- 153 <input class="form-check-input" type="checkbox" id="digits" checked>
- 154 <label class="form-check-label" for="digits">Digits (0–9)</label>
- 155 </div>
- 156 <div class="form-check">
- 157 <input class="form-check-input" type="checkbox" id="symbols" checked>
- 158 <label class="form-check-label" for="symbols">Symbols (!@#$…)</label>
- 159 </div>
- 160 </div>
- 161 </div>
- 162 <div class="form-check mt-2">
- 163 <input class="form-check-input" type="checkbox" id="noAmbig" checked>
- 164 <label class="form-check-label" for="noAmbig">Avoid ambiguous characters</label>
- 165 <div class="form-text">Skips look-alikes like <code>O</code>/<code>0</code>, <code>l</code>/<code>1</code>, <code>{}</code>/<code>[]</code>.</div>
- 166 </div>
- 167 </div>
- 168
- 169 <div class="col-12">
- 170 <div class="d-flex gap-2">
- 171 <button type="button" id="generateBtn" class="btn btn-perky fw-semibold">
- 172 <i class="bi bi-magic"></i> Generate
- 173 </button>
- 174 <button type="button" id="copyBtn2" class="btn btn-outline-secondary">
- 175 <i class="bi bi-clipboard"></i> Copy
- 176 </button>
- 177 </div>
- 178 </div>
- 179 </form>
- 180
- 181 <div class="mt-4 footer-note">
- 182 Tip: Use a unique password for every site. Consider a password manager.
- 183 </div>
- 184 </div>
- 185
- 186 <div class="text-center mt-3">
- 187 <a class="small text-decoration-underline" href="/" aria-label="Back to Paste">← Back to Paste</a>
- 188 </div>
- 189 </div>
- 190 </div>
- 191 </div>
- 192 </main>
- 193
- 194 <script>
- 195 (function () {
- 196 "use strict";
- 197
- 198 const $ = (id) => document.getElementById(id);
- 199
- 200 const lowerChars = "abcdefghijklmnopqrstuvwxyz";
- 201 const upperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
- 202 const digitChars = "0123456789";
- 203 const symbolChars = "!@#$%^&*()-_=+[]{};:,.<>/?~";
- 204 const ambiguous = "O0oIl1|[]{}()<>`'\".,;:~";
- 205
- 206 const len = $("len");
- 207 const lenOut = $("lenOut");
- 208 const lower = $("lower");
- 209 const upper = $("upper");
- 210 const digits = $("digits");
- 211 const symbols = $("symbols");
- 212 const noAmbig = $("noAmbig");
- 213
- 214 const out = $("out");
- 215 const generateBtn = $("generateBtn");
- 216 const regenBtn = $("regenBtn");
- 217 const copyBtn = $("copyBtn");
- 218 const copyBtn2 = $("copyBtn2");
- 219 const entropyBadge = $("entropyBadge");
- 220 const strengthLabel= $("strengthLabel");
- 221
- 222 function updateLenOutput() {
- 223 lenOut.textContent = String(len.value);
- 224 }
- 225
- 226 function buildCharset() {
- 227 let set = "";
- 228 if (lower.checked) set += lowerChars;
- 229 if (upper.checked) set += upperChars;
- 230 if (digits.checked) set += digitChars;
- 231 if (symbols.checked) set += symbolChars;
- 232 if (noAmbig.checked) {
- 233 set = [...set].filter(ch => !ambiguous.includes(ch)).join("");
- 234 }
- 235 return Array.from(new Set(set)).join(""); // dedupe
- 236 }
- 237
- 238 function getRandomValues(length) {
- 239 const arr = new Uint32Array(length);
- 240 if (window.crypto && window.crypto.getRandomValues) {
- 241 window.crypto.getRandomValues(arr);
- 242 } else {
- 243 // Fallback (should almost never happen): use Math.random
- 244 for (let i = 0; i < length; i++) arr[i] = Math.floor(Math.random() * 0xFFFFFFFF);
- 245 }
- 246 return arr;
- 247 }
- 248
- 249 function generatePassword() {
- 250 const L = parseInt(len.value, 10);
- 251 let charset = buildCharset();
- 252 if (!charset) {
- 253 alert("Please select at least one character set.");
- 254 return;
- 255 }
- 256 // Ensure we sample uniformly from charset using rejection sampling
- 257 const chars = Array.from(charset);
- 258 const n = chars.length;
- 259 const outChars = [];
- 260 const rand = getRandomValues(L * 2); // buffer
- 261 let i = 0;
- 262
- 263 const max = Math.floor(0x100000000 / n) * n; // highest multiple of n below 2^32
- 264
- 265 for (let produced = 0; produced < L; ) {
- 266 if (i >= rand.length) {
- 267 // refill
- 268 const refill = getRandomValues(L);
- 269 for (let k = 0; k < refill.length; k++) rand[k] = refill[k];
- 270 i = 0;
- 271 }
- 272 const r = rand[i++];
- 273 if (r < max) {
- 274 outChars.push(chars[r % n]);
- 275 produced++;
- 276 }
- 277 }
- 278
- 279 const pwd = outChars.join("");
- 280 out.value = pwd;
- 281 updateStrength(pwd, n);
- 282 }
- 283
- 284 function log2(x) { return Math.log(x) / Math.log(2); }
- 285
- 286 function updateStrength(pwd, charsetSize) {
- 287 const L = pwd.length || 0;
- 288 // Shannon-style estimate (assuming uniform random from charset)
- 289 const bits = Math.round(L * log2(Math.max(2, charsetSize)));
- 290 entropyBadge.textContent = bits + " bits";
- 291
- 292 let label = "";
- 293 let cls = "text-secondary";
- 294 if (bits < 45) { label = "Weak"; cls = "text-danger"; }
- 295 else if (bits < 64) { label = "Fair"; cls = "text-warning"; }
- 296 else if (bits < 90) { label = "Strong"; cls = "text-success"; }
- 297 else { label = "Excellent"; cls = "text-success"; }
- 298
- 299 strengthLabel.className = "small " + cls;
- 300 strengthLabel.textContent = label + " (charset " + charsetSize + ", length " + L + ")";
- 301 }
- 302
- 303 function copyToClipboard() {
- 304 out.select();
- 305 out.setSelectionRange(0, 99999);
- 306 navigator.clipboard?.writeText(out.value).then(() => {
- 307 flashCopied(copyBtn);
- 308 flashCopied(copyBtn2);
- 309 }).catch(() => {
- 310 // Fallback
- 311 document.execCommand("copy");
- 312 flashCopied(copyBtn);
- 313 flashCopied(copyBtn2);
- 314 });
- 315 }
- 316
- 317 function flashCopied(btn) {
- 318 if (!btn) return;
- 319 const original = btn.innerHTML;
- 320 btn.innerHTML = '<i class="bi bi-check2-circle"></i> Copied';
- 321 btn.disabled = true;
- 322 setTimeout(() => {
- 323 btn.innerHTML = original;
- 324 btn.disabled = false;
- 325 }, 1200);
- 326 }
- 327
- 328 // Events
- 329 len.addEventListener("input", updateLenOutput);
- 330 generateBtn.addEventListener("click", generatePassword);
- 331 regenBtn.addEventListener("click", generatePassword);
- 332 copyBtn.addEventListener("click", copyToClipboard);
- 333 copyBtn2.addEventListener("click", copyToClipboard);
- 334 [lower, upper, digits, symbols, noAmbig].forEach(el => {
- 335 el.addEventListener("change", generatePassword);
- 336 });
- 337
- 338 // Init
- 339 updateLenOutput();
- 340 generatePassword();
- 341 })();
- 342 </script>
- 343</body>
- 344</html>
Raw Paste