Created
February 26, 2024 11:13
-
-
Save pypy-vrc/ec90c3d389ae82b92b7eee83f1a59011 to your computer and use it in GitHub Desktop.
Time-based one-time password (TOTP)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { createHmac, randomBytes } from "crypto"; | |
// Time-based one-time password (TOTP) | |
// @see https://github.com/google/google-authenticator/wiki/Key-Uri-Format | |
// otpauth://totp/Example:[email protected]?secret=JBSWY3DPEHPK3PXP&issuer=Example | |
const decodeBase32 = (str: string) => { | |
const buf = Buffer.allocUnsafe(64); // Math.floor(str.length * 0.625) | |
let len = 0; | |
let value = 0; | |
let bits = 0; | |
for (const s of str) { | |
const c = s.charCodeAt(0); | |
let v; | |
if (c >= 50 && c <= 55) { | |
v = c - 24; | |
} else if (c >= 65 && c <= 90) { | |
v = c - 65; | |
} else if (c >= 97 && c <= 122) { | |
v = c - 97; | |
} else { | |
continue; | |
} | |
value = (value << 5) | v; | |
bits += 5; | |
if (bits >= 8) { | |
bits -= 8; | |
buf.writeUint8((value >>> bits) & 255, len++); | |
} | |
} | |
return buf.subarray(0, len); | |
}; | |
export const totp = (secret: string, time?: number, digits = 6) => { | |
const key = decodeBase32(secret); | |
const counter = Math.floor((time ?? Date.now()) / 30000); // period: 30s | |
const buf = Buffer.alloc(8); | |
buf.writeUint32BE(counter, 4); | |
const hash = createHmac("sha1", key).update(buf).digest(); | |
const offset = hash.readUint8(hash.byteLength - 1) & 15; | |
const code = hash.readUint32BE(offset) & 0x7fffffff; | |
return code.toString().slice(-digits); | |
}; | |
export const randomSecret = (length = 32) => { | |
// length=32 for 160 bits | |
// length=64 for 320 bits | |
const a = []; | |
for (const v of randomBytes(length)) { | |
a.push("234567ABCDEFGHIJKLMNOPQRSTUVWXYZ"[v & 31]); | |
} | |
return a.join(""); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment