Skip to content

Instantly share code, notes, and snippets.

@reverofevil
Created January 27, 2024 23:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save reverofevil/06340de9af1f37696e71e3e96f01ae9f to your computer and use it in GitHub Desktop.
Save reverofevil/06340de9af1f37696e71e3e96f01ae9f to your computer and use it in GitHub Desktop.
Github TOTP in Node.js
import { createHmac } from 'crypto';
// token here
const priv = "...";
const base32chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
const base32tohex = (base32: string) => base32
.toUpperCase()
.replace(/=+$/, "")
.split('')
.map(char => {
const val = base32chars.indexOf(char);
if (val === -1) {
throw new Error(`Incorrect character: ${JSON.stringify(char)}`)
}
const bits = val.toString(2).padStart(5, '0');
return bits;
})
.join('')
.match(/.{1,8}/g)
.map(chunk => parseInt(chunk, 2).toString(16).padStart(2, "0"))
.join('');
type Options = Partial<{
period: number,
digits: number,
timestamp: number,
}>
const hex2dec = (s: string) => parseInt(s, 16);
const mask31bit = hex2dec("7fffffff");
const getToken1 = (key: string, {
period = 30,
digits = 6,
timestamp = Date.now(),
}: Options = {}) => {
const kkey = base32tohex(key);
const secondsSinceEpoch = Math.floor(timestamp / 1000.0);
const time = Math.floor(secondsSinceEpoch / period)
.toString(16)
.padStart(16, "0");
const hmac = createHmac('sha1', Buffer.from(kkey, 'hex'))
.update(Buffer.from(time, 'hex'))
.digest('hex');
// takes the 4 least significant bits of the MAC and uses them as a byte offset i
const offset = hex2dec(hmac.substring(hmac.length - 1));
// index i is used to select 31 bits from MAC, starting at bit i × 8 + 1:
const otp = String(hex2dec(hmac.substr(offset * 2, 8)) & mask31bit);
return otp.substr(Math.max(otp.length - digits, 0), digits);
}
console.log(getToken1(priv, {
digits: 6,
period: 30,
}));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment