RFC 2831

Using Digest Authentication as a SASL Mechanism

๐Ÿ“… published May 2000, from ietf

1. Introduction โœจ

Okay bestie, this is RFC 2831, and it's literally the doc that explains how to use Digest Authentication as a SASL mechanism ๐Ÿ’–

Quick title-block tea ๐Ÿซ–: it's from the Network Working Group, and the authors are P. Leach (Microsoft) and C. Newman (Innosoft). The category is Standards Track, and the date is May 2000. The title block doesn't list anything it obsoletes or updates, so this one doesn't replace or change any other RFC ๐Ÿงš

Status of this Memo ๐Ÿ“œ

This document specifies an Internet standards track protocol for the Internet community, and requests discussion and suggestions for improvements. Please refer to the current edition of the "Internet Official Protocol Standards" (STD 1) for the standardization state and status of this protocol. Distribution of this memo is unlimited.

Copyright (C) The Internet Society (2000). All Rights Reserved.

Abstract ๐ŸŒธ

Okay so this spec defines how HTTP Digest Authentication [Digest] can be used as a SASL [RFC 2222] mechanism for any protocol that has a SASL profile. It's meant as an improvement over CRAM-MD5 [RFC 2195], and as a convenient way to support one single authentication mechanism for web, mail, LDAP, and other protocols. It's giving unified login energy ๐Ÿ’…

Table of Contents ๐Ÿฆ‹

   1 INTRODUCTION.....................................................2
    1.1 CONVENTIONS AND NOTATION......................................2
    1.2 REQUIREMENTS..................................................3
   2 AUTHENTICATION...................................................3
    2.1 INITIAL AUTHENTICATION........................................3
     2.1.1 Step One...................................................3
     2.1.2 Step Two...................................................6
     2.1.3 Step Three................................................12
    2.2 SUBSEQUENT AUTHENTICATION....................................12
     2.2.1 Step one..................................................13
     2.2.2 Step Two..................................................13
    2.3 INTEGRITY PROTECTION.........................................13
    2.4 CONFIDENTIALITY PROTECTION...................................14
   3 SECURITY CONSIDERATIONS.........................................15
    3.1 AUTHENTICATION OF CLIENTS USING DIGEST AUTHENTICATION........15
    3.2 COMPARISON OF DIGEST WITH PLAINTEXT PASSWORDS................16
    3.3 REPLAY ATTACKS...............................................16
    3.4 ONLINE DICTIONARY ATTACKS....................................16
    3.5 OFFLINE DICTIONARY ATTACKS...................................16
    3.6 MAN IN THE MIDDLE............................................17
    3.7 CHOSEN PLAINTEXT ATTACKS.....................................17
    3.8 SPOOFING BY COUNTERFEIT SERVERS..............................17
    3.9 STORING PASSWORDS............................................17
    3.10 MULTIPLE REALMS.............................................18
    3.11 SUMMARY.....................................................18
   4 EXAMPLE.........................................................18
   5 REFERENCES......................................................20
   6 AUTHORS' ADDRESSES..............................................21
   7 ABNF............................................................21
    7.1 AUGMENTED BNF................................................21
    7.2 BASIC RULES..................................................23
   8 SAMPLE CODE.....................................................25
   9 FULL COPYRIGHT STATEMENT........................................27

1. Introduction ๐Ÿซถ

This spec describes how to use HTTP Digest Access Authentication as a SASL mechanism. The authentication type tied to the Digest SASL mechanism is "DIGEST-MD5" ๐Ÿ”ฅ

This spec is meant to be upward compatible with the "md5-sess" algorithm of HTTP/1.1 Digest Access Authentication, which is specified in [Digest]. The only difference in the "md5-sess" algorithm is that some directives that aren't needed in a SASL mechanism have had their values defaulted. Lowkey clean ๐Ÿงผ

There's one new feature for use as a SASL mechanism: integrity protection on application protocol messages after an authentication exchange โœจ

Also, compared to CRAM-MD5, DIGEST-MD5 prevents chosen plaintext attacks, and it permits the use of third party authentication servers, mutual authentication, and optimized reauthentication if a client has recently authenticated to a server. Main character energy ๐Ÿ‘‘

1.1. Conventions and Notation ๐Ÿ“

This spec uses the same ABNF notation and lexical conventions as the HTTP/1.1 specification. See appendix A.

Let { a, b, ... } be the concatenation of the octet strings a, b, and so on.

Let H(s) be the 16 octet MD5 hash [RFC 1321] of the octet string s.

Let KD(k, s) be H({k, ":", s}), i.e., the 16 octet hash of the string k, a colon, and the string s.

Let HEX(n) be the representation of the 16 octet MD5 hash n as a string of 32 hex digits. Alphabetic characters are always lower case, since MD5 is case sensitive ๐ŸŒˆ

Let HMAC(k, s) be the 16 octet HMAC-MD5 [RFC 2104] of the octet string s, using the octet string k as a key.

The value of a quoted string constant as an octet string does not include any terminating null character.

1.2. Requirements ๐Ÿ’œ

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 [RFC 2119]. Those words are the rules of the game, so read them carefully ๐Ÿ™ˆ

An implementation is not compliant if it fails to satisfy one or more of the MUST level requirements for the protocols it implements. An implementation that satisfies all the MUST level and all the SHOULD level requirements for its protocols is said to be "unconditionally compliant". One that satisfies all the MUST level requirements but not all the SHOULD level requirements for its protocols is said to be "conditionally compliant." So the vibe check is: MUST is the floor, and SHOULD is the glow-up ๐Ÿ’…

2. Authentication ๐ŸŒท

This section describes how to use Digest as a SASL authentication mechanism, and the following sections go through it step by step ๐Ÿงš

2.1. Initial Authentication ๐ŸŽ€

If the client has not recently authenticated to the server, then it must perform "initial authentication", as defined in this section. If it has recently authenticated, then a more efficient form is available, which is defined in the next section. Okay so it's basically: fresh login gets the full routine, and a recent login gets the speedrun ๐Ÿƒโ€โ™€๏ธ๐Ÿ’จ


Part 1 of 6 is done, and part 2 is coming up next, so stay tuned ๐Ÿฆ‹

