Closed tommilligan closed 3 years ago
Thanks for reporting and researching the problem.
While your assessment is correct in general applications of the base32 encoding, the specific implementation in PyOTP is specific to the otpauth:// key URI schema defined here: https://github.com/google/google-authenticator/wiki/Key-Uri-Format
In particular, https://github.com/google/google-authenticator/wiki/Key-Uri-Format#secret states:
REQUIRED: The secret parameter is an arbitrary key value encoded in Base32 according to RFC 3548. The padding specified in RFC 3548 section 2.2 is not required and should be omitted.
So the issue you describe appears to be a bug in the speakeasy library's base32 parsing logic as applied to the variant of base32 used in the otpauth schema.
Actually it looks like there are multiple issues here. The inconsistency in provisioning_uri output after calling verify() is definitely a bug, I've addressed it in ee827b4cc6f52317bcf6c0db1a482fc81b203fae.
Actually since the RFC recommends 160 bits, I'm going to go ahead and follow your suggestion, requiring base32 strings of minimum length 32.
Fixes released in v2.6.0, please test.
Thanks for the fast response and patches - after reading the spec I agree that the current behaviour for unpadded values is correct, both for genreation and interpretation.
I will investigate the speakeasy
library further and raise an issue there if required.
I can confirm the behaviour of the new release looks good in our test suite.
Relates to #109 Introduced in 9576711d5de1b0873056ab668b409473a97e3a9c
The current output of the
random_base32()
function is a string of base32 alphabet characters, of 26 length. This is not a valid base32 string, as it does not include padding to a length multiple of 8.This causes problems when it is used as the secret value for a TOTP, like the output of
TOTP.provisioning_uri
changing depending on whether or notTOTP.verify
has previously been called:More importantly, it introduces undefined behaviour when interoperating with other TOTP libraries, such as node's speakeasy. The example secret below is the same in both examples, but produces different codes in each library:
This is flaky behaviour, as a different base32 alphabet string of length 26 does give the same codes between libraries. I imagine how the two libraries handle invalid base32 differs in implementation detail.
To fix this, I suggest increasing the default length of the generated string to 32, which is a multiple of 8.