// encryption.js // Phonetic alphabet for table names const PHONETIC = ['Alpha', 'Bravo', 'Charlie', 'Delta', 'Echo', 'Foxtrot', 'Golf', 'Hotel', 'India', 'Juliet', 'Kilo', 'Lima', 'Mike', 'November', 'Oscar', 'Papa', 'Quebec', 'Romeo', 'Sierra', 'Tango', 'Uniform', 'Victor', 'Whiskey', 'Xray', 'Yankee', 'Zulu']; const CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; const LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; // Seeded RNG using SHA256 class SeededRandom { constructor(seed) { this.seed = seed; this.counter = 0; } next() { const hash = CryptoJS.SHA256(this.seed + ':' + this.counter); this.counter++; const hex = hash.toString(CryptoJS.enc.Hex); return parseInt(hex.substring(0, 8), 16) / 0xFFFFFFFF; } nextInt(min, max) { return Math.floor(this.next() * (max - min)) + min; } choice(arr) { return arr[this.nextInt(0, arr.length)]; } } // HOTP Generation - 3 digit nonce -> 3 letter code function generateHOTP(secret, nonce) { const rng = new SeededRandom(secret + ':hotp:' + nonce); let code = ''; for (let i = 0; i < 3; i++) { code += rng.choice(LETTERS.split('')); } return code; } // Auth Tables function generateAuthTable(seed, tableIndex) { const rng = new SeededRandom(seed + ':table:' + tableIndex); const rows = 'NOPQRSTUVWXYZ'.split(''); const cols = 'ABCDEFGHIJKLM'.split(''); const table = []; for (let row of rows) { const rowData = []; for (let col of cols) { rowData.push(rng.choice(CHARS.split(''))); } table.push(rowData); } return table; } // One-Time Pad (for encode/decode) function generatePad(seed, padKey, length = 60) { const rng = new SeededRandom(seed + ':pad:' + padKey); const pad = []; for (let i = 0; i < length; i++) { pad.push(rng.nextInt(0, 256)); } return pad; } function generatePadDisplay(seed, padKey) { const rng = new SeededRandom(seed + ':pad:' + padKey); const groups = []; for (let i = 0; i < 12; i++) { const num = rng.nextInt(0, 100000).toString().padStart(5, '0'); groups.push(num); } return groups.join(' '); } function encodeMessage(message, key, padKey, useStream = true) { const messageBytes = new TextEncoder().encode(message); const padLength = useStream ? messageBytes.length : 60; const pad = generatePad(key, padKey, padLength); const encoded = []; for (let i = 0; i < messageBytes.length; i++) { const padByte = pad[i % pad.length]; encoded.push(messageBytes[i] ^ padByte); } return Array.from(encoded).map(b => b.toString(16).padStart(2, '0')).join(''); } function decodeMessage(encoded, key, padKey, useStream = true) { const bytes = []; for (let i = 0; i < encoded.length; i += 2) { bytes.push(parseInt(encoded.substr(i, 2), 16)); } const padLength = useStream ? bytes.length : 60; const pad = generatePad(key, padKey, padLength); const decoded = []; for (let i = 0; i < bytes.length; i++) { const padByte = pad[i % pad.length]; decoded.push(bytes[i] ^ padByte); } return new TextDecoder().decode(new Uint8Array(decoded)); }