2.1.1. Step One โœจ

okay bestie, the server kicks things off by sending a challenge ๐Ÿ’Œ. The data inside it has to be a string that follows the rules for a digest-challenge, and it's giving very specific vibes:

   digest-challenge  =
         1#( realm | nonce | qop-options | stale | maxbuf | charset
               algorithm | cipher-opts | auth-param )

        realm             = "realm" "=" <"> realm-value <">
        realm-value       = qdstr-val
        nonce             = "nonce" "=" <"> nonce-value <">
        nonce-value       = qdstr-val
        qop-options       = "qop" "=" <"> qop-list <">
        qop-list          = 1#qop-value
        qop-value         = "auth" | "auth-int" | "auth-conf" |
                             token
        stale             = "stale" "=" "true"
        maxbuf            = "maxbuf" "=" maxbuf-value
        maxbuf-value      = 1*DIGIT
        charset           = "charset" "=" "utf-8"
        algorithm         = "algorithm" "=" "md5-sess"
        cipher-opts       = "cipher" "=" <"> 1#cipher-value <">
        cipher-value      = "3des" | "des" | "rc4-40" | "rc4" |
                            "rc4-56" | token
        auth-param        = token "=" ( token | quoted-string )

Okay, now let's talk about what each directive actually means ๐Ÿง 

realm ๐Ÿ  Mechanistically, it's a string that helps users figure out which username and password to use, in case they have different ones for different servers. Conceptually, it's the name of a collection of accounts that might include the user's account. This string should contain at least the name of the host doing the authentication, and it might also say which group of users could have access. An example is "registered_users@gotham.news.example.com". This directive is optional. If it's not there, the client SHOULD ask the user for it or be able to compute a default, and a plausible default might be the realm the user gave when they logged in to the client system. Multiple realm directives are allowed, in which case the user or client must choose one realm to supply the username and password for. ๐Ÿ’…

nonce ๐ŸŽฒ This is a server-specified data string that MUST be different every time a digest-challenge is sent as part of initial authentication. It's recommended that this string be base64 or hexadecimal data. Since the string gets passed as a quoted string, the double-quote character isn't allowed unless it's escaped (see section 7.2). The contents of the nonce are implementation dependent, and the security of the implementation depends on a good choice. It is RECOMMENDED that it contain at least 64 bits of entropy. The nonce is opaque to the client, so no peeking ๐Ÿ™ˆ. This directive is required and MUST appear exactly once. If it's not present, or if there are multiple instances, the client should abort the authentication exchange. ๐Ÿ˜ญ

qop-options ๐Ÿ” This is a quoted string with one or more tokens showing the "quality of protection" values the server supports. The value "auth" means authentication. The value "auth-int" means authentication with integrity protection. The value "auth-conf" means authentication with integrity protection and encryption, so it's the full glam โœจ. This directive is optional, and if it's not present it defaults to "auth". The client MUST ignore unrecognized options. If the client recognizes no option at all, it should abort the authentication exchange.

stale ๐Ÿฅ€ The stale directive is not used in initial authentication. Check out the next section for how it's used in subsequent authentications. This directive may appear at most once. If there are multiple instances, the client should abort the authentication exchange.

maxbuf ๐Ÿ“ฆ This is a number showing the size of the biggest buffer the server can receive when using "auth-int" or "auth-conf". If this directive is missing, the default value is 65536. This directive may appear at most once. If there are multiple instances, the client should abort the authentication exchange.

charset ๐ŸŒ If this directive is present, it means the server supports UTF-8 encoding for the username and password. If it's not present, the username and password must be encoded in ISO 8859-1 (which US-ASCII is a subset of). This directive is needed for backwards compatibility with HTTP Digest, which only supports ISO 8859-1. It may appear at most once. If there are multiple instances, the client should abort the authentication exchange.

algorithm ๐Ÿงฎ This one is required for backwards compatibility with HTTP Digest, which supports other algorithms. This directive is required and MUST appear exactly once. If it's not present, or if there are multiple instances, the client should abort the authentication exchange.

cipher-opts ๐ŸŽ€ This is a list of ciphers the server supports. This directive must be present exactly once if "auth-conf" is offered in the "qop-options" directive. In that case, the "3des" and "des" modes are mandatory-to-implement. The client MUST ignore unrecognized options. If the client recognizes no option, it should abort the authentication exchange. Here's the cipher lineup:

  • des ๐Ÿ”’ the Data Encryption Standard (DES) cipher [FIPS] in cipher block chaining (CBC) mode with a 56 bit key.
  • 3des ๐Ÿ”’๐Ÿ”’ the "triple DES" cipher in CBC mode with EDE, using the same key for each E stage (aka "two keys mode") for a total key length of 112 bits.
  • rc4, rc4-40, rc4-56 ๐ŸŒ€ the RC4 cipher with a 128 bit, 40 bit, and 56 bit key, respectively.

auth-param ๐Ÿงฉ This construct is there for future extensions, and it may appear more than once. The client MUST ignore any unrecognized directives.

๐Ÿšจ Okay, this one's important! If you're using this as a SASL mechanism, there are a few changes from HTTP's digest-challenge. The following Digest options (called "directives" in HTTP terminology) are unused. That means they MUST NOT be sent, and they MUST be ignored if received: opaque domain

Last but not least, the size of a digest-challenge MUST be less than 2048 bytes. Keep it small, bestie ๐Ÿซถ

2.1.2. Step Two โœจ

Okay bestie, step two is where the client gets its moment ๐Ÿ’… It makes note of the "digest-challenge" and then responds with a string formatted and computed by the rules for a "digest-response". Here's the whole lineup ๐Ÿฆ‹

