Have you wanted to throw away your expensive internet bill and use your neighbor's insecure wifi? Was the only thing holding you back the Honeybadger single-factor auth flow? Well, have I got news for you.
Announcing: Two Factor Authentication (2FA)
All kidding aside, we take security very seriously. We are thrilled to be able to provide an important tool to help keep our users and their data more secure.
I figure I could just explain how to enable 2FA, but what's the fun in that? How about we take a light-hearted glimpse into the wild world of cryptography and figure out how 2FA works.
If fun's not your thing, you can totally go straight to How to enable, my feelings won't be hurt.
⚠️ Warning: Some theory below
Many 2FA implementations are built upon an algorithm called Time-Based One-Time Password (TOTP). Rolls right off the tongue.
One-Time?
Yes, the beauty in this method is that you are not transmitting the same "password" every time you log in. Using an authenticator application, a one-time "password" is generated and remains valid for a short duration (usually 30 seconds), which greatly dilutes the effectiveness of a malcolm-in-the-middle attack.
Although there are some complicated bits, the overall implementation is relatively simple.
The Secret
A key point to understand about TOTP is that the server (verifier) and the authenticator (prover) must have a shared secret. This is the real "password", however it's only transmitted once (so I guess this is one-time as well)!
The recommended way to consume the secret is via QR code. When you scan the QR code into your smartphone, the secret is encoded within a standard URI that looks something like this:
otpauth://totp/Honeybadger.io:inigo@honeybadger.io?secret=base32-secret-key
There are a few benefits to providing a QR code (as opposed to exposing the plaintext secret):
- Who has time to type in a 32 character code?
- Your authenticator app can provide helpful labels for your one-time password (referencing the email and issuer in the URI). This is especially useful when you use 2FA on multiple services.
- Finally a good reason to use a QR code!
There are some additional (mostly ignored) parameters for the otpauth
scheme, which you can view here.
MAC and Me
Once the secrets have been agreed upon 🤝, you will be asked (at some point) to provide a One-Time Password. This is where things get real.
TOTP is built on top of another algorithm called HMAC-Based One-Time Password (HOTP), which itself is based on Hash-based Message Authentication Code (HMAC). It's all one big crypto onion.
TOTP & HOTP are basically variations on what message gets hashed. HMAC is really where the magic happens. HMAC produces a digest, which is the output of running some value through a hashing algorithm. Let's see it in action with some ruby code:
OpenSSL::HMAC.digest("SHA1", "my-secret-key", "the-earth-is-flat")
=> "R\xABp\xCB\xEC\xFEJ\r#\x02\xC8\xAB\x96\xB68\v\xDA0\xD7z"
Wow, look at that binary string! That's our Message Authentication Code (MAC).
We chose SHA1
as our hashing function (as does our implementation of TOTP). There are many other functions to choose from, each with their own security and performance implications. What's radical about a hashing algorithm is that for any input, we will get the same output, every time! It will also produce a vastly different and unique output from any subtle input changes (ahem, depending on the hashing function, but don't worry we are not terribly concerned with collisions here).
Okay, so it's called a Message Authentication Code because it can be used to prove that a received message is authentic. If you and I know the secret (my-secret-key
), I can send you the message the-earth-is-flat
along with the MAC. All you need to do is run the exact same digest function, with the same inputs, and if the MACs match up, you have authenticated the message! You know it wasn't tampered with and only could have come from me!
😎
Timecode
So let's bring this back to TOTP. It's time to talk about time. You see, with TOTP, instead of digesting a preposterous message like the-earth-is-flat
, we instead use a time step count (converted to a string) as the input. If you open your authenticator app, and watch when the codes get switched, you might notice a pattern. That's right, say it with me: "They switch on the minute and the half minute", very good.
This makes calculating the message a little simpler. In this case we use the rounded down count of 30 seconds (the time step) since the beginning of time (well, unix time). So, in ruby again:
Time.now.to_i / 30
=> 51772900
Ah hah! Thats our Message.
So in our Authenticator app, we use our trusty digest function to get a new HMAC:
hmac = OpenSSL::HMAC.digest("SHA1", "avwe8aw71j2boib23jkbjk32", "51772900")
=> "H\x7F\xC1\xACL\xDA\xDB\xE7DQ\x91kE\x1C\xE3,c\nH\xA0"
get_otp_code(hmac)
=> "332204"
The last step is to get the number code from this binary string. Since there is some bit manipulation required, I am going to hide behind our anonymous get_otp_code
function for now. If you want, you can read about how it works in the HOTP Spec.
When you submit the code to the server, it performs the exact same digest operation and compares the codes. When they match, you're in! Because we are working with time, latency is a factor. There are strategies to account for possible delays. Understanding that will be your homework, if you want.
That's the gist of how Time-Based One-Time Passwords work!! 🎉 Feel free to send me a thank you email after you use this info to be the life of your next dinner party!
Implementation
While I think it's important to understand how these things work, it's strongly discouraged to roll-your-own anything crypto related. One of the great things about the Ruby / Rails ecosystem is that you can often find a gem (sometimes many) to solve your problem.
We use devise here at Honeybadger and there is a great devise plugin called two_factor_authentication. There are a few 2FA implementations to choose from, so here are some reasons we chose this one:
- Simple setup (a migration, some config & initializers sprinkled about)
- It allows for the One-Time code to be entered on its own page (after valid user/password)
- Acceptable defaults, yet simple to override
- It also has a max login attempts check, which is recommended by the spec
How to enable
Setup is simple: Navigate to the User Settings > Authentication page, select "Set Up Two Factor Authentication" and follow the directions to sync up the secret using Authy or Google Authenticator (or really any other TOTP app).
That's it! Next time you login, we will prompt you for your authenticator code. Fill it in and go with complete and utter peace of mind. 🧘
Security and simplicity? Can it get any better?
One last thing: Pwned Password Check
I've got a quick one for all you proponents of weak passwords. We also integrated with the devise-pwned_password gem. From now on, when you perform any operation that requires the password, we check to make sure the proposed password is not prevalent within any known data breaches. If you are adding a new password, and it fails the check, we won't allow it.
If you aren't already, we highly recommend using a password manager (1Password ahem).
One of the more interesting bits of Pwned Password is how your password stays secure even while checking against a database from a 3rd party service. It's called k-Anonymity
and you can get an overview about it here.
Well, that's all we have for now. Until next time! 👋