speakeasyjs / speakeasy

**NOT MAINTAINED** Two-factor authentication for Node.js. One-time passcode generator (HOTP/TOTP) with support for Google Authenticator.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Same secret with Google Authenticator and SpeakEasy, different tokens

TomasHubelbauer opened this issue · comments

Hey, I have obtained a TOTP shared secret key from GitHub and I have manually inserted the secret to both Google Authenticator and SpeakEasy and verified that the values are correct. I did this twice manually and once using the QR code from GitHub to set up Google Authenticator.

Here's my SpeakEasy code, I am using literally just this line:

console.log(speakeasy.totp({ secret: '<the secret>' }));

The secret is a string in the format of 16 lowercase letters and numbers as provided by GitHub.

Google Authenticator and SpeakEasy give me totally different code. I have tried to cross the time window boundary to check if maybe SpeakEasy was giving me a token one window too old or too new, but they just seems to be completely unrelated. Needless to say GitHub won't accept my TOTP token, but will Google Authenticator's.

Do I miss options which I should be using? The README.md doesn't show my use case of generating tokens from pre-shared secrets so I am not sure what I could be missing, but I think the defaults in options match what Google Authenticator is doing, so I am confused as to why the difference exists.

Steps to Reproduce:

  • Go through the GitHub flow's for reconfiguring 2FA
  • On the QR code page, do not use the QA code but click for text code and copy that
  • Insert the secret manually into Google Authenticator and triple check it
  • Insert it also into the code above and quadruple check it
  • Confirm they do not match

Interestingly, I have the same problem with NOTP: guyht/notp#47. SpeakEasy and NOTP give me the same code, but it is not accepted by GitHub and is different from Google Authenticator and Microsoft Authenticator, which both give me the same, valid, code.

Thanks for the detailed report. I saw you mentioned in the other thread that you checked your time already and it was accurate. I think the issue here is that the given key is base32 encoded, but the totp() function’s default encoding is ascii. Specify the encoding as base32 (see documentation) and let me know if that fixes the issue.

@markbao Yeah, that fixed it! Thank you. I didn't realize the encoding was BASE63, I tried to specify encoding before, but with ascii which was no good. Should I contribute an example to the README?

Actually, I was incorrect. The example is already in the README, I just missed it.

var token = speakeasy.totp({
  secret: secret.base32,
  encoding: 'base32'
});

No problem, glad to hear this solved the issue.

Just in case someone else comes across this problem as well. If you are having trouble matching Google Authentication token to speakeasy, make sure that if you are generating an otpauthURL that you have set the encoding option to base32 as below.

const url = speakeasy.otpauthURL({ secret: secret.base32, label: 'MyWebApp', encoding: 'base32', issuer: 'Me' });