Since March, Trail of Bits has been working with the Python Software Foundation to add two-factor authentication (2FA) to Warehouse, the codebase that powers PyPI. As of today, PyPI members can enable time-based OTP (TOTP) and WebAuthn (currently in beta). If you have an account on PyPI, go enable your preferred 2FA method before you continue reading!
2018 and 2019 have been big years for two factor authentication:
- W3C and FIDO finalized the Level 1 WebAuthn specification back in March. Chrome and Firefox already have mature support for it, and Safari is expected to follow.
- Upcoming Android releases will allow users to use their phone as a security key, and iOS is expected to do the same.
- Progress on the CTAP2 standard continues. While CTAP2 isn’t required for WebAuthn, future WebAuthn devices will benefit from its transport and authenticator configuration improvements.
- Major sites have begun to discourage the use of SMS and voice codes, which are thoroughly broken as second factors. Google now allows G-Suite admins to enforce 2FA without SMS, preventing users from adding a weak link to their 2FA methods.
All told, there’s never been a better time to add 2FA to your services. Keep reading to find out how you can do it right.
What 2FA is and is not
Before we get into the right decisions to make when implementing two factor authentication, it’s crucial to understand what second factors are and shouldn’t be.
- Second factor methods should not be knowable. Second factor methods are something the user has or is, not something the user knows.
- Second factor methods should not be a replacement for a user’s first factor (usually their password). Because they’re something the user has or is, they are an attestation of identity. WebAuthn is a partial exception to this: it can serve as a single factor due to its stronger guarantees.
- Second factor methods are orderable by security. In particular, WebAuthn is always better than TOTP, so a user who has both enabled should be prompted for WebAuthn first. Don’t let users default to a less secure second factor. If you support SMS as a second factor for legacy reasons, do let users know that they can remove it once they add a better method.
- 2FA implementations should not request the user’s second factor before the first factor. This isn’t really feasible with TOTP anyways, but you might be tempted to do it with a WebAuthn credential’s ID or public key. This doesn’t introduce a security risk per se, but inconsistent ordering only serves to confuse users that already have difficulty understanding the role their security key plays in authentication.
- Recovery codes should be available and should be opt-out with sufficient warnings for users who prefer their accounts to fail-deadly. Recovery codes are not second factors — they circumvent the 2FA scheme. However, users don’t understand 2FA (see below) and will disable it out of frustration if not given a straightforward recovery method. When stored securely, recovery codes represent an acceptable compromise between usability and the soundness of your 2FA scheme.
Your users don’t understand 2FA, and will try to break it
Users, even extremely technical ones (like the average PyPI package maintainer), do not understand 2FA or its constraints. They will, to varying degrees:
|Screenshot their TOTP QR codes and leave them lying in their Downloads folder||Exposed TOTP secrets.||Documentation: Warn users not to save their QR codes|
|Use the same QR to provision multiple TOTP applications||Poor understanding of what/where their second factor is.||Documentation: Tell users to only provision one device|
|Use TOTP applications that allow them to export their codes as unencrypted text||Exposed TOTP secrets; unsafe secret management.||Documentation: Suggest TOTP applications that don’t support unencrypted export|
|Use broken TOTP applications, or applications that don’t respect TOTP parameters.||Incorrect TOTP code generation; confusing TOTP labeling within the application.||Little to none: virtually every TOTP application ignores or imposes additional constraints on provisioning. Use default provisioning parameters!|
|Scan the TOTP QR with the wrong application.||Lost second factor; inability to log in.||Require the user to enter a TOTP code before accepting the provisioning request.|
|Attempt to enter the provisioning URI or secret by hand and get it wrong.||Lost second factor; inability to log in.||Same as above; require a TOTP code to complete provisioning.|
|Label their TOTP logins incorrectly and get them confused.||Mislabeled second factor; inability to log in.||Provide all username and issuer name fields supported by otpauth. Discourage users from using TOTP applications that only support a whitelist of services or require manual labeling.|
|Delete their TOTP secret from their application before your service.||Account lockout.||Documentation: Warn users against doing this, and recommend TOTP applications that provide similar warnings.|
|Save their recovery codes to a text file on their Desktop.||Second factor bypass.||Make recovery codes opt-in, and tell users to save only a print copy of their recovery codes.|
|Get recovery codes for different services mixed up.||Lost bypass; inability to log in.||Prefix recovery codes with the site name or other distinguishing identifier.|
|Ignore their second factors entirely and only use recovery codes.||Not real 2FA.||Track recovery code usage and warn repeat offenders.|
|Attempt to use their old RSA SecurID, weird corporate HOTP fob, or pre-U2F key.||Not supported by WebAuthn.||Provide explicit errors when provisioning fails. Most browsers should do this for pre-U2F keys.|
|Get their hardware keys mixed up.||Mislabeled second factor; inability to log in.||Give your users the ability to label their registered keys with human-friendly names on your service, and encourage them to mark them physically.|
|Give or re-sell their hardware keys without deprovisioning them.||Second factor compromise.||Documentation: Warn users against doing this. For more aggressive security, challenge them to assert each of their WebAuthn credentials on some interval.|
Technical users can be even worse: while writing this post, an acquaintance related a tale of using Twilio and a notification-pushing service to circumvent his University’s SMS-based 2FA.
Many of these scenarios are partially unavoidable, and not all fundamentally weaken or threaten to weaken the soundness of your 2FA setup. You should however be aware of each of them, and seek to user-proof your scheme to the greatest extent possible.
WebAuthn and TOTP are the only things you need
You don’t need SMS or voice codes. If you currently support SMS or voice codes for legacy reasons, then you should be:
- Preventing new users from enabling them,
- Telling current users to remove them and change to either WebAuthn or TOTP, and
- Performing extra logging and alerting on users who still have SMS and/or voice codes enabled.
Paranoid? Yes. But if you hold any cryptocurrency, you probably should be paranoid.
Overkill? Maybe. SIM port attacks remain relatively uncommon (and targeted), despite requiring virtually no technical skill. It’s still better to have 2FA via SMS or voice codes than nothing at all. Google’s own research shows that just SMS prevents nearly all untargeted phishing attacks. The numbers for targeted attacks are more bleak: nearly one quarter of targeted attacks were successful against users with only SMS-based 2FA.
Worried about anything other than SMS being impractical and/or costly? Don’t be. There is a plethora of free TOTP applications for both iOS and Android. On the WebAuthn front, Google will sell you a kit with two security keys for $50. You can even buy a fully-open source key that will work with WebAuthn for $25! Most importantly of all: the fact that TOTP is not as good as a hardware key is not an excuse to continue allowing either SMS or voice codes.
Contrasting TOTP and WebAuthn
TOTP and WebAuthn are both solid choices for adding 2FA to your service and, given the opportunity, you should support both. Here are some factors for consideration:
TOTP is symmetric and simple, WebAuthn is asymmetric and complex
TOTP is a symmetric cryptographic scheme, meaning that the client and server share a secret. This, plus TOTP’s relatively simple code-generation process, makes it a breeze to implement, but results in some gotchas:
- Because clients are required to store the symmetric secret, TOTP is only as secure as the containing application or device. If a malicious program can extract the user’s TOTP secrets, then they can produce as many valid TOTP codes as they want without the user’s awareness.
- Because the only state shared between the client and server in TOTP is the initial secret and subsequent generated codes, TOTP lacks a notion of device identity. As a result a misinformed user can provision multiple devices with the same secret, increasing their attack surface.
- TOTP provides no inherent replay protection. Services may elect to guard against replays by refusing to accept a valid code more than once, but this can ensnare legitimate users who log in more than once within a TOTP window.
- Potentially brute-forceable. Most services use 6 or 8-digit TOTP codes and offer an expanded validation window to accommodate mobility-impaired users (and clock drift), putting an online brute-force just barely on the edge of feasibility. The solution: rate-limit login attempts.
- All of the above combine to make TOTP codes into ideal phishing targets. Both private and nation-state groups have successfully used fake login forms and other techniques to successfully fool users into sharing their TOTP codes.
By contrast, WebAuthn uses asymmetric, public-key cryptography: the client generates a keypair after receiving a list of options from the server, sends the public half to the server for verification purposes, and securely stores the private half for signing operations during authentication. This design results in a substantially more complex attestation model, but yields numerous benefits:
- Device identity: WebAuthn devices are identified by their credential ID, typically paired with a human-readable label for user management purposes. WebAuthn’s notion of identity makes it easy to support multiple security keys per user — don’t artificially constrain your users to a single WebAuthn key per account!
- Anti-replay and anti-cloning protections: device registration and assertion methods include a random challenge generated by the authenticating party, and well-behaved WebAuthn devices send an updated sign counter after each assertion.
- Origin and secure context guarantees: WebAuthn includes origin information during device registration and attestation and only allows transactions within secure contexts, preventing common phishing vectors.
TOTP is free, WebAuthn (mostly, currently) is not
As mentioned above, there are many free TOTP applications, available for just about every platform your users will be on. Almost all of them support Google’s otpauth URI “standard,” albeit with varying degrees of completeness/correctness.
In contrast, most potential users of WebAuthn will need to buy a security key. The relationship between various hardware key standards is confusing (and could occupy an entire separate blog post), but most U2F keys should be WebAuthn compatible. WebAuth is not, however, limited to security keys: as mentioned earlier, Google is working to make their mobile devices function as WebAuthn-compatible second factors, and we hope that Apple is doing the same. Once that happens, many of your users will be able to switch to WebAuthn without an additional purchase.
Use the right tools
TOTP’s simplicity makes it an alluring target for reimplementation. Don’t do that — it’s still a cryptosystem, and you should never roll your own crypto. Instead, use a mature and misuse-resistant implementation, like PyCA’s hazmat.primitives.twofactor.
WebAuthn is still relatively new, and as such doesn’t have as many server-side implementations available. The fine folks at Duo are working hard to remedy that: they’ve already open sourced Go and Python libraries, and have some excellent online demos and documentation for users and implementers alike.
Learn from our work
Want to add 2FA to your service, but have no idea where to start? Take a look at our TOTP and WebAuthn implementations within the Warehouse codebase.
Our public interfaces are well documented, and (per Warehouse standards) all branches are test-covered. Multiple WebAuthn keys are supported, and support for optional recovery codes will be added in the near future.
If you have other, more bespoke cryptography needs, contact us for help.
Pingback: Getting 2FA Right in 2019 – Hacker News Robot
I have a service where I am considering having SMS magic link as the primary login and requiring 2FA for the more secure pages. I keep feeling like TOTP is just not enough but maybe it is. I am using SMS as a poor-mans identity as most of the target market will have a cell phone and we want to ensure we have 1 signup per person. Webauthn is a no-go as the users are very non-technical and the task of ordering and plugging in a key is huge.
Any idea on how to at least mildly enforce 1 human per login (I know this can be circumvented with Twilio and the like but these users will not be doing that)
What are your thoughts on storing totp tokens in your password manager? I was surprised to see that recommended by haveibeenpwned and have felt uneasy about it. I articulated my thoughts here:
Great post! I’m glad to see several of these written out in the open. What does account recovery look like in a WebAuthn & TOTP only account? Should you encourage registering multiple WebAuthn devices?
Hey Mike! Sorry for the late response.
Yep, registering multiple WebAuthn devices is the way to go. Having TOTP as a backup on an account where WebAuthn is your normal 2FA method is also a good solution.
On the political/human side, account recovery when all second factors have been lost should be defined by a playbook. Most of the organizations that I’m familiar with take one of two tacks: either users are completely locked out when they lose all their second factors, or may be permitted back in with sufficient real-world identification (provided that there was an original identity to associate with).
Pingback: Lazy Reading for 2019/06/30 – DragonFly BSD Digest
Awesome post. 2FA is a must-have for keeping accounts secure.
Thanks for writing the article. There might be a slight error in the copy. In the sentence “Label their TOTP logins correctly and get them confused.”, should “correctly” be “incorrectly?”
Additionally, what is your stance on services telling users to print their unencrypted TOTP QR seed for backup? I feel this is bad, as an attacker who gains access to the printed copy can generate all future tokens. If a user wants to opt-in for a paper-based copy, I feel a set of one-time recovery codes with sufficient entropy and length to prevent brute forcing is the better route to go.
Is it reasonable to have different policies in place to define what occurs when a backup code is used, versus a code provided by an authenticator?
Sorry for the late response! That “correctly” is indeed a copy error; I’ve fixed it :-)
I agree that telling users to print their TOTP secret is extremely bad — it violates the 2FA property of unique physical possession, and is nowhere near as intuitive to novice/unfamiliar users as the concept of recovery codes.
I do think it’s reasonable to have different policies in place for backup codes. The specifics of those policies can vary depending on your use case, but some of the reasonable ones I’ve seen in practice are additional verification emails and additional logging, as well as alerts provided to the user when they log in next (telling them that a recovery code has been used on their account).
Does this also apply to how applications store OTP keys? Google Authenticator for instance doesn’t have option to export/backup keys but keep them in SQLite in plain text which leaves more possibility to steal them (rooted device? “privileged” system backups from vendor?)
What about applications that store OTP secret in plain text, such as Google Authenticator; it doesn’t offer any export option, though. Having such data at rest non-encrypted (even though it’s not easily accessible) doesn’t sound good to me: privileged backups (that could leak, be stored unencrypted, etc), vendor’s tools with privileged access, you name it…
What do you think?
Hey! I agree re: unencrypted at-rest tokens being undesirable. That being said, I think it’s a relatively minor concern in the presence of other OTP best practices (especially in the presence of mobile OSes with decent application isolation + system-level encryption).
I agree with the stated advantages of Webauthn and Fido devices regarding their anti-phishing measures but there are also advantages to hardware tokens that are fully self-contained and without a requirement for connecting to a USB port (which brings about separate security concerns). Overall there can be no single perfect solution for all circumstances but perhaps making available as many options as possible (with the possible exception of SMS) is probably the most prudent approach.