Day 015 - #100DaysOfCode
2FA and OTP
I have been thinking some things around unique hashes and One Time Passwords (OTP) as well as how Two Factor Authentication (2FA) works so I thought I would look it up.
I found this reasonably description on YouTube
How does the Google Authenticator Work? HOTP TOTP Difference 2FA Authentication
The author mentions the implementation and rfc6238 - TOTP: Time-Based One-Time Password Algorithm. This also reminded me of the time I wanted to better understand YubiKey. Which led me to find the OpenSource alternative OnlyKey. That led me to think how one can build this with an Arduino and sure enought there are projects such as https://github.com/lucadentella/TOTP-Arduino all based on RFC6238.
Basically the idea is you have a secret and a counter. The counter is just unix time. To make the OTP not rotate too quickly time is usually divided by 30 seconds and depending on the implementation, some level of drift in the code entered to allow for network latency or clocks not being the same.
The HMAC-based One-Time Password (HOTP) algorithm then sha1
HMAC’s the secret and counter, looks at the last byte to work out which part of the generated hash to read. Then reads the parts of the hash and finally converts it to a 6 digit number which is the secret. If you type it in, the server will check it and you are good to go.
A common way to share the secret is via a QR code which you can then read into an app like Google Authenticator and from there authenticator will generate the codes for you as needed.
Ofcourse what happens on the server side. I had assumed that there was some kind of 1 way encryption going on, a bit like with passwords. With a password, you type it in, it gets processed and is compared with the result that is stored on the server. It is not trivial to find a password to create the same result and there is no way to change the result back into the password.
Well with OTP it seems that the secret is stored encrypted on the server. It is then decrypted and runs through the same algorithm to generate the same code. This means that if anyone can get to the server with the keys for encryption, they can easily decrypt the secret and start generating any number of one time passwords for any of the users. Note that at this point in time knowing the password is harder as they cannot reverse that.
If I have rails using [Devise] (https://github.com/heartcombo/devise) with Devise-two-factor which uses ROTP under the covers this is as simple as
The middle column above is the secret in this case. Anyone with that secret will now be able to easily generate the OTP with
Ok but the secret is stored encrypted in the database
psql -d rails_2fa_play_development \
-c "SELECT email, encrypted_otp_secret FROM users WHERE email = 'user_1@example.com';"
email | encrypted_otp_secret
--------------------+----------------------------------------------------------
user_1@example.com | zxEW23kkTcTbOP06xlxd5o+UHfhwG3FiGiqF95ZOzUG3F7s7fVHtlQ==+
|
(1 row)
so how do we decrypt that.
Well first I re-recreated a rails app with devise and devise-two-factor which you can find here https://github.com/saramic/100-days-of-code/tree/main/2fa_play/rails_2fa_play
Then re-running the above commands, you can see that some of these things are setup differently by default
psql -d rails_2fa_play_development \
-c "SELECT email, otp_secret FROM users;"
email | otp_secret
-------+------------------------------------------
m@m.m | {"p":"eBD8EHxoL7nbW1lqjrE2dCnBui3Bo0Hr",
"h":{"iv":"IMEXj/ujOBwsjfrI",
"at":"0F7+0GLgMXUSCyu7bJePmA==",
"e":"QVNDSUktOEJJVA=="}}
and in rails
bin/rails runner 'pp User.all.map{|u| [u.email, u.otp_secret] }'
[["m@m.m", "6H4ZOX4EFJCWGEZDADWEN5YD"]]
finally to generate an OTP
bin/rails runner "pp ROTP::TOTP.new(
User.find_by(email: 'm@m.m').otp_secret).now"
"090549"
Next time
so how do we decrypt this? and how does the algorithm work? and how do we rate other frameworks like Rodauth