digest-response  = 1#( username | realm | nonce | cnonce |
                       nonce-count | qop | digest-uri | response |
                       maxbuf | charset | cipher | authzid |
                       auth-param )

    username         = "username" "=" <"> username-value <">
    username-value   = qdstr-val
    cnonce           = "cnonce" "=" <"> cnonce-value <">
    cnonce-value     = qdstr-val
    nonce-count      = "nc" "=" nc-value
    nc-value         = 8LHEX
    qop              = "qop" "=" qop-value
    digest-uri       = "digest-uri" "=" <"> digest-uri-value <">
    digest-uri-value  = serv-type "/" host [ "/" serv-name ]
    serv-type        = 1*ALPHA
    host             = 1*( ALPHA | DIGIT | "-" | "." )
    serv-name        = host
    response         = "response" "=" response-value
    response-value   = 32LHEX
    LHEX             = "0" | "1" | "2" | "3" |
                       "4" | "5" | "6" | "7" |
                       "8" | "9" | "a" | "b" |
                       "c" | "d" | "e" | "f"
    cipher           = "cipher" "=" cipher-value
    authzid          = "authzid" "=" <"> authzid-value <">
    authzid-value    = qdstr-val

username ๐Ÿซถ The user's name in the specified realm, encoded according to the value of the charset directive. This directive is required and MUST be present exactly once; otherwise, authentication fails.

realm ๐ŸŒธ The realm containing the user's account. This directive is required if the server provided any realms in the "digest-challenge", in which case it may appear exactly once and its value SHOULD be one of those realms. If the directive is missing, "realm-value" will set to the empty string when computing A1 (see below for details).

nonce ๐ŸŽ€ The server-specified data string received in the preceding "digest-challenge". This directive is required and MUST be present exactly once; otherwise, authentication fails.

cnonce ๐Ÿ’œ A client-specified data string which MUST be different each time a digest-response is sent as part of initial authentication. The cnonce-value is an opaque quoted string value provided by the client and used by both client and server to avoid chosen plaintext attacks, and to provide mutual authentication. The security of the implementation depends on a good choice. It is RECOMMENDED that it contain at least 64 bits of entropy. This directive is required and MUST be present exactly once; otherwise, authentication fails.

nonce-count ๐Ÿ”ฅ The nc-value is the hexadecimal count of the number of requests (including the current request) that the client has sent with the nonce value in this request. For example, in the first request sent in response to a given nonce value, the client sends "nc=00000001". The purpose of this directive is to allow the server to detect request replays by maintaining its own copy of this count - if the same nc-value is seen twice, then the request is a replay. See the description below of the construction of the response value. This directive may appear at most once; if multiple instances are present, the client should abort the authentication exchange.

qop ๐Ÿ‘‘ Indicates what "quality of protection" the client accepted. If present, it may appear exactly once and its value MUST be one of the alternatives in qop-options. If not present, it defaults to "auth". These values affect the computation of the response. Note that this is a single token, not a quoted list of alternatives.

serv-type ๐Ÿงš Indicates the type of service, such as "www" for web service, "ftp" for FTP service, "smtp" for mail delivery service, etc. The service name as defined in the SASL profile for the protocol see section 4 of [RFC 2222], registered in the IANA registry of "service" elements for the GSSAPI host-based service name form [RFC 2078].

host ๐Ÿ“ The DNS host name or IP address for the service requested. The DNS host name must be the fully-qualified canonical name of the host. The DNS host name is the preferred form; see notes on server processing of the digest-uri.

serv-name ๐Ÿฆ‹ Indicates the name of the service if it is replicated. The service is considered to be replicated if the client's service-location process involves resolution using standard DNS lookup operations, and if these operations involve DNS records (such as SRV, or MX) which resolve one DNS name into a set of other DNS names. In this case, the initial name used by the client is the "serv-name", and the final name is the "host" component. For example, the incoming mail service for "example.com" may be replicated through the use of MX records stored in the DNS, one of which points at an SMTP server called "mail3.example.com"; it's "serv-name" would be "example.com", it's "host" would be "mail3.example.com". If the service is not replicated, or the serv-name is identical to the host, then the serv-name component MUST be omitted.

digest-uri ๐ŸŒˆ Indicates the principal name of the service with which the client wishes to connect, formed from the serv-type, host, and serv-name. For example, the FTP service on "ftp.example.com" would have a "digest-uri" value of "ftp/ftp.example.com"; the SMTP server from the example above would have a "digest-uri" value of "smtp/mail3.example.com/example.com".

Servers SHOULD check that the supplied value is correct. This will detect accidental connection to the incorrect server. It is also so that clients will be trained to provide values that will work with implementations that use a shared back-end authentication service that can provide server authentication.

The serv-type component should match the service being offered. The host component should match one of the host names of the host on which the service is running, or it's IP address. Servers SHOULD NOT normally support the IP address form, because server authentication by IP address is not very useful; they should only do so if the DNS is unavailable or unreliable. The serv-name component should match one of the service's configured service names.

This directive may appear at most once; if multiple instances are present, the client should abort the authentication exchange.

Note: In the HTTP use of Digest authentication, the digest-uri is the URI (usually a URL) of the resource requested -- hence the name of the directive.

response ๐Ÿ’… A string of 32 hex digits computed as defined below, which proves that the user knows a password. This directive is required and MUST be present exactly once; otherwise, authentication fails.

maxbuf โญ A number indicating the size of the largest buffer the client is able to receive. If this directive is missing, the default value is 65536. This directive may appear at most once; if multiple instances are present, the server should abort the authentication exchange.

charset ๐Ÿ™ˆ This directive, if present, specifies that the client has used UTF-8 encoding for the username and password. If not present, the username and password must be encoded in ISO 8859-1 (of which US-ASCII is a subset). The client should send this directive only if the server has indicated it supports UTF-8. The directive is needed for backwards compatibility with HTTP Digest, which only supports ISO 8859-1.

LHEX ๐Ÿ“ 32 hex digits, where the alphabetic characters MUST be lower case, because MD5 is not case insensitive.

cipher ๐Ÿ”ฅ The cipher chosen by the client. This directive MUST appear exactly once if "auth-conf" is negotiated; if required and not present, authentication fails.

authzid ๐Ÿ‘‘ The "authorization ID" as per RFC 2222, encoded in UTF-8. This directive is optional. If present, and the authenticating user has sufficient privilege, and the server supports it, then after authentication the server will use this identity for making all accesses and access checks. If the client specifies it, and the server does not support it, then the response-value will be incorrect, and authentication will fail.

The size of a digest-response MUST be less than 4096 bytes.


