speakeasyjs / speakeasy

**NOT MAINTAINED** Two-factor authentication for Node.js. One-time passcode generator (HOTP/TOTP) with support for Google Authenticator.
MIT License
2.7k stars 228 forks source link

Same secret with Google Authenticator and SpeakEasy, different tokens #102

Closed TomasHubelbauer closed 6 years ago

TomasHubelbauer commented 6 years ago

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:

TomasHubelbauer commented 6 years ago

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.

markbao commented 6 years ago

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.

TomasHubelbauer commented 6 years ago

@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?

TomasHubelbauer commented 6 years ago

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

var token = speakeasy.totp({
  secret: secret.base32,
  encoding: 'base32'
});
markbao commented 6 years ago

No problem, glad to hear this solved the issue.

ww-AdamJayMarinovic commented 4 years ago

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' });