Search code examples
one-time-passwordgoogle-authenticator

What is the difference between Google Authenticator TOTP and regular TOTP


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


Solution

  • 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