Skip to content

Instantly share code, notes, and snippets.

@alancasagrande
Last active November 16, 2020 12:19
Show Gist options
  • Save alancasagrande/d20d259fa8c4939b35e8a15f5ece0a59 to your computer and use it in GitHub Desktop.
Save alancasagrande/d20d259fa8c4939b35e8a15f5ece0a59 to your computer and use it in GitHub Desktop.
import crypto from 'crypto';
import base32Decode from 'base32-decode';
export function generateHOTP(secret, counter) {
const decodedSecret = base32Decode(secret, 'RFC4648');
const buffer = Buffer.alloc(8);
for (let i = 0; i < 8; i++) {
buffer[7 - i] = counter & 0xff;
counter = counter >> 8;
}
// Step 1: Generate an HMAC-SHA-1 value
const hmac = crypto.createHmac('sha1', Buffer.from(decodedSecret));
hmac.update(buffer);
const hmacResult = hmac.digest();
// Step 2: Generate a 4-byte string (Dynamic Truncation)
const offset = hmacResult[hmacResult.length - 1] & 0xf;
const code =
((hmacResult[offset] & 0x7f) << 24) |
((hmacResult[offset + 1] & 0xff) << 16) |
((hmacResult[offset + 2] & 0xff) << 8) |
(hmacResult[offset + 3] & 0xff);
// Step 3: Compute an HOTP value
return `${code % 10 ** 6}`.padStart(6, '0');
}
export function generateTOTP(secret, window = 0) {
const counter = Math.floor(Date.now() / 30000);
return generateHOTP(secret, counter + window);
}
export function verifyTOTP(token, secret, window = 1) {
for (let errorWindow = -window; errorWindow <= +window; errorWindow++) {
const totp = generateTOTP(secret, errorWindow);
if (token === totp) {
return true;
}
}
return false;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment