Skip to content

Explanation of Changes from 3.0 to 3.1

Yulian Kuncheff edited this page Jun 20, 2022 · 1 revision

This page is written to better explain the semi-breaking changes made from 3.0 to 3.1.

So for a bit of background. the RFC specs for TOTP codes specifies that the secret passed into TOTP just needs to be a string of any content. It gets decoded into bytes, and used as the secret. But Google and Google Authenticator decided that the string needs to be Base32 and decoded from Base32 into the bytes it needs to be.

Since I think all or almost all Authenticators clients (the apps or tools used to generate the code for logging in) are implemented to be compatible with Google Authenticator, they all default to taking in Base32 codes so they can properly work with Google. Now most websites and companies also follow this pattern, all using Base32 because Google did it.

Problem here is the RFC spec doesn't specify anything about Base32, and ignores encoding. This causes a split in logic that I somewhat forgot about when implementing my functions. The main problem was I was enforcing all secrets be Base32, even when isGoogle was set to false.

Now this went under the radar for nearly 3 major versions. I have tests using the RFC data sets, but they didn't catch this because I was encoding the secret as Base32 so that it would go into my functions cleanly, again not realizing why I was doing that and that I shouldn't.

Then recently, Issue #39 was opened, stating that I wasn't generating the same codes as javascript libraries otplib and notp. While I can understand 1 library being different, and I have had issues in the past where I wasn't matching a third party library, but it was a bug in the other library, this was 2 libraries at the same time, with very different implementations.

So once I started digging, I realized the mistake in the code. I was forcing Base32 decoding, but if you used a Base32 valid string, that wasn't supposed to be treated as base32 (aka isGoogle: false) then I would be generating the wrong Base32 code.

So, once I fixed that, fixed my tests, and verified against multiple other libraries, I released 3.1. While this was a fairly important bug fix, I didn't think it was big enough for a Major version change. Also, this was fixing a bug that was generating the wrong codes in this particular situation, and now generating the right codes.

So you might be asking how this affects you?

If you were using isGoogle: true, then nothing changes. The same codes will generate, and you won't notice a thing. (this is also because Google uses a different padding pattern than the RFC, so there were already differences in the codes generated).

If you were using isGoogle: false, then the codes you generate, will be different. But they will be correct, so they shouldn't break any end-user experience. If anything it will improve it. If you were using dart on backend and frontend though, you won't notice a change as the codes will still match because your secret didn't change, it just how I was processing the secret.

Hopefully this makes more sense on why the change was made and how it affects you.

Clone this wiki locally