I’m pretty sure Facebook just makes up a random number and stores it?
Clocks and secrets only needed if the user is providing a number generated on the remote side.
def hotp(key, counter, digits=6, digest='sha1'):
key = base64.b32decode(key.upper() + '=' * ((8 - len(key)) % 8))
counter = struct.pack('>Q', counter)
mac = hmac.new(key, counter, digest).digest()
offset = mac[-1] & 0x0f
binary = struct.unpack('>L', mac[offset:offset+4])[0] & 0x7fffffff
return str(binary)[-digits:].zfill(digits)
def totp(key, time_step=30, digits=6, digest='sha1'):
return hotp(key, int(time.time() / time_step), digits, digest)
> Also, the author struggled to check their implementation. One can easily compare an implementation of many websites by grabbing the QR codes they use for login and importing into your favorite authenticator app and also decoding the QR code to get the secret.
Can you clarify this? It's been some time since I have written the code, AFAIK it was working fine. Did you see any discrepencies when you tested the implementation against a real authenticator app?
However, if your random login code is easily typable then it's usually drawn from a small enough keyspace that any such code is trivially brute-forceable. Like if it's decimal digits you need more than ten.
So in practice people just trust that you are on good terms with your email provider and anyone else with access to your email, and use other mechanisms to limit access to these codes from insiders.
Incidentally, if you think of TOTP as being HMAC(unix mod 30, secret), one idea would be to do public key crypto instead of symmetric HMAC stuff. That's basically what a security key is.
If you additionally made it so that you couldn't phish the security key -- by having the OS + web browser know which apps can ask for which security keys -- you'd have reinvented WebAuthn.
P.S.: Make you sure you have stuffing protection in place against these kinds of six-digit-code auth schemes. A million possibilities is often acceptable for a secondary factor, but it's useless if attackers can just try all million codes.
Since they're in the thread, nice article 'dogacel! I've never seen an article on this that also took the time to dig into HMAC internals and that gnarly DT function.
I agree that an asymmetric key makes much sense. Secret key can be left at the user device while server only contains the public key. That sounds much more secure. I will dig deeper!
True about the stuffing proteciton, I actually want to do further reading on how TOTP is secured from random attacks. Statistically you are expected to crack 1 account in every 1 million attempts in 6 digits codes. Those numbers look pretty huge in the context of security, and a bot-net can potentially brute force couple hundred accounts every day.
Some password managers such as KeepassXC have TOTP incorporated into them and you can have it available right next to the password. It may defeat the purpose of 2FA under some assumptions.
> It may defeat the purpose of 2FA
True, I think this as a mid-step of smooth transition from plain-text passwords to secure keys. You kinda get the benefit of both.
Also those apps are secured much better than a traditional password manager as browser auto-fill for example.
Let me know if it doesn't work. Also would be glad if you can give browser / platform.
I thought anything carried over SSL doesn't have a _significant_ MITM risk.
TOTP devices can be powered offline, which makes it extra secure, as you don't transfer any data around, possibility of leaking it is extremely low.
Random numbers could only work in online flow, where server sends you a one-time code using a secure communication method, such as a trusted phone number or email address.
For text message codes though, there’s plenty of attacks. In authoritarian regimes, government can monitor your text messages directly – I think some protestors in Belarus have lost their Telegram accounts due to this. There’s also the SIM swapping attack, where an attacker pretends to be you and ports out your number: https://en.wikipedia.org/wiki/SIM_swap_scam
I hated popups in the 90's, same as I do now. It's an immediate bounce for me.
>browser / platform
Vivaldi / Linux (Debian)
RFC4226 and RFC6238 do not specify anything but the actual algorithm(s), which is exactly what OP implemented.
This is not what I meant. Storing the TOTP next to the password means you don't really have 2FA as it's a single point of failure. Still better than nothing especially when the objective is what I stated in the first paragraph.
If you store the OTP secret in an HSM, then you can do the same when generating a random number. I'm not aware of anyone actually doing that though (I surely won't have seen even 1% of what's out there, but as a security consultant I get around at least a little bit)
A bit longer but most of it is just boilerplate Java stuff to deal with polymorphism and a base32 implementation. I recall, stripping most of that away in our internal adapted version of that.
Key points:
- generate a 16 character base32 secret and stuff it in a totp link. otpauth://totp/Alice:alice@example.com?secret=JBSWY3DPEHPK3PXP&issuer=Alice
- stuff that in a QR code and show it to the user so they point their phone authenticator app at it to store the secret. We used a js library for this.
- store the secret with the user account in a secure way (we used aes encryption for this)
- when verifying, use the secret, a timestamp in seconds after the epoch divided by 30 (simple normalization step applied on the client as well) and use the user provided number to construct a sha1 hmac and grab the last digits and prepend with zeros. The calculated string should be the same as what the user typed from their token app as long as their clock is in sync.
- we actually implemented a grace period by calculating the before and after code as well so the user isn't screwed over if the number rotates while they were tapping out the code.
While relatively easy to implement, we ran into a lot of friction rolling this out to normal users. Basically non technical people find this stuff super confusing and we had to hand hold quite a few people through the process and we also had to deal with people that lost their secret, or kept on using the wrong code (for a different account). The UX of this stuff is just terrible. Be prepared to deal with a lot of support overhead if you choose to roll this out. A non trivial percentage of users will manage to lock themselves out of their accounts.
> you could still store an expiration time, and limit the number of attempts to use the code
Storing plain-text passwords is bad because:
1. Users re-use passwords
2. An attacker can read the access token from the database (as with SQL injection) or capture it in transit (as on insecure connections) and log in directly (if that is still useful when having, e.g., SQL-injection-based database access)
I do not understand what threat limiting the number of attempts protects against. Even if you have that limit implemented on a hardware level, the attacker either knows the right code or can crack the hash if it's a hash of just a few digits. With something like PAKE you can protect against capturing in transit but on secure channels (e.g., if you already use TLS) that's typically overkill
(Of course, you absolutely need to have rate limiting on OTPs, but that is not to protect against the correct code being read straight from disk by the attacker; it's to protect from guessing the digits, a surprisingly common flaw)
> you're probably sending it to the user insecurely anyway (via email or text message)
(I kind of want to remark about this assumed insecurity: the user needs to be targeted for these to not be adequate. Attackers very rarely go after people to the point where they first compromise an email inbox and then look for your specific service, or drive up to the person with cell tower spoofing equipment. It surely happens in red teaming exercises, spy scenarios, if you have a stalker, and probably more, but it's not the common case. Anyway...)
I don't see how rate limiting and expiration helps against sending it via moderately-secure media either. This is necessarily plain text for random codes because the user would otherwise need a decryption key and then you're basically back at the TOTP scenario makes a difference for at-rest storage security. So with them being plain text, if someone is intercepting your SMSes or sitting in your inbox, they'll either manually trigger the code sending or wait for the legitimate user to do this and then log in. Similar to the previous bit: sure, the security of the transport method is relevant, but not for the security of token storage on the server
https://docs.python.org/3/library/struct.html#format-charact...
You can probably come up with something related to S/KEY (which was kind of a precursor to HOTP) that can be made to work with arbitrary sized one time passwords and is technically asymetric (and quantum resistant at that), but the security trade-offs involved in that and somewhat wild user registration step of S/KEY make HOTP/TOTP more sane choice.
If you additionally made it so that you couldn't phish the security key -- by having the OS + web browser know which apps can ask for which security keys -- you'd have reinvented WebAuthn.
Another key part of FIDO2 phishing protection is challenge-response. The relying party sends some random material/nonce that the authenticator has to sign. This avoids replay attacks that e.g. a time-based method would have, since when a phisher tries to authenticate, the RP will send a different nonce and the phisher cannot sign it.
def hotp(key: bytes, counter: int, digits: int = 6, digest: Literal["sha1", "sha256", "sha512"] = "sha1"):
mac = hmac.digest(
key=base64.b32decode(key.upper() + "=" * ((8 - len(key)) % 8)),
msg=struct.pack(">Q", counter),
digest=digest,
)
offset = mac[-1] & 0x0F
binary: int = struct.unpack(">L", mac[offset : offset + 4])[0] & 0x7FFFFFFF
return str(binary)[-digits:].zfill(digits)
And Pyright doesn’t yell at this version because no type-changing variables here: https://basedpyright.com/?typeCheckingMode=all&code=JYWwDg9g...---
This is actually really helpful. I’m using Pass [1], which requires oathtool for OTP support [2]. I’m currently on a Mac without admin rights (so no Homebrew for me), and compiling oathtool is a PITA. I’ve wanted to put together a pure Python replacement for a while now, but with this it can be a single-file script: https://gist.github.com/notpushkin/7ac32ddf35a0c73bc6f181a1b...
I run a suite of servers and setup scripts that go with them. I can create users and secret keys easily enough using our APIs, but I needed a way to generate TOTP codes on the fly. I got it working on my machine, but sharing it with others was a bit difficult because really the only "logic" was generating the secrets while everything else was static data and storing responses from the APIs.
I ended up making my own API to generate TOTP codes from secrets, <https://totpapi.com>. I try to make it clear it should only be used for testing, but it makes this kind of thing much easier for me. Maybe it will help someone else as well. :)
It's a little more complicated now with push notifications and more complex flows, but for generic TOTP?
This is not the first attempt I made. That was about 11 years ago with https://github.com/gbraad-apps/gauth using JavaScript as an application that would work on a Nokia Symbian/Maemo phone and as a webapp.
Not sure what you mean by this, the server checks the hashed version of the password.
TOTP also doesn't stop the biggest threat that SMS faces: phishing. Saving you from sim-swap attacks is just not a particular huge increase in security posture.
My bank at least offers TOTP as an option, but the huge majority of people are going to enroll with SMS.
Deciding on how to store the credentials is still a hard task. Even storing the secret. Ideally it shouldn't stay as a plain text in your database. If you use cloud, something like KMS can be used for additional security. Also you should still consider replay attacks, rate limits etc.
I agree in the sense that TOTP is hard to implement, no it is not. I hope this article helped people understand how TOTP works.
And before someone asks, the decrypt key is only stored in my head and the app fails silently after a significant delay if the decrypt fails.
What I don't get is how HOTP is anything but a fail waiting to happen if used across an unreliable network. Maybe this explains why I have yet to encounter a real world deployment of HOTP.
How does the server know that the handshake request is not malicious? 2FA that is resettable on demand (without 2FA) effectively voids the whole concept does it not?
With HOTP, the counter is the shared secret --- but a dynamic and potentially unstable one. One failed request or one missed response and the counters on client and server are no longer in sync. Hence, a failure waiting to happen on an unreliable network.
In HOTP, the secret counter is not independent and must remain synchronized between client and server.
A counter that can be synchronized on demand is kinda superfluous --- not really secret and not terribly relevant either. All else being equal, an attacker can sync up just as easily as a legitimate client so why bother with the counter?
I expect HOTP exists somewhere out there in the real world but I have yet to encounter it. Every 2FA I have actual experience with has been TOTP.
A unique counter for each authorization attempt ensures the resulting key is different for each attempt, which makes replay attacks not possible. I agree if you sync the counter two ways, it is better to use a "nonce", a totally random secret each time.