Skip to content

Instantly share code, notes, and snippets.

@pypy-vrc
Created February 26, 2024 11:13
Show Gist options
  • Save pypy-vrc/ec90c3d389ae82b92b7eee83f1a59011 to your computer and use it in GitHub Desktop.
Save pypy-vrc/ec90c3d389ae82b92b7eee83f1a59011 to your computer and use it in GitHub Desktop.
Time-based one-time password (TOTP)
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:alice@google.com?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