2.1.2.1. Response-value ๐ŸŽ€

The definition of "response-value" above indicates the encoding for its value -- 32 lower case hex characters. The following definitions show how the value is computed.

Although qop-value and components of digest-uri-value may be case-insensitive, the case which the client supplies in step two is preserved for the purpose of computing and verifying the response-value.

   response-value  =

      HEX( KD ( HEX(H(A1)),
              { nonce-value, ":" nc-value, ":",
                cnonce-value, ":", qop-value, ":", HEX(H(A2)) }))

If authzid is specified, then A1 is

   A1 = { H( { username-value, ":", realm-value, ":", passwd } ),
        ":", nonce-value, ":", cnonce-value, ":", authzid-value }

If authzid is not specified, then A1 is

   A1 = { H( { username-value, ":", realm-value, ":", passwd } ),
        ":", nonce-value, ":", cnonce-value }

where

      passwd   = *OCTET

The "username-value", "realm-value" and "passwd" are encoded according to the value of the "charset" directive. If "charset=UTF-8" is present, and all the characters of either "username-value" or "passwd" are in the ISO 8859-1 character set, then it must be converted to ISO 8859-1 before being hashed. This is so that authentication databases that store the hashed username, realm and password (which is common) can be shared compatibly with HTTP, which specifies ISO 8859-1. A sample implementation of this conversion is in section 8.

If the "qop" directive's value is "auth", then A2 is:

   A2       = { "AUTHENTICATE:", digest-uri-value }

If the "qop" value is "auth-int" or "auth-conf" then A2 is:

   A2       = { "AUTHENTICATE:", digest-uri-value,
            ":00000000000000000000000000000000" }

Note that "AUTHENTICATE:" must be in upper case, and the second string constant is a string with a colon followed by 32 zeros.

These apparently strange values of A2 are for compatibility with HTTP; they were arrived at by setting "Method" to "AUTHENTICATE" and the hash of the entity body to zero in the HTTP digest calculation of A2.

Also, in the HTTP usage of Digest, several directives in the "digest-challenge" sent by the server have to be returned by the client in the "digest-response". These are:

  • opaque
  • algorithm

These directives are not needed when Digest is used as a SASL mechanism (i.e., MUST NOT be sent, and MUST be ignored if received). ๐ŸŒˆโœจ

2.1.3. Step Three โœจ

okay so now it's the server's turn ๐Ÿ’… It receives and validates the "digest-response", and it checks that the nonce-count is "00000001". If it supports subsequent authentication (see section 2.2) ๐Ÿ”, it saves the nonce and the nonce-count so it can use them later. Then it sends back a message shaped like this:

response-auth = "rspauth" "=" response-value

where response-value is calculated as above ๐Ÿงฎ, using the values sent in step two, except that if qop is "auth", then A2 is:

A2 = { ":", digest-uri-value }

And if qop is "auth-int" or "auth-conf", then A2 is:

A2 = { ":", digest-uri-value, ":00000000000000000000000000000000" }

Compared to how HTTP uses it ๐ŸŒธ, these Digest directives in the "digest-response" are unused:

  • nextnonce
  • qop
  • cnonce
  • nonce-count

2.2. Subsequent Authentication ๐Ÿ”

If the client has authenticated to the server before, and it remembers the values of username, realm, nonce, nonce-count, cnonce, and qop it used back then, AND the SASL profile for the protocol allows an initial client response, then it MAY perform "subsequent authentication", as defined in this section ๐Ÿฆ‹

2.2.1. Step one

The client grabs the values from the previous authentication and sends an initial response. It's a string formatted and computed by the rules for a "digest-response" (as defined above), but with a nonce-count one greater than the one used in the last "digest-response" โž•

2.2.2. Step Two

The server receives the "digest-response" and decides what to do:

  • If the server does not support subsequent authentication, it sends a "digest-challenge", and authentication proceeds as in initial authentication.
  • If the server has no saved nonce and nonce-count from a previous authentication, it also sends a "digest-challenge", and authentication proceeds as in initial authentication.
  • Otherwise, the server validates the "digest-response", checks that the nonce-count is one greater than the one used in the previous authentication using that nonce, and saves the new value of nonce-count ๐Ÿง 

If the response is invalid ๐Ÿšจ, the server sends a "digest-challenge", and authentication proceeds as in initial authentication. The server should also be configurable to log the failure in some sort of security audit log, since the failure may be a symptom of an attack ๐Ÿ‘€ The nonce-count MUST NOT be incremented in this case, because doing so would allow a denial of service attack by sending an out-of-order nonce-count ๐Ÿ™…โ€โ™€๏ธ

If the response is valid, the server MAY choose to deem that authentication has succeeded โœ… However, if it has been too long since the previous authentication, or for any other reason, the server MAY send a new "digest-challenge" with a new value for nonce ๐Ÿ”„ The challenge MAY contain a "stale" directive with the value "true". That basically says the client may respond to the challenge using the password it used in the previous response ๐Ÿ”‘ Otherwise, the client must solicit the password anew from the user. This lets the server make sure the user has presented their password recently ๐Ÿ’œ (The directive name refers to the previous nonce being stale, not to the last use of the password.) Except for the handling of "stale", after sending the "digest-challenge", authentication proceeds as in the case of initial authentication.


2.3. Integrity Protection ๐Ÿ›ก๏ธ

If the server offered qop=auth-int and the client responded qop=auth-int, then subsequent messages between the client and the server, up to but not including the next subsequent authentication, MUST be integrity protected ๐Ÿ”’ Using the value of H(A1) as defined above as the base session key, the client and server calculate a pair of message integrity keys like this:

The key for integrity protecting messages from client to server is:

Kic = MD5({H(A1),
"Digest session key to client-to-server signing key magic constant"})

The key for integrity protecting messages from server to client is:

Kis = MD5({H(A1),
"Digest session key to server-to-client signing key magic constant"})

where MD5 is as specified in [RFC 1321]. If message integrity is negotiated, a MAC block gets appended to each message ๐Ÿง The MAC block is 16 bytes: the first 10 bytes of the HMAC-MD5 [RFC 2104] of the message, a 2-byte message type number in network byte order with value 1, and the 4-byte sequence number in network byte order. The message type is there to allow for future extensions, such as rekeying ๐Ÿ”ฎ

