Simple Authentication and Security Layer (SASL, RFC4422) is the framework that was abstracted from the IMAP and POP protocols. Among the most popular mechanisms are PLAIN (clear-text passwords, usually under TLS), CRAM-MD5 (RFC2195), and GSSAPI (for Kerberos V5). The DIGEST-MD5 mechanism was an attempt to improve upon the CRAM-MD5 mechanism, but ended up introducing a lot of complexity and insufficient desirable features and deployment was a mess — read RFC6331 for background on why it has been deprecated.
The effort to develop SCRAM (RFC5802) came, as far as I can tell, from the experiences with DIGEST-MD5 and the desire to offer something better than CRAM-MD5. In protocol design discussions, SCRAM is often still considered as “new” even though the specification was published in 2011 and even that had been in the making for several years. Developers that implement IMAP and SMTP still usually start out with supporting PLAIN and CRAM-MD5. The focus of this blog post is to delve in why this is, and inspire next steps in this area. My opinion around this topic has existed for a couple of years already, formed while implementing SCRAM in GNU SASL, and my main triggers to write something about them now are 1) Martin Lambers‘ two-post blog series that first were negative about SCRAM and then became positive, and 2) my desire to work on or support new efforts in this area.
Let’s take a step back and spend some time analyzing PLAIN and CRAM-MD5. What are the perceived advantages and disadvantages?
Advantages: PLAIN and CRAM-MD5 solves the use-case of password-based user authentication, and are easy to implement.
Main disadvantages with PLAIN and CRAM-MD5:
- PLAIN transfers passwords in clear text to the server (sometimes this is considered an advantage, but from a security point of view, it isn’t).
- CRAM-MD5 requires that the server stores the password in plaintext (impossible to use a hashed or encrypted format).
- Non-ASCII support was not there from the start.
A number of (debatable) inconveniences with PLAIN and CRAM-MD5 exists:
- CRAM-MD5 does not support the notion of authorization identities.
- The authentication is not bound to a particular secure channel, opening up for tunneling attacks.
- CRAM-MD5 is based on HMAC-MD5 that is cryptographically “old” (but has withhold well) – the main problem today is that usually MD5 is not something you want to implement since there is diminishing other uses for it.
- Servers can impersonate the client against other servers since they know the password.
- Neither offer to authenticate the server to the client.
If you are familiar with SCRAM, you know that it solves these issues. So why hasn’t everyone jumped on it and CRAM-MD5 is now a thing of the past? In the first few years, my answer was that things take time and we’ll see improvements. Today we are ten years later; there are many SCRAM implementations out there, and the Internet has generally migrated away from protocols that have much larger legacy issues (e.g., SSL), but we are still doing CRAM-MD5. I think it is time to become critical of the effort, and try to learn from past. Here is my attempt at summarizing the concerns I’ve seen come up:
- The mechanism family concept add complexity, in several ways:
- The specification is harder to understand.
- New instances of the mechanism family (SCRAM-SHA-256) introduce even more complexity since they tweak some of the poor choices made in the base specification.
- Introducing new hashes to the family (like the suggested SHA3 variants) adds deployment costs since databases needs new type:value pairs to hold more than one “SCRAM” hashed password.
- How to negotiate which variant to use is not well-defined. Consider if the server only has access to a SCRAM-SHA-1 hashed password for user X and a SCRAM-SHA-256 hashed password for user Y. What mechanisms should it offer to an unknown client? Offering both is likely to cause authentication failures, and the fall-back behaviour of SASL is poor.
- The optional support for channel bindings and the way they are negotiated adds complexity.
- The original default ‘tls-unique’ channel binding turned out to be insecure, and it cannot be supported in TLS 1.3.
- Support for channel bindings requires interaction between TLS and SASL layers in an application.
- The feature that servers cannot impersonate a client is dubious: the server only needs to participate in one authentication exchange with the client to gain this ability.
- SCRAM does not offer any of the cryptographic properties of a Password-authenticated key agreement.
What other concerns are there? I’m likely forgetting some. Some of these are debatable, and were intentional design choices.
Can we save SCRAM? I’m happy to see the effort to introduce a new channel binding and update the SCRAM specifications to use it for TLS 1.3+. I brought up a similar approach back in the days when some people were still insisting on ‘tls-unique’. A new channel binding solves some of the issues above.
It is hard to tell what the main reason for not implementing SCRAM more often is. A sense of urgency appears to be lacking. My gut feeling is that to an implementer SCRAM looks awfully similar to DIGEST-MD5. Most of the problems with DIGEST-MD5 could be fixed, but the fixes adds more complexity.
How to proceed from here? I see a couple of options:
- Let time go by to see increased adoption. Improving the channel binding situation will help.
- Learn from the mistakes and introduce a new simple SCRAM, which could have the following properties:
- No mechanism family, just one mechanism instance.
- Hash is hard-coded, just like CRAM-MD5.
- TLS and a channel binding is required and always used.
- Review one of the PAKE alternatives and specify a SASL mechanism for it. Preferably without repeating the mistakes of CRAM-MD5, DIGEST-MD5 and SCRAM.
- Give up on having “complex” authentication mechanisms inside SASL, and help some PAKE variant become implemented through a TLS library, and SASL applications should just use EXTERNAL to use TLS user authentication.
I feel the following XKCD is appropriate here.