Google Authenticator app produces tokens which we try to reproduce with an OTP library (otplib) as follows. Even when run at the same time, one matches and the other doesn't
import { authenticator, totp, hotp } from 'otplib'
const secret = "NZQKPMNENSPOWUQZ"
console.log(authenticator.generate(secret)) // matches the app token
console.log(totp.generate(secret)) // does not match
Why do the two generated tokens differ? One difference between the options for each generator is the encoding so also tried this with same results
totp.options = {encoding: 'hex'} // default is 'ascii'
How to generate the same code with totp
(or hotp
) as with authenticator
I had the same question as you and I figured out why these objects generate different values.
The value NZQKPMNENSPOWUQZ
is a base32 encoded string. So you have decode it, convert it to hex, then set the encoding to hex
on the totp
object, and you're good. The authenticator
object does all that for you. The totp
object does not.
import { authenticator, totp, hotp } from 'otplib';
import { KeyEncodings } from 'otplib/core';
import base32Decode from 'base32-decode'; // for ease of use
const secret = 'NZQKPMNENSPOWUQZ'; // this is base32 encoded
console.log(authenticator.generate(secret));
// decode base32 key to hex string
const hexSecret = Buffer.from(base32Decode(secret, 'RFC4648')).toString('hex');
// set totp option encoding to 'hex'
totp.options = { encoding: KeyEncodings.HEX };
// pass in the decoded hex secret
console.log(totp.generate(hexSecret));
// BONUS
// generate the code with hotp
// all three use hotp under the hood
// generate the counter correctly and now you have totp
hotp.options = { encoding: KeyEncodings.HEX };
const step = 30; // seconds per code
const counter = Math.floor(Date.now() / step / 1000);
console.log(hotp.generate(hexSecret, counter));
Source: This is where I found the authenticator
object automatically decodes the base32 secret