MAC(Ki, SeqNum, msg) = (HMAC(Ki, {SeqNum, msg})[0..9], 0x0001,
SeqNum)

where Ki is Kic for messages sent by the client and Kis for those sent by the server. The sequence number starts at zero and goes up by one for each message sent ๐Ÿ”ข

Upon receipt, MAC(Ki, SeqNum, msg) is computed and compared with the received value. If they differ, the message gets discarded ๐Ÿ—‘๏ธ


2.4. Confidentiality Protection ๐Ÿคซ

If the server sent a "cipher-opts" directive and the client responded with a "cipher" directive, then subsequent messages between the client and the server MUST be confidentiality protected ๐ŸŽ€ Using the value of H(A1) as defined above as the base session key, the client and server calculate a pair of message integrity keys as follows.

The key for confidentiality protecting messages from client to server is:

Kcc = MD5({H(A1)[0..n],
"Digest H(A1) to client-to-server sealing key magic constant"})

The key for confidentiality protecting messages from server to client is:

Kcs = MD5({H(A1)[0..n],
"Digest H(A1) to server-to-client sealing key magic constant"})

where MD5 is as specified in [RFC 1321]. For cipher "rc4-40", n is 5. For "rc4-56", n is 7. For the rest, n is 16. The key for the "rc-*" ciphers is all 16 bytes of Kcc or Kcs. The key for "des" is the first 7 bytes, and the key for "3des" is the first 14 bytes. The IV for "des" and "3des" is the last 8 bytes of Kcc or Kcs ๐Ÿงฉ

If message confidentiality is negotiated, each message is encrypted with the chosen cipher, and a MAC block gets appended to the message ๐Ÿ”

The MAC block is a variable length padding prefix followed by 16 bytes, formatted as follows: the first 10 bytes of the HMAC-MD5 [RFC 2104] of the message, a 2-byte message type number in network byte order with value 1, and the 4-byte sequence number in network byte order. If the blocksize of the chosen cipher is not 1 byte, the padding prefix is one or more octets, each containing the number of padding bytes, so that the total length of the encrypted part of the message is a multiple of the blocksize. The padding and the first 10 bytes of the MAC block are encrypted along with the message ๐Ÿซถ

SEAL(Ki, Kc, SeqNum, msg) =
      {CIPHER(Kc, {msg, pad, HMAC(Ki, {SeqNum, msg})[0..9])}), 0x0001,
       SeqNum}

where CIPHER is the chosen cipher, and Ki and Kc are Kic and Kcc for messages sent by the client, and Kis and Kcs for those sent by the server. The sequence number starts at zero and goes up by one for each message sent ๐Ÿ”ข

Upon receipt, the message is decrypted, HMAC(Ki, {SeqNum, msg}) is computed and compared with the received value. If they differ, the message gets discarded ๐Ÿ—‘๏ธ


3. Security Considerations ๐Ÿšจ

Okay this section is SO important bestie, read it carefully ๐Ÿฅบ

3.1. Authentication of Clients using Digest Authentication

Digest Authentication is not a strong authentication mechanism compared to, for example, public key based mechanisms ๐Ÿ”‘ However, since it prevents chosen plaintext attacks, it's stronger than (e.g.) CRAM-MD5, which has been proposed for use with LDAP [10], POP and IMAP (see RFC 2195 [9]). It's meant to replace the much weaker and even more dangerous use of plaintext passwords ๐Ÿ’€ Still, since it's a password based mechanism, it avoids some of the potential deployabilty issues with public-key, OTP, or similar mechanisms.

Digest Authentication offers no confidentiality protection beyond protecting the actual password ๐Ÿ‘€ Everything else in the challenge and response is available to an eavesdropper, including the user's name and authentication realm.

3.2. Comparison of Digest with Plaintext Passwords ๐Ÿ†š

The biggest threat to the kinds of transactions these protocols are used for is network snooping ๐Ÿ•ต๏ธโ€โ™€๏ธ Think online access to a mail service that's only for paying subscribers, for example. With plaintext password authentication, an eavesdropper can get the user's password. That doesn't just unlock the database. It's often worse, because it also unlocks anything else the user protects with the same password ๐Ÿ˜ญ

3.3. Replay Attacks ๐Ÿ”

Replay attacks are defeated if the client or the server chooses a fresh nonce for each authentication, as this specification requires โœจ

3.4. Online dictionary attacks ๐Ÿ“–

If the attacker can eavesdrop, it can test any overheard nonce/response pairs against a (potentially very large) list of common words. That list is usually much smaller than the total number of possible passwords. The cost of computing the response for each password on the list is paid once for each challenge ๐Ÿ’ธ

The server can mitigate this attack by not letting users pick passwords that are in a dictionary ๐Ÿ™…โ€โ™€๏ธ

3.5. Offline dictionary attacks ๐ŸŒ™

If the attacker can choose the challenge, it can precompute the possible responses to that challenge for a list of common words. That list is usually much smaller than the total number of possible passwords. The cost of computing the response for each password on the list is paid just once ๐Ÿงฎ

Offline dictionary attacks are defeated if the client chooses a fresh nonce for each authentication, as this specification requires ๐Ÿ’œ

3.6. Man in the Middle ๐Ÿ‘น

Digest authentication is vulnerable to "man in the middle" (MITM) attacks. Obviously, a MITM would have all the problems of eavesdropping, but it also gives the attacker some extra opportunities ๐Ÿ˜ˆ

One possible MITM attack is to swap in a weaker qop scheme for the one(s) the server sent. The server won't be able to detect this attack, so the client should always use the strongest scheme it understands from the choices offered, and it should never choose a scheme that doesn't meet its minimum requirements ๐Ÿ›ก๏ธ

3.7. Chosen plaintext attacks ๐ŸŽฏ

A chosen plaintext attack is where a MITM or a malicious server can arbitrarily choose the challenge the client will use to compute the response. Being able to choose the challenge is known to make cryptanalysis much easier [8] ๐Ÿง 

