totp-simulator.html
HTML5 + Web Crypto API
<!DOCTYPE html> <html lang="fr"> <head> <meta charset="UTF-8"> <style> body { font-family: sans-serif; display: flex; justify-content: center; } .card { background: white; padding: 2rem; border-radius: 12px; width: 350px; } .code { font-size: 3rem; font-weight: bold; text-align: center; } #progress-bar { width: 100%; height: 8px; background: #1a73e8; } </style> </head> <body> <div class="card"> <h2>Mon Authenticator</h2> <input type="text" id="secretInput" value="JBSWY3DPEHPK3PXP"> <div id="totp-display" class="code">------</div> <div id="progress-bar"></div> </div> <script> // Algorithme TOTP conforme RFC 6238 const TOTP = { base32ToBuf(str) { const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; let bits = ""; for (let i = 0; i < str.length; i++) { const val = alphabet.indexOf(str[i].toUpperCase()); if (val === -1) continue; bits += val.toString(2).padStart(5, '0'); } const bytes = []; for (let i = 0; i + 8 <= bits.length; i += 8) { bytes.push(parseInt(bits.substring(i, i + 8), 2)); } return new Uint8Array(bytes); }, async generate(secretBase32) { const keyBuf = this.base32ToBuf(secretBase32); const epoch = Math.floor(Date.now() / 1000); const counter = Math.floor(epoch / 30); const counterBuf = new ArrayBuffer(8); const view = new DataView(counterBuf); view.setUint32(4, counter); const cryptoKey = await crypto.subtle.importKey( "raw", keyBuf, { name: "HMAC", hash: "SHA-1" }, false, ["sign"] ); const hmac = await crypto.subtle.sign("HMAC", cryptoKey, counterBuf); const res = new Uint8Array(hmac); const offset = res[res.length - 1] & 0xf; const code = ((res[offset] & 0x7f) << 24 | (res[offset + 1] & 0xff) << 16 | (res[offset + 2] & 0xff) << 8 | (res[offset + 3] & 0xff)) % 1000000; return code.toString().padStart(6, '0'); } }; </script> </body> </html>
