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 correctly 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
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.