However, Digest doesn't let the attacker choose the challenge as long as the client chooses a fresh nonce for each authentication, as this specification requires ๐Ÿ™Œ

3.8. Spoofing by Counterfeit Servers ๐ŸŽญ

If a user can be led to believe she's connecting to a host with information protected by a password she knows, when she's actually connecting to a hostile server, then the hostile server can obtain challenge/response pairs where it was able to partly choose the challenge. There is no known way to exploit this ๐Ÿคทโ€โ™€๏ธ

3.9 Storing passwords

Okay bestie, so the authenticating agent (usually the server) has to store some data derived from the user's name and password in a "password file" tied to a given realm. ๐Ÿ” Normally that's pairs of username and H({ username-value, ":", realm-value, ":", passwd }), which is enough to work out H(A1) like we talked about earlier, without ever exposing the actual password ๐Ÿ’…โœจ.

Here's the tea โ˜•. If that password file gets compromised, an attacker instantly has access to the documents on the server in that realm ๐Ÿ˜ญ. Unlike, say, a standard UNIX password file, they don't need to decrypt anything to get in ๐Ÿ’€. The real password would still need decryption, or more likely a brute force attack, so it's not instant doom for the password itself ๐Ÿ™ˆ. That's exactly why the realm is part of the digested data! If one Digest password file gets leaked, it doesn't automatically compromise the others that use the same username and password ๐Ÿฆ‹ (though it does leave them open to brute force attacks, so still not chill ๐Ÿฅฒ).

Two big security takeaways, periodt ๐Ÿ‘‘:

First, the password file has to be protected as if it held plaintext passwords ๐Ÿ”’, because for the purpose of getting into its realm, it basically does ๐Ÿ’œ.

Second, the realm string should be unique among all the realms any single user is likely to use ๐ŸŒˆ. Like, it should include the name of the host doing the authentication ๐Ÿงš. Main character energy for your realm names ๐Ÿ’–.

3.10 Multiple realms

Using multiple realms is a double-edged sparkle โœจ. On one hand, if the security database for one realm gets compromised, that doesn't take down all your security ๐Ÿ›ก๏ธ. On the other hand, you've now got more things to protect to keep the whole system secure ๐Ÿ˜ฉ๐Ÿซถ.

3.11 Summary

Real talk, by modern cryptographic standards Digest Authentication is weak ๐Ÿ’€, especially compared to (say) public key based mechanisms. But for a huge range of purposes it's valuable as a replacement for plaintext passwords ๐ŸŒธ. Its strength may also vary depending on the implementation ๐ŸŽ€.

4. Example ๐Ÿฆ‹

This example shows how the Digest SASL mechanism works with the IMAP4 AUTHENTICATE command [RFC 2060] ๐Ÿ’….

In this example, "C:" and "S:" are lines sent by the client or server, respectively, each including a CRLF at the end ๐Ÿ“. Linebreaks and indentation inside a "C:" or "S:" line are editorial and not part of the protocol ๐Ÿ™ˆ. The password in this example was "secret" ๐Ÿคซ. Also, the base64 encoding of the challenges and responses is part of the IMAP4 AUTHENTICATE command, not part of the Digest spec itself ๐Ÿงš.

S: * OK elwood.innosoft.com PMDF IMAP4rev1 V6.0-9
C: c CAPABILITY
S: * CAPABILITY IMAP4 IMAP4rev1 ACL LITERAL+ NAMESPACE QUOTA
            UIDPLUS AUTH=CRAM-MD5 AUTH=DIGEST-MD5 AUTH=PLAIN
S: c OK Completed
C: a AUTHENTICATE DIGEST-MD5
S: + cmVhbG09ImVsd29vZC5pbm5vc29mdC5jb20iLG5vbmNlPSJPQTZNRzl0
     RVFHbTJoaCIscW9wPSJhdXRoIixhbGdvcml0aG09bWQ1LXNlc3MsY2hh
     cnNldD11dGYtOA==
C: Y2hhcnNldD11dGYtOCx1c2VybmFtZT0iY2hyaXMiLHJlYWxtPSJlbHdvb2
   QuaW5ub3NvZnQuY29tIixub25jZT0iT0E2TUc5dEVRR20yaGgiLG5jPTAw
   MDAwMDAxLGNub25jZT0iT0E2TUhYaDZWcVRyUmsiLGRpZ2VzdC11cmk9Im
   ltYXAvZWx3b29kLmlubm9zb2Z0LmNvbSIscmVzcG9uc2U9ZDM4OGRhZDkw
   ZDRiYmQ3NjBhMTUyMzIxZjIxNDNhZjcscW9wPWF1dGg=
S: + cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZA==
C:
S: a OK User logged in

The base64-decoded version of the SASL exchange is:

S: realm="elwood.innosoft.com",nonce="OA6MG9tEQGm2hh",qop="auth",
   algorithm=md5-sess,charset=utf-8
C: charset=utf-8,username="chris",realm="elwood.innosoft.com",
   nonce="OA6MG9tEQGm2hh",nc=00000001,cnonce="OA6MHXh6VqTrRk",
   digest-uri="imap/elwood.innosoft.com",
   response=d388dad90d4bbd760a152321f2143af7,qop=auth
S: rspauth=ea40f60335c427b5527b84dbabcdfffd

The password in this example was "secret". ๐Ÿคซ

This example shows the Digest SASL mechanism used with ACAP, using the same notational conventions and password as the previous example ๐ŸŽ€. Note that ACAP does not base64 encode and uses fewer round trips than IMAP4 ๐Ÿ”ฅ.

S: * ACAP (IMPLEMENTATION "Test ACAP server") (SASL "CRAM-MD5"
           "DIGEST-MD5" "PLAIN")
C: a AUTHENTICATE "DIGEST-MD5"
S: + {94}
S: realm="elwood.innosoft.com",nonce="OA9BSXrbuRhWay",qop="auth",
   algorithm=md5-sess,charset=utf-8
C: {206}
C: charset=utf-8,username="chris",realm="elwood.innosoft.com",
   nonce="OA9BSXrbuRhWay",nc=00000001,cnonce="OA9BSuZWMSpW8m",
   digest-uri="acap/elwood.innosoft.com",
   response=6084c6db3fede7352c551284490fd0fc,qop=auth
