daegalus / dart-otp

RFC6238 Time-Based One-Time Password / Google Authenticator Library
MIT License
100 stars 25 forks source link

Padding of TOTP secrets #19

Closed hpoul closed 4 years ago

hpoul commented 4 years ago

Hi, thanks for your library. I try to understand how exactly totp generation works because i've got a user report about problems with some tokens.. https://github.com/authpass/authpass/issues/67

What i don't understand is the padding, which is documented as "TOTP style padding", but i haven't found any mention of it in the actual RFC 6238 .. it only mentions:

Keys SHOULD be of the length of the HMAC output to facilitate interoperability.

but not really how to deal with keys which are not the right length.. and if i'm not completely missing something, it also seems to do no padding in the reference implementation: https://tools.ietf.org/html/rfc6238#appendix-A

Maybe you could point me to the TOTP style padding spec? I just try to understand how it should work.

the only padding i've found whas that of the SHA-2 algorithms, which works quite differently

Pre-processing (Padding): begin with the original message of length L bits append a single '1' bit append K '0' bits, where K is the minimum number >= 0 such that L + 1 + K + 64 is a multiple of 512 append L as a 64-bit big-endian integer, making the total post-processed length a multiple of 512 bits

thanks, Herbert

daegalus commented 4 years ago

Thats a fair question. I came to figure this out when I had to do some interop changes recently to match other TOTP libraries in other languages. There is no hard rule for it. It is why I made it configurable. and in the latest code, I added more flags. Apparently implementations aren't following the spec and adding unintended behavior. Mostly because the spec doesn't actually specify how to pad something.

In Appendix A, they do this in a hardcoded manner:

public static void main(String[] args) {
         // Seed for HMAC-SHA1 - 20 bytes
         String seed = "3132333435363738393031323334353637383930";
         // Seed for HMAC-SHA256 - 32 bytes
         String seed32 = "3132333435363738393031323334353637383930" +
         "313233343536373839303132";
         // Seed for HMAC-SHA512 - 64 bytes
         String seed64 = "3132333435363738393031323334353637383930" +
         "3132333435363738393031323334353637383930" +
         "3132333435363738393031323334353637383930" +
         "31323334";

It might seem that they are just making a longer secret, but that is the intended way to pad something according to their test vectors.

Another reference to it is: https://www.npmjs.com/package/otplib#length-of-secrets

Also, i implemented their test vectors in my tests. The only way they worked is with that style of padding.

hpoul commented 4 years ago

thanks for your fast answer. that's helpful, at least some mention of padding.. :-) i'm not quite sold on the RFC test data though.. as you said, looks more like a longer secret.. maybe i'll try collect a few more test vectors from different implementations.. i don't quite understand why there are services with secrets of non-conforming lengths in the first place 🤦️

but i guess that setting to disable padding is a good idea.. will try that out.. thanks..