S: a OK (SASL {40}
S: rspauth=2f0b3d7c3c2e486600ef710726aa2eae) "AUTHENTICATE
Completed"

The server uses the values of all the directives, plus knowledge of the user's password (or the hash of the user's name, server's realm and the user's password) to verify the computations above ๐Ÿง. If they check out, then the user has authenticated ๐ŸŽ‰โœจ.

5. References ๐Ÿ“š

[Digest] Franks, J., et al., "HTTP Authentication: Basic and Digest Access Authentication", RFC 2617, June 1999.

[ISO-8859] ISO-8859. International Standard--Information Processing--8-bit Single-Byte Coded Graphic Character Sets -- Part 1: Latin alphabet No. 1, ISO-8859-1:1987. Part 2: Latin alphabet No. 2, ISO-8859-2, 1987. Part 3: Latin alphabet No. 3, ISO-8859-3, 1988. Part 4: Latin alphabet No. 4, ISO-8859-4, 1988. Part 5: Latin/Cyrillic alphabet, ISO-8859-5, 1988. Part 6: Latin/Arabic alphabet, ISO-8859-6, 1987. Part 7: Latin/Greek alphabet, ISO-8859-7, 1987. Part 8: Latin/Hebrew alphabet, ISO-8859-8, 1988. Part 9: Latin alphabet No. 5, ISO-8859-9, 1990.

[RFC 822] Crocker, D., "Standard for The Format of ARPA Internet Text Messages," STD 11, RFC 822, August 1982.

[RFC 1321] Rivest, R., "The MD5 Message-Digest Algorithm", RFC 1321, April 1992.

[RFC 2047] Moore, K., "MIME (Multipurpose Internet Mail Extensions) Part Three: Message Header Extensions for Non-ASCII Text", RFC 2047, November 1996.

[RFC 2052] Gulbrandsen, A. and P. Vixie, "A DNS RR for specifying the location of services (DNS SRV)", RFC 2052, October 1996.

[RFC 2060] Crispin, M., "Internet Message Access Protocol - Version 4rev1", RFC 2060, December 1996.

[RFC 2104] Krawczyk, H., Bellare, M. and R. Canetti, "HMAC: Keyed-Hashing for Message Authentication", RFC 2104, February 1997.

[RFC 2195] Klensin, J., Catoe, R. and P. Krumviede, "IMAP/POP AUTHorize Extension for Simple Challenge/Response", RFC 2195, September 1997.

[RFC 2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, March 1997.

[RFC 2222] Myers, J., "Simple Authentication and Security Layer (SASL)", RFC 2222, October 1997.

[USASCII] US-ASCII. Coded Character Set - 7-Bit American Standard Code for Information Interchange. Standard ANSI X3.4-1986, ANSI, 1986.

6. Authors' Addresses ๐Ÿซถ

Paul Leach Microsoft 1 Microsoft Way Redmond, WA 98052

EMail: paulle@microsoft.com

Chris Newman Innosoft International, Inc. 1050 Lakes Drive West Covina, CA 91790 USA

EMail: chris.newman@innosoft.com

7. ABNF โœจ

okay so this section is the grammar notation bit, and it's literally the same notation used in the HTTP/1.1 spec (RFC 2616) and the HTTP auth spec (RFC 2617) ๐ŸŽ€. we copy-pasted it here so it's easy to find. the whole point is that one Digest implementation can support both HTTP and SASL protocols, so everyone uses the same notation to make comparisons easy and avoid sneaky differences ๐Ÿฆ‹. ngl it's cut-and-paste from HTTP, so not every production is used here, and it's not quite legal ABNF either. the errors came along from the HTTP specs, not from us ๐Ÿ’€

7.1. Augmented BNF ๐Ÿ’…

bestie, all the mechanisms in this doc are described in both prose and an augmented Backus-Naur Form (BNF) that's similar to the one in RFC 822 [RFC 822]. implementers need to be familiar with it to get what the spec is saying ๐Ÿงš

the augmented BNF has these constructs:

name = definition

the name of a rule is just the name itself (no < and > around it) and it's separated from the definition by the equals = sign. whitespace only matters for continuation lines, where indentation shows that a rule goes over more than one line. certain basic rules are in uppercase, like SP, LWS, HT, CRLF, DIGIT, ALPHA, etc. angle brackets show up inside definitions when they help you tell which rule names are which ๐ŸŒธ

"literal"

quotation marks wrap literal text. unless stated otherwise, the text is case-insensitive ๐Ÿซถ

rule1 | rule2

elements separated by a bar | are alternatives, so "yes | no" accepts yes or no ๐Ÿ’–

(rule1 rule2)

elements in parentheses count as a single element. so "(elem (foo | bar) elem)" allows the token sequences elem foo elem and elem bar elem ๐Ÿฆ‹

*rule

the * in front of an element means repetition. the full form is <n>*<m>element, meaning at least <n> and at most <m> occurrences of element. the defaults are 0 and infinity, so *(element) allows any number (zero included), 1*element requires at least one, and 1*2element allows one or two โœจ

[rule]

square brackets mark optional elements. [foo bar] is the same as *1(foo bar) ๐ŸŒˆ

N rule

specific repetition: <n>(element) is the same as <n>*<n>(element), which is exactly <n> occurrences of (element). so 2DIGIT is a 2-digit number, and 3ALPHA is a string of three alphabetic characters ๐Ÿ“

#rule

the # is defined like *, but for lists of elements. the full form is <n>#<m>element, meaning at least <n> and at most <m> elements, each separated by one or more commas (,) and OPTIONAL linear white space (LWS). this makes lists super easy. a rule like this:

( *LWS element *( *LWS "," *LWS element ))

can be written as:

1#element

wherever this construct is used, null elements are allowed, but they don't count toward the number of elements. so "(element), , (element) " is allowed but only counts as two elements. that means that wherever at least one element is required, at least one non-null element MUST be present ๐Ÿ‘‘ the defaults are 0 and infinity, so #element allows any number (zero included), 1#element requires at least one, and 1#2element allows one or two ๐Ÿ’œ

; comment

a semicolon set off to the right of the rule text starts a comment that runs to the end of the line. it's a simple way to leave useful notes next to the specs ๐ŸŽ€

implied *LWS

the grammar here is word-based. except where noted otherwise, linear white space (LWS) can go between any two adjacent words (token or quoted-string), and between adjacent words and separators, without changing how a field is read. at least one delimiter (LWS and/or separators) MUST exist between any two tokens (see the definition of "token" below), otherwise they'd be read as one big token ๐Ÿ˜ญ

7.2. Basic Rules ๐Ÿฉท

the rules below are used all over this spec to describe the basic parsing stuff. the US-ASCII character set is defined by ANSI X3.4-1986 [USASCII] ๐ŸŒท

OCTET          = <any 8-bit sequence of data>
CHAR           = <any US-ASCII character (octets 0 - 127)>
UPALPHA        = <any US-ASCII uppercase letter "A".."Z">
LOALPHA        = <any US-ASCII lowercase letter "a".."z">
ALPHA          = UPALPHA | LOALPHA
DIGIT          = <any US-ASCII digit "0".."9">
CTL            = <any US-ASCII control character
                 (octets 0 - 31) and DEL (127)>
CR             = <US-ASCII CR, carriage return (13)>
LF             = <US-ASCII LF, linefeed (10)>
SP             = <US-ASCII SP, space (32)>
HT             = <US-ASCII HT, horizontal-tab (9)>
<">            = <US-ASCII double-quote mark (34)>
CRLF           = CR LF

all linear white space, folding included, means the same thing as SP. a recipient MAY replace any linear white space with a single SP before it interprets the field value or forwards the message downstream ๐Ÿ’…

LWS            = [CRLF] 1*( SP | HT )

the TEXT rule is only for descriptive field contents and values that aren't meant to be parsed by the message parser. words of *TEXT MAY contain characters from character sets other than ISO-8859-1 [ISO 8859] only when they're encoded according to the rules of RFC 2047 [RFC 2047] ๐Ÿฆ‹

TEXT           = <any OCTET except CTLs,
                 but including LWS>

a CRLF is only allowed in TEXT as part of a header field continuation. the folding LWS is expected to be replaced with a single SP before the TEXT value gets interpreted ๐Ÿซถ

hexadecimal characters show up in a bunch of protocol elements:

HEX            = "A" | "B" | "C" | "D" | "E" | "F"
               | "a" | "b" | "c" | "d" | "e" | "f" | DIGIT

lots of HTTP/1.1 header field values are words separated by LWS or special characters. these special characters MUST be inside a quoted string to be used within a parameter value ๐Ÿ’–

token          = 1*<any CHAR except CTLs or separators>
separators     = "(" | ")" | "<" | ">" | "@"
               | "," | ";" | ":" | "\" | <">
               | "/" | "[" | "]" | "?" | "="
               | "{" | "}" | SP | HT

a string of text gets parsed as a single word if it's wrapped in double-quote marks ๐ŸŒˆ

quoted-string  = ( <"> qdstr-val <"> )
qdstr-val      = *( qdtext | quoted-pair )
qdtext         = <any TEXT except <">>

heads up: LWS is NOT implicit between the double-quote marks (<">) and the qdstr-val inside them. any LWS counts as part of the qdstr-val. this is also true for quotation marks around any other construct ๐Ÿ‘‘

the backslash "\" MAY be used as a single-character quoting mechanism only inside qdstr-val and comment constructs ๐Ÿงš

quoted-pair    = "\" CHAR

the value of this construct is CHAR. and yes, the big takeaway is that backslash has to be quoted itself ๐Ÿ’€


8. Sample Code ๐Ÿ“

okay so this part is the sample code. the sample implementation in [Digest] also applies to DIGEST-MD5, so that's the main thing to know ๐Ÿฆ‹

the code below handles converting UTF-8 to 8859-1 when it's needed:

/* if the string is entirely in the 8859-1 subset of UTF-8, then
 * translate to 8859-1 prior to MD5
 */
void MD5_UTF8_8859_1(MD5_CTX *ctx, const unsigned char *base,
    int len)
{
    const unsigned char *scan, *end;
    unsigned char cbuf;

    end = base + len;
    for (scan = base; scan < end; ++scan) {
        if (*scan > 0xC3) break; /* abort if outside 8859-1 */
        if (*scan >= 0xC0 && *scan <= 0xC3) {
            if (++scan == end || *scan < 0x80 || *scan > 0xBF)
                break;
        }
    }
    /* if we found a character outside 8859-1, don't alter string
     */
    if (scan < end) {
        MD5Update(ctx, base, len);
        return;
    }

    /* convert to 8859-1 prior to applying hash
     */
    do {
        for (scan = base; scan < end && *scan < 0xC0; ++scan)
            ;
        if (scan != base) MD5Update(ctx, base, scan - base);
        if (scan + 1 >= end) break;
        cbuf = ((scan[0] & 0x3) << 6) | (scan[1] & 0x3f);
        MD5Update(ctx, &cbuf, 1);
        base = scan + 2;
    } while (base < end);
}

Copyright (C) The Internet Society (2000). All Rights Reserved. ๐ŸŽ€

this document and its translations may be copied and shared with others, and derivative works that comment on or explain it, or help with implementing it, may be prepared, copied, published and distributed, in whole or in part, without restriction of any kind. the one catch is that the above copyright notice and this paragraph have to be included on all those copies and derivative works. but this document itself may not be modified in any way, like removing the copyright notice or references to the Internet Society or other Internet organizations. the exception is for developing Internet standards, where the procedures for copyrights in the Internet Standards process must be followed, or when translating it into languages other than English ๐Ÿซถ

the limited permissions above are perpetual and won't be revoked by the Internet Society or its successors or assigns ๐Ÿ’œ

this document and the info in it is provided on an "AS IS" basis, and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE ๐Ÿ˜ญ

Acknowledgement ๐ŸŒˆ

funding for the RFC Editor function is currently provided by the Internet Society ๐Ÿ’–

๐Ÿ“ฆ where this page comes from