RFC 2617

HTTP Authentication: Basic and Digest Access Authentication

πŸ“… published June 1999, from ietf

HTTP Authentication: Basic and Digest Access Authentication πŸ’–

Okay so this is RFC 2617 πŸŽ€, and it's literally the spec for how HTTP does authentication. It's a Standards Track document dated June 1999, and it was written by a whole squad: J. Franks (Northwestern University), P. Hallam-Baker (Verisign, Inc.), J. Hostetler (AbiSource, Inc.), S. Lawrence (Agranat Systems, Inc.), P. Leach (Microsoft Corporation), A. Luotonen (Netscape Communications Corporation), and L. Stewart (Open Market, Inc.). It obsoletes RFC 2069 πŸ¦‹, which means this one is the new and improved version that replaces it.

Status of this Memo ✨

This document specifies an Internet standards track protocol for the Internet community, and it's asking for discussion and suggestions for improvements 🫢. For the standardization state and status of this protocol, check the current edition of the "Internet Official Protocol Standards" (STD 1). Distribution of this memo is unlimited.

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

Abstract 🌸

Okay so "HTTP/1.0" already includes a Basic Access Authentication scheme. Basic is not considered to be a secure method of user authentication (unless it's paired with some external secure system like SSL [5]), because the user name and password get sent over the network as cleartext 😭.

This document also gives the spec for HTTP's authentication framework, the original Basic scheme, and a scheme built on cryptographic hashes called "Digest Access Authentication". It's also meant to replace RFC 2069 [6]. Some optional elements from RFC 2069 were removed because problems turned up after it was published πŸ™ˆ. Other new elements were added for compatibility, and those are optional, but they are strongly recommended πŸ’œ.

Like Basic, Digest access authentication checks that both parties know a shared secret (a password) πŸ”₯. The difference is that Digest can do this without sending the password in the clear, and that's Basic's biggest weakness πŸ’€. As with most other authentication protocols, the biggest risks usually aren't in the core protocol itself. They live in the policies and procedures around how it gets used πŸ‘‘.

Table of Contents 🧚

1   Access Authentication................................   3
 1.1   Reliance on the HTTP/1.1 Specification............   3
 1.2   Access Authentication Framework...................   3
2   Basic Authentication Scheme..........................   5
3   Digest Access Authentication Scheme..................   6
 3.1   Introduction......................................   6
  3.1.1  Purpose.........................................   6
  3.1.2  Overall Operation...............................   6
  3.1.3  Representation of digest values.................   7
  3.1.4  Limitations.....................................   7
 3.2   Specification of Digest Headers...................   7
  3.2.1  The WWW-Authenticate Response Header............   8
  3.2.2  The Authorization Request Header................  11
  3.2.3  The Authentication-Info Header..................  15
 3.3   Digest Operation..................................  17
 3.4   Security Protocol Negotiation.....................  18
 3.5   Example...........................................  18
 3.6   Proxy-Authentication and Proxy-Authorization......  19
4   Security Considerations..............................  19
 4.1   Authentication of Clients using Basic
       Authentication....................................  19
 4.2   Authentication of Clients using Digest
       Authentication....................................  20
 4.3   Limited Use Nonce Values..........................  21
 4.4   Comparison of Digest with Basic Authentication....  22
 4.5   Replay Attacks....................................  22
 4.6   Weakness Created by Multiple Authentication
       Schemes...........................................  23
 4.7   Online dictionary attacks.........................  23
 4.8   Man in the Middle.................................  24
 4.9   Chosen plaintext attacks..........................  24
 4.10  Precomputed dictionary attacks....................  25
 4.11  Batch brute force attacks.........................  25
 4.12  Spoofing by Counterfeit Servers...................  25
 4.13  Storing passwords.................................  26
 4.14  Summary...........................................  26
5   Sample implementation................................  27
6   Acknowledgments......................................  31
7   References...........................................  31
8   Authors' Addresses...................................  32
9   Full Copyright Statement.............................  34

1. Access Authentication πŸ¦‹

1.1. Reliance on the HTTP/1.1 Specification 🌈

This spec is a companion to the HTTP/1.1 specification [2]. It uses the augmented BNF from section 2.1 of that document, and it leans on both the non-terminals defined there and other parts of the HTTP/1.1 specification too 🧠.

1.2. Access Authentication Framework πŸ”₯

Okay, so HTTP has a simple challenge-response authentication mechanism. A server MAY use it to challenge a client's request, and a client MAY use it to hand over authentication information ✨. It uses an extensible, case-insensitive token to name the authentication scheme, followed by a comma-separated list of attribute-value pairs that carry the parameters needed to authenticate with that scheme πŸ’–.

auth-scheme    = token
auth-param     = token "=" ( token | quoted-string )

The 401 (Unauthorized) response message is how an origin server challenges a user agent's authorization πŸŽ€. That response MUST include a WWW-Authenticate header field with at least one challenge that applies to the requested resource. The 407 (Proxy Authentication Required) response message is how a proxy challenges a client's authorization. It MUST include a Proxy-Authenticate header field with at least one challenge that applies to the proxy for the requested resource 🫢.

challenge   = auth-scheme 1*SP 1#auth-param

Note: User agents need to take special care when parsing the WWW-Authenticate or Proxy-Authenticate header field value if it has more than one challenge, or if more than one WWW-Authenticate header field shows up. That's because a challenge's contents can already include a comma-separated list of authentication parameters πŸ™ˆ.

The authentication parameter realm is defined for all authentication schemes πŸ’œ:

realm       = "realm" "=" realm-value
realm-value = quoted-string

The realm directive (case-insensitive) is required for every authentication scheme that issues a challenge πŸ“. The realm value (case-sensitive), combined with the canonical root URL of the server being accessed, defines the protection space. The canonical root URL is the absoluteURI for the server whose abs_path is empty; see section 5.1.2 of [2]. Protection spaces let a server split its protected resources into a set of separate chunks, and each one can have its own authentication scheme and/or authorization database πŸ‘‘. The realm value is a string, usually assigned by the origin server, and it may have extra meaning specific to the authentication scheme. Also, there can be multiple challenges with the same auth-scheme but different realms 🌸.

A user agent that wants to authenticate itself with an origin server MAY do so by including an Authorization header field in the request. This is usually, but not always, done after it receives a 401 (Unauthorized) πŸ¦‹. A client that wants to authenticate itself with a proxy MAY do so by including a Proxy-Authorization header field in the request. This is usually, but not always, done after it receives a 407 (Proxy Authentication Required) ✨. Both the Authorization field value and the Proxy-Authorization field value are made up of credentials with the client's authentication info for the realm of the requested resource πŸ’…. The user agent MUST choose one of the challenges with the strongest auth-scheme it understands, then ask the user for credentials based on that challenge πŸ”₯.

credentials = auth-scheme #auth-param

Note: Many browsers only recognize Basic, and they require it to be the first auth-scheme presented. Servers should only include Basic if it is minimally acceptable πŸ’€.

The protection space decides the domain over which credentials can be applied automatically 🧚. If a prior request was authorized, the same credentials MAY be reused for all other requests within that protection space, for a period of time set by the authentication scheme, its parameters, and/or user preference πŸ“. Unless the authentication scheme defines otherwise, a single protection space cannot extend outside the scope of its server 🌈.

If the origin server doesn't want to accept the credentials sent with a request, it SHOULD return a 401 (Unauthorized) response. That response MUST include a WWW-Authenticate header field with at least one challenge (possibly a new one) that applies to the requested resource. If a proxy doesn't accept the credentials sent with a request, it SHOULD return a 407 (Proxy Authentication Required). That response MUST include a Proxy-Authenticate header field with a challenge (possibly a new one) that applies to the proxy for the requested resource πŸ’–.

HTTP doesn't limit applications to this simple challenge-response mechanism for access authentication πŸŽ€. Additional mechanisms MAY be used, such as encryption at the transport level or through message encapsulation, along with extra header fields that specify authentication information. However, this specification does not define those additional mechanisms 🫢.

Proxies MUST be completely transparent about user agent authentication by origin servers 😭. In other words, they must forward the WWW-Authenticate and Authorization headers untouched, and follow the rules in section 14.8 of [2]. Both the Proxy-Authenticate and Proxy-Authorization header fields are hop-by-hop headers (see section 13.5.1 of [2]) 🌸.

2. Basic Authentication Scheme ✨

okay so basic auth is the OG πŸ¦‹. The client has to prove who it is with a user-ID and a password for each realm πŸ”. The realm is just an opaque string, and the only thing you're allowed to do with it is check whether it's equal to another realm on that same server 🫢. The server only serves the request if it can validate the user-ID and password for the protection space of the Request-URI. And there are no optional authentication parameters, so no extra bells and whistles πŸ’….

For Basic, the framework from before works like this:

challenge   = "Basic" realm
credentials = "Basic" basic-credentials

If an unauthorized request comes in for a URI inside the protection space, the origin server MAY reply with a challenge that looks like this:

WWW-Authenticate: Basic realm="WallyWorld"

Here "WallyWorld" is just the name the server gave to the protection space of the Request-URI 🌸. A proxy can send back the same challenge, but it uses the Proxy-Authenticate header field instead 🧚.

To get access, the client sends the userid and password, separated by a single colon (":") character, inside a base64 [7] encoded string in the credentials. The grammar is giving very precise πŸ€“:

basic-credentials = base64-user-pass
base64-user-pass  = <base64 [4] encoding of user-pass,
                     except not limited to 76 char/line>
user-pass   = userid ":" password
userid      = *<TEXT excluding ":">
password    = *TEXT

Userids might be case sensitive πŸ‘€, so keep that in mind, bestie.

Say the user agent wants to send the userid "Aladdin" and the password "open sesame". It would use this header field:

Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

A client SHOULD assume that all paths at or deeper than the depth of the last symbolic element in the path field of the Request-URI are also within the protection space specified by the Basic realm value of the current challenge πŸ—ΊοΈ. A client MAY preemptively send the matching Authorization header with requests for resources in that space, even without getting another challenge from the server ⚑. Same vibe for proxies: when a client sends a request to a proxy, it may reuse a userid and password in the Proxy-Authorization header field without receiving another challenge from the proxy server πŸŽ€. And hey, check out section 4 for the security considerations that come with Basic authentication. This part is serious, so please read it πŸ™ˆ.

3. Digest Access Authentication Scheme

3.1. Introduction

3.1.1. Purpose

The protocol called "HTTP/1.0" includes the spec for a Basic Access Authentication scheme[1]. Honestly? Not a secure method of user authentication πŸ’€, because the user name and password go over the network unencrypted. This section gives you the spec for a scheme that never sends the password in cleartext, and it's called "Digest Access Authentication" πŸ’œ.

The Digest Access Authentication scheme is not meant to be a complete answer to security on the World Wide Web 🌈. It doesn't encrypt any message content. The goal is simply to make an access authentication method that dodges the most serious flaws of Basic authentication ✨.

3.1.2. Overall Operation

Just like Basic Access Authentication, the Digest scheme runs on a simple challenge-response paradigm πŸ¦‹. The Digest scheme challenges with a nonce value. A valid response contains a checksum (by default, the MD5 checksum) of the username, the password, the given nonce value, the HTTP method, and the requested URI πŸ”₯. That's how the password never gets sent in the clear, periodt πŸ’…. And just like with Basic, the username and password have to be prearranged in some way that this document doesn't cover πŸ“.

3.1.3. Representation of digest values

An optional header lets the server say which algorithm was used to make the checksum or digest 🧁. By default it's the MD5 algorithm, and that's the only algorithm this document describes πŸ‘‘.

For this document, an MD5 digest of 128 bits is written as 32 ASCII printable characters πŸ”’. The bits in the 128 bit digest get converted from most significant to least significant bit, four bits at a time, into their ASCII presentation like this. Each four bits is shown with its familiar hexadecimal notation from the characters 0123456789abcdef. So binary 0000 becomes the character '0', 0001 becomes '1', and so on, all the way up to 1111 becoming 'f' πŸ§šβ€β™€οΈ.

3.1.4. Limitations

The Digest authentication scheme described in this document has a lot of known limitations 😭. It's meant to replace Basic authentication and nothing more 🫢. It's a password-based system, and on the server side it has all the same problems as any password system πŸ”. In particular, this protocol makes no provision for the initial secure arrangement between user and server that sets up the user's password, so that part is on you, bestie πŸ™ˆ.

Users and implementors should be aware that this protocol is not as secure as Kerberos, and not as secure as any client-side private-key scheme. Nevertheless it is better than nothing, better than what is commonly used with telnet and ftp, and better than Basic authentication πŸ’–.

3.2. Specification of Digest Headers

The Digest Access Authentication scheme is conceptually similar to the Basic scheme ✨. Below you'll find the formats of the modified WWW-Authenticate header line and the Authorization header line. There's also a brand new header, Authentication-Info, that gets specified too πŸŽ€.

3.2.1. The WWW-Authenticate Response Header ✨

okay so this is the server's big moment πŸŽ€ if a server gets a request for an access-protected object and there's no acceptable Authorization header, it fires back a 401 Unauthorized status code plus a WWW-Authenticate header, following the framework from above. for digest, the header is built like this:

challenge        =  "Digest" digest-challenge

digest-challenge  = 1#( realm | [ domain ] | nonce |
                    [ opaque ] |[ stale ] | [ algorithm ] |
                    [ qop-options ] | [auth-param] )


domain            = "domain" "=" <"> URI ( 1*SP URI ) <">
URI               = absoluteURI | abs_path
nonce             = "nonce" "=" nonce-value
nonce-value       = quoted-string
opaque            = "opaque" "=" quoted-string
stale             = "stale" "=" ( "true" | "false" )
algorithm         = "algorithm" "=" ( "MD5" | "MD5-sess" |
                     token )
qop-options       = "qop" "=" <"> 1#qop-value <">
qop-value         = "auth" | "auth-int" | token

here's what each directive is giving πŸ’…

realm πŸ‘‘ a string that gets shown to users so they know which username and password to use. it 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.com". iconic πŸ¦‹

domain 🌸 a quoted, space-separated list of URIs, as in RFC XURI [7], that defines the protection space. if a URI is an abs_path, it's relative to the canonical root URL (see section 1.2 above) of the server being accessed. an absoluteURI in this list may point to a different server than the one being accessed. the client can use this list to figure out which URIs the same authentication info can be sent to: any URI that has a URI in this list as a prefix (after both have been made absolute) can be assumed to be in the same protection space. if this directive is left out or its value is empty, the client should assume the protection space is all URIs on the responding server. 🧚

this directive isn't meaningful in Proxy-Authenticate headers, where the protection space is always the entire proxy. if it's there anyway, it should be ignored. πŸ™ˆ

nonce πŸ”₯ a server-specified data string that should be uniquely generated every time a 401 response is made. it's recommended that this string be base64 or hexadecimal data. since the string gets passed in the header lines as a quoted string, the double-quote character is not allowed. no drama there πŸ“

the contents of the nonce are implementation dependent, and the quality of the implementation depends on making a good choice. a nonce might, for example, be built as the base 64 encoding of:

    time-stamp H(time-stamp ":" ETag ":" private-key)

where time-stamp is a server-generated time or some other non-repeating value, ETag is the value of the HTTP ETag header associated with the requested entity, and private-key is data only the server knows. with a nonce like this, the server would recalculate the hash portion after receiving the client's authentication header, and reject the request if it doesn't match the nonce from that header or if the time-stamp isn't recent enough. that's how the server can limit how long the nonce stays valid ⏰ the ETag keeps someone from replaying a request for an updated version of the resource.

(okay this part is my favourite 🫢 including the client's IP address in the nonce would seem to let the server limit reuse of the nonce to the same client that originally got it. BUT that would break proxy farms, where requests from a single user often go through different proxies in the farm. and IP address spoofing is not that hard, sadly πŸ’€)

an implementation might choose not to accept a previously used nonce or a previously used digest, to protect against a replay attack. or it might use one-time nonces or digests for POST or PUT requests and a time-stamp for GET requests. for more on the issues involved, see section 4. of this document.

the nonce is opaque to the client. πŸ”’

opaque 🧸 a string of data, specified by the server, that the client should send back unchanged in the Authorization header of later requests with URIs in the same protection space. it's recommended that this string be base64 or hexadecimal data. πŸ’–

stale πŸ’œ a flag that says the client's previous request was rejected because the nonce value was stale. if stale is TRUE (case-insensitive), the client may want to just retry the request with a new encrypted response, without making the user type in a new username and password again. the server should only set stale to TRUE if it gets a request where the nonce is invalid but the digest is valid for that nonce, which means the client clearly knows the correct username and password. if stale is FALSE, or anything other than TRUE, or the stale directive isn't there at all, then the username and/or password are invalid and new ones have to be obtained. 😀

algorithm πŸ¦„ a string naming a pair of algorithms used to produce the digest and a checksum. if it's not present, it's assumed to be "MD5". if the algorithm isn't understood, the challenge should be ignored (and a different one should be used, if there's more than one). 🌈

in this document, the string you get from applying the digest algorithm to the data "data" with secret "secret" is written as KD(secret, data), and the string you get from applying the checksum algorithm to the data "data" is written as H(data). the notation unq(X) means the value of the quoted-string X without the surrounding quotes. nerd stuff but make sure you know it πŸ€“

for the "MD5" and "MD5-sess" algorithms:

H(data) = MD5(data)

and

KD(secret, data) = H(concat(secret, ":", data))

i.e., the digest is the MD5 of the secret concatenated with a colon concatenated with the data. the "MD5-sess" algorithm is meant to allow efficient 3rd party authentication servers. for how its usage is different, see the description in section 3.2.2.2. ✨

qop-options πŸŽ€ this directive is optional, but only so it stays backward compatible with RFC 2069 [6]. it SHOULD be used by all implementations compliant with this version of the Digest scheme. if it's present, it's a quoted string of one or more tokens that show the "quality of protection" values the server supports. the value "auth" means authentication, and the value "auth-int" means authentication with integrity protection. see the descriptions below for how the response directive value is calculated for whichever choice gets used. unrecognized options MUST be ignored. 🚫

auth-param 🌟 this directive allows for future extensions. any unrecognized directive MUST be ignored. πŸ’…


3.2.2. The Authorization Request Header 🎁

the client is expected to retry the request, passing an Authorization header line. it's defined according to the framework above, and it's used like this 🫢

credentials      = "Digest" digest-response
digest-response  = 1#( username | realm | nonce | digest-uri
                | response | [ algorithm ] | [cnonce] |
                [opaque] | [message-qop] |
                    [nonce-count]  | [auth-param] )

username         = "username" "=" username-value
username-value   = quoted-string
digest-uri       = "uri" "=" digest-uri-value
digest-uri-value = request-uri   ; As specified by HTTP/1.1
message-qop      = "qop" "=" qop-value
cnonce           = "cnonce" "=" cnonce-value
cnonce-value     = nonce-value
nonce-count      = "nc" "=" nc-value
nc-value         = 8LHEX
response         = "response" "=" request-digest
request-digest = <"> 32LHEX <">
LHEX             =  "0" | "1" | "2" | "3" |
                    "4" | "5" | "6" | "7" |
                    "8" | "9" | "a" | "b" |
                    "c" | "d" | "e" | "f"

the values of the opaque and algorithm fields must be the ones supplied in the WWW-Authenticate response header for the entity being requested. πŸ”

response πŸ” a string of 32 hex digits, computed as defined below, that proves the user knows a password.

username πŸ™‹β€β™€οΈ the user's name in the specified realm.

digest-uri 🧭 the URI from the Request-URI of the Request-Line. it's duplicated here because proxies are allowed to change the Request-Line in transit.

qop πŸ’« shows what "quality of protection" the client has applied to the message. if it's present, its value MUST be one of the alternatives the server said it supports in the WWW-Authenticate header. these values affect how the request-digest is computed. note that this is a single token, not a quoted list of alternatives like in WWW-Authenticate. this directive is optional in order to keep backward compatibility with a minimal implementation of RFC 2069 [6], but it SHOULD be used if the server indicated that qop is supported by providing a qop directive in the WWW-Authenticate header field. 🌷

cnonce 🍭 this MUST be specified if a qop directive is sent (see above), and MUST NOT be specified if the server did not send a qop directive in the WWW-Authenticate header field. the cnonce-value is an opaque quoted string value provided by the client. both client and server use it to avoid chosen plaintext attacks, to provide mutual authentication, and to provide some message integrity protection. see the descriptions below of how the response-digest and request-digest values are calculated. πŸ›‘οΈ

nonce-count πŸ”’ this MUST be specified if a qop directive is sent (see above), and MUST NOT be specified if the server did not send a qop directive in the WWW-Authenticate header field. the nc-value is the hexadecimal count of the number of requests (including the current one) 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 let the server detect request replays by keeping 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 how the request-digest value is constructed. πŸ•΅οΈβ€β™€οΈ

auth-param 🌈 this directive allows for future extensions. any unrecognized directive MUST be ignored.

if a directive or its value is improper, or required directives are missing, the proper response is 400 Bad Request. if the request-digest is invalid, a login failure should be logged, since repeated login failures from a single client may mean an attacker is trying to guess passwords. 🚨

the definition of request-digest above shows the encoding for its value. the following definitions show how the value is computed. πŸ’–

3.2.2.1. Request-Digest ✨

okay bestie, this is where the actual digest gets cooked 🍳 If the "qop" value is "auth" or "auth-int", the request-digest is built like this:

   request-digest  = <"> < KD ( H(A1),     unq(nonce-value)
                                       ":" nc-value
                                       ":" unq(cnonce-value)
                                       ":" unq(qop-value)
                                       ":" H(A2)
                               ) <">

If the "qop" directive isn't present at all, that's a compatibility thing with RFC 2069, so the recipe is shorter 🧁

   request-digest  =
              <"> < KD ( H(A1), unq(nonce-value) ":" H(A2) ) >
   <">

A1 and A2 get their own sections right after this, so keep scrolling πŸ¦‹

3.2.2.2. A1 πŸ’–

If the "algorithm" directive's value is "MD5" or it's unspecified, then A1 is:

   A1       = unq(username-value) ":" unq(realm-value) ":" passwd

where

   passwd   = < user's password >

If the "algorithm" directive's value is "MD5-sess", then A1 only gets calculated once 😀 That happens on the first request the client makes after getting a WWW-Authenticate challenge from the server. It uses the server nonce from that challenge, plus the first client nonce value, to build A1 like so:

   A1       = H( unq(username-value) ":" unq(realm-value)
                  ":" passwd )
                  ":" unq(nonce-value) ":" unq(cnonce-value)

This makes a 'session key' for authenticating the later requests and responses in that "authentication session", and it's different every time, so way less stuff gets hashed under any single key πŸ” (Note: see further discussion of the authentication session in section 3.3.) The server only needs the hash of the user's credentials to make the A1 value, so this setup could work with a third party authentication service, meaning the web server wouldn't need the actual password value πŸ‘€ The spec for that kind of protocol is out of scope here, though.

3.2.2.3. A2 🌸

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

   A2       = Method ":" digest-uri-value

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

   A2       = Method ":" digest-uri-value ":" H(entity-body)

3.2.2.4. Directive values and quoted-string πŸŽ€

Lots of directives, like "username-value", are defined as a "quoted-string". BUT the "unq" notation means the surrounding quotation marks get stripped when forming the string A1 🧼 So if the Authorization header includes these fields:

   username="Mufasa", realm=myhost@testrealm.com

and the user Mufasa has the password "Circle Of Life", then H(A1) would be H(Mufasa:myhost@testrealm.com:Circle Of Life), with no quotation marks in the digested string.

No white space is allowed in any string that gets fed into the digest function H(), unless that white space is actually inside the quoted strings or the entity body being digested πŸ™…β€β™€οΈ For example, the A1 string from above has to look like this:

        Mufasa:myhost@testrealm.com:Circle Of Life

No white space on either side of the colons, but the spaces between the words in the password stay because they're part of the password. The other strings going into H() follow the same rule: no white space on either side of the colons that separate their fields, unless that white space was in the quoted strings or entity body being digested ✨

Also, if integrity protection is applied (qop=auth-int), the H(entity-body) is the hash of the entity body, NOT the message body πŸ™ƒ It gets computed before any transfer encoding is applied by the sender, and after the recipient removes it. This includes multipart boundaries and embedded headers in each part of any multipart content-type.

3.2.2.5. Various considerations 🧠

The "Method" value is the HTTP request method as specified in section 5.1.1 of [2]. The "request-uri" value is the Request-URI from the request line as specified in section 5.1.2 of [2]. It can be "*", an "absoluteURL" or an "abs_path" as specified in section 5.1.2 of [2], but it MUST agree with the Request-URI. In particular, it MUST be an "absoluteURL" if the Request-URI is an "absoluteURL". The "cnonce-value" is an optional client-chosen value whose purpose is to foil chosen plaintext attacks πŸ›‘οΈ

The authenticating server must make sure that the resource designated by the "uri" directive is the same as the resource in the Request-Line. If they're not, the server SHOULD return a 400 Bad Request error. (Since this could be a symptom of an attack, server implementers may want to consider logging such errors πŸ‘€) Why duplicate the request URL info in this field? To handle the possibility that an intermediate proxy alters the client's Request-Line. That altered (but presumably semantically equivalent) request wouldn't produce the same digest the client calculated, so it'd be a mismatch 😭

Implementers should be aware of how authenticated transactions interact with shared caches. This is a big one, so read it twice πŸ’€

The HTTP/1.1 protocol says that when a shared cache (see section 13.7 of [2]) has received a request containing an Authorization header, and a response from relaying that request, it MUST NOT return that response as a reply to any other request, unless one of two Cache-Control (see section 14.9 of [2]) directives was present in the response. If the original response included the "must-revalidate" Cache-Control directive, the cache MAY use the entity of that response when replying to a later request, but it MUST first revalidate it with the origin server, using the request headers from the new request so the origin server can authenticate the new request πŸ” Or, if the original response included the "public" Cache-Control directive, the response entity MAY be returned in reply to any subsequent request.

3.2.3. The Authentication-Info Header πŸ¦‹

The Authentication-Info header is how the server tells the client some info about the successful authentication in the response πŸ’Œ

     AuthenticationInfo = "Authentication-Info" ":" auth-info
     auth-info          = 1#(nextnonce | [ message-qop ]
                            | [ response-auth ] | [ cnonce ]
                            | [nonce-count] )
     nextnonce          = "nextnonce" "=" nonce-value
     response-auth      = "rspauth" "=" response-digest
     response-digest    = <"> *LHEX <">

The value of the nextnonce directive is the nonce the server wants the client to use for a future authentication response. The server may send the Authentication-Info header with a nextnonce field as a way of doing one-time or otherwise changing nonces πŸ”„ If the nextnonce field is present, the client SHOULD use it when constructing the Authorization header for its next request. If the client doesn't do that, the server may ask it to re-authenticate with "stale=TRUE" 😬

Server implementations should carefully consider the performance implications of this mechanism 🐒 Pipelined requests won't be possible if every response includes a nextnonce directive that has to be used on the next request the server receives. Think about the performance vs. security tradeoffs of letting an old nonce value be used for a limited time so pipelining can happen. Using the nonce-count can keep most of the security advantages of a new server nonce without the deleterious effects on pipelining πŸ’…

message-qop

This indicates the "quality of protection" options the server applied to the response. The value "auth" means authentication, and the value "auth-int" means authentication with integrity protection. The server SHOULD use the same value for the message-qop directive in the response as the client sent in the corresponding request 🫢

The optional response digest in the "response-auth" directive supports mutual authentication. The server proves it knows the user's secret, and with qop=auth-int it also provides limited integrity protection of the response πŸ” The "response-digest" value is calculated just like the "request-digest" in the Authorization header, EXCEPT that if "qop=auth" is specified, or isn't specified, in the Authorization header for the request, A2 is:

   A2       = ":" digest-uri-value

and if "qop=auth-int", then A2 is:

   A2       = ":" digest-uri-value ":" H(entity-body)

where "digest-uri-value" is the value of the "uri" directive on the Authorization header in the request. The "cnonce-value" and "nc-value" MUST be the ones for the client request this message is responding to. The "response-auth", "cnonce", and "nonce-count" directives MUST BE present if "qop=auth" or "qop=auth-int" is specified. 🎯

The Authentication-Info header is allowed in the trailer of an HTTP message transferred via chunked transfer-coding 🧚

3.3 Digest Operation ✨

okay so when the server gets an Authorization header, it can check that it's legit by looking up the password for the username that was submitted πŸ” Then the server must do the same digest operation (e.g., MD5) that the client did, and compare the result to the given request-digest value. periodt πŸ’…

ngl this is my favorite part πŸ™ˆ The HTTP server doesn't actually need to know the user's cleartext password! As long as H(A1) is available to the server, the validity of an Authorization header can be verified. main character energy for security πŸ’œ

When a client responds to a WWW-Authenticate challenge for a protection space, that starts an authentication session with that protection space πŸŽ€ The session lasts until the client receives another WWW-Authenticate challenge from any server in the protection space. A client should remember the username, password, nonce, nonce count and opaque values tied to that session, so it can build the Authorization header for future requests in that protection space 🧚 The Authorization header may be included preemptively, which improves server efficiency and avoids extra round trips for authentication challenges πŸš€ The server may choose to accept the old Authorization header information, even though the nonce value might not be fresh. Or it can send a 401 response with a new nonce value, which makes the client retry the request. By specifying stale=TRUE with that response, the server tells the client to retry with the new nonce, but without prompting for a new username and password. no awkward password pop-up, iconic πŸ¦‹

Because the client is required to return the value of the opaque directive the server gave it for the whole session, the opaque data can be used to carry authentication session state information πŸ“ (Note that any such use can also be accomplished more easily and safely by including the state in the nonce. πŸ’‘) For example, a server could be responsible for authenticating content that actually sits on another server. It would do this by having the first 401 response include a domain directive whose value includes a URI on the second server, and an opaque directive whose value contains the state information. The client will retry the request, and at that point the server might respond with a 301/302 redirection pointing to the URI on the second server. The client will follow the redirection and pass an Authorization header, including the <opaque> data. 🌈

Just like with the basic scheme, proxies must be completely transparent in the Digest access authentication scheme. That means they must forward the WWW-Authenticate, Authentication-Info and Authorization headers untouched 🫢 If a proxy wants to authenticate a client before a request is forwarded to the server, it can do that using the Proxy-Authenticate and Proxy-Authorization headers described in section 3.6 below.


3.4 Security Protocol Negotiation πŸ”

It's super useful for a server to know which security schemes a client is capable of handling πŸ‘€

A server may want to require Digest as its authentication method, even if it doesn't know that the client supports it. A client is encouraged to fail gracefully if the server specifies only authentication schemes it cannot handle. be graceful, bestie 🌸

3.5 Example πŸ“–

The following example assumes that an access-protected document is being requested from the server via a GET request. The URI of the document is http://www.nowhere.org/dir/index.html. Both client and server know that the username for this document is "Mufasa", and the password is "Circle Of Life" (with one space between each of the three words). 🦁

The first time the client requests the document, no Authorization header is sent, so the server responds with:

      HTTP/1.1 401 Unauthorized
      WWW-Authenticate: Digest
              realm="testrealm@host.com",
              qop="auth,auth-int",
              nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
              opaque="5ccc069c403ebaf9f0171e9517f40e41"

The client may prompt the user for the username and password, after which it will respond with a new request, including the following Authorization header:

      Authorization: Digest username="Mufasa",
              realm="testrealm@host.com",
              nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
              uri="/dir/index.html",
              qop=auth,
              nc=00000001,
              cnonce="0a4f113b",
              response="6629fae49393a05397450978507c4ef1",
              opaque="5ccc069c403ebaf9f0171e9517f40e41"

3.6 Proxy-Authentication and Proxy-Authorization πŸ›‘οΈ

The digest authentication scheme can also be used for authenticating users to proxies, proxies to proxies, or proxies to origin servers, using the Proxy-Authenticate and Proxy-Authorization headers. These headers are instances of the Proxy-Authenticate and Proxy-Authorization headers specified in sections 10.33 and 10.34 of the HTTP/1.1 specification [2], and their behavior is subject to the restrictions described there. The transactions for proxy authentication are very similar to the ones we already covered πŸ‘― Upon receiving a request which requires authentication, the proxy/server must issue the "407 Proxy Authentication Required" response with a "Proxy-Authenticate" header. The digest-challenge used in the Proxy-Authenticate header is the same as the one for the WWW-Authenticate header defined above in section 3.2.1.

The client/proxy must then re-issue the request with a Proxy-Authorization header, with directives as specified for the Authorization header in section 3.2.2 above.

On subsequent responses, the server sends Proxy-Authentication-Info with directives the same as those for the Authentication-Info header field.

Note that in principle a client could be asked to authenticate itself to both a proxy and an end-server, but never in the same response. one at a time, queen πŸ‘‘


4 Security Considerations 🚨

4.1 Authentication of Clients using Basic Authentication 😬

The Basic authentication scheme is not a secure method of user authentication, and it doesn't protect the entity in any way, since the entity is transmitted in cleartext across the physical network used as the carrier. HTTP does not prevent additional authentication schemes and encryption mechanisms from being used to increase security, or enhancements (such as schemes to use one-time passwords) being added to Basic authentication.

The most serious flaw in Basic authentication is that it results in the essentially cleartext transmission of the user's password over the physical network. This is the problem Digest Authentication tries to address πŸ’–

Because Basic authentication involves the cleartext transmission of passwords, it SHOULD NOT be used (without enhancements) to protect sensitive or valuable information. 🚫

A common use of Basic authentication is for identification purposes -- requiring the user to provide a user name and password as a means of identification, for example, for gathering accurate usage statistics on a server. When used this way, it's tempting to think there's no danger in using it if illicit access to the protected documents isn't a major concern. That's only correct if the server issues both user name and password to the users, and in particular does not allow the user to choose their own password. The danger comes from naive users, who frequently reuse a single password to avoid the task of maintaining multiple passwords. not the vibe, lol πŸ™ˆ

If a server permits users to select their own passwords, the threat is not only unauthorized access to documents on the server, but also unauthorized access to any other resources on other systems that the user protects with the same password. Furthermore, in the server's password database, many of the passwords may also be users' passwords for other sites. The owner or administrator of such a system could therefore expose all users of the system to the risk of unauthorized access to all those sites, if this information is not maintained in a secure fashion. 😱

Basic Authentication is also vulnerable to spoofing by counterfeit servers. If a user can be led to believe they're connecting to a host containing information protected by Basic authentication when, in fact, they're connecting to a hostile server or gateway, then the attacker can request a password, store it for later use, and feign an error. This type of attack is not possible with Digest Authentication. Server implementers SHOULD guard against the possibility of this sort of counterfeiting by gateways or CGI scripts. In particular, it is very dangerous for a server to simply turn over a connection to a gateway. That gateway can then use the persistent connection mechanism to engage in multiple transactions with the client while impersonating the original server, in a way that is not detectable by the client. πŸ’€

4.2 Authentication of Clients using Digest Authentication πŸ”’

Digest Authentication does not provide a strong authentication mechanism when compared to public key based mechanisms, for example.

However, it is significantly stronger than (e.g.) CRAM-MD5, which has been proposed for use with LDAP [10], POP and IMAP (see RFC 2195 [9]). It is intended to replace the much weaker and even more dangerous Basic mechanism. ✨

Digest Authentication offers no confidentiality protection beyond protecting the actual password. All of the rest of the request and response are available to an eavesdropper. πŸ‘€

Digest Authentication offers only limited integrity protection for the messages in either direction. If qop=auth-int mechanism is used, those parts of the message used in the calculation of the WWW-Authenticate and Authorization header field response directive values (see section 3.2 above) are protected. Most header fields and their values could be modified as part of a man-in-the-middle attack. so not airtight, bestie πŸ₯Ί

Many needs for secure HTTP transactions cannot be met by Digest Authentication. For those needs, TLS or SHTTP are more appropriate protocols. In particular, Digest authentication cannot be used for any transaction requiring confidentiality protection. Nevertheless, many functions remain for which Digest authentication is both useful and appropriate. Any service in present use that uses Basic should be switched to Digest as soon as practical. switch to Digest, it's giving security upgrade 🌈🫢

4.3 Limited Use Nonce Values ✨

okay so the Digest scheme uses a nonce from the server to seed the request-digest value (check section 3.2.2.1 above πŸ’…). Look at the example nonce in section 3.2.1. The server can build that nonce so it only works for one particular client, one resource, a limited time, or a limited number of uses, or with any other restrictions it likes πŸŽ€. Doing this makes things safer against replay attacks, for example (see 4.5). BUT it should be noted that however you generate and check the nonce also costs performance and resources, so it's not free πŸ’€.

For example, a server could let each nonce work only once. To do that it keeps a record of which recently issued nonces have come back, and it sends a next-nonce directive in the Authentication-Info header field of every response 🧚. That blocks even an immediate replay, but checking nonces gets really expensive. Even more importantly, it will cause authentication failures for any pipelined requests (they'll probably get a stale nonce indication back) 😭. Including something request-specific, like the Etag value for a resource, also works. It limits the nonce to that one version of the resource, and it also kills pipelining πŸ™ˆ. So this might be a good move for methods with side effects, but the performance hit makes it a no-go for methods without them 🌈.

4.4 Comparison of Digest with Basic Authentication πŸ¦‹

Okay real talk: Digest and Basic Authentication are both pretty weak when it comes to security strength 😬. Still, comparing the two shows why it's useful, even necessary, to swap Basic out for Digest 🫢.

The biggest threat for the kind of transactions these protocols are used for is network snooping. Picture online access to a database that only paying subscribers can use πŸ’…. With Basic authentication, an eavesdropper can grab the user's password. That means they can get into everything in the database, and honestly the worse part is they can also get into anything else that user protects with the same password πŸ’€.

With Digest Authentication, the eavesdropper only gets access to that one transaction and never sees the user's password πŸ”₯. What they learn could still allow a replay attack, but only for a request for the same document. And even that might be limited by the server's choice of nonce πŸ‘‘.

4.5 Replay Attacks πŸ“

A replay attack against Digest authentication would usually be pointless for a simple GET request πŸ™„. Why? The eavesdropper already saw the only document they could get with a replay. The URI of the requested document is digested in the client request, and the server will only deliver that document. Under Basic Authentication, though, once the eavesdropper has the user's password, every document protected by that password is open to them 😭.

So for some purposes, you really do need to protect against replay attacks πŸ›‘οΈ. A good Digest implementation can do this in a few ways. The server-created "nonce" value is implementation dependent. But if it contains a digest of the client IP, a time-stamp, the resource ETag, and a private server key (as recommended above), then a replay attack isn't simple at all ✨. The attacker has to convince the server the request is coming from a fake IP address, and also make the server deliver the document to an IP address that's different from the one it thinks it's sending to. The attack can only work before the time-stamp expires ⏰. Putting the client IP and time-stamp in the nonce also lets an implementation work without keeping state between transactions πŸ’–.

For applications where no replay attack can be tolerated, the server can use one-time nonce values that won't be honored for a second use. The catch is that the server has to remember which nonce values have been used until the nonce time-stamp (and so the digest built with it) expires. That's extra overhead, but it protects effectively against replay attacks πŸ”’.

An implementation must give special attention to replay attacks with POST and PUT requests πŸ‘€. Unless the server uses one-time or otherwise limited-use nonces, and/or insists on integrity protection with qop=auth-int, an attacker could replay valid credentials from a successful request together with counterfeit form data or some other message body 😱. Even with integrity protection, most of the metadata in header fields is still not protected. Proper nonce generation and checking gives some protection against replaying valid credentials that were already used, but see 4.8 too 🫠.

4.6 Weakness Created by Multiple Authentication Schemes 🌸

An HTTP/1.1 server can send back multiple challenges with a 401 (Authenticate) response, and each challenge can use a different auth-scheme 🎭. A user agent MUST choose the strongest auth-scheme it understands and ask the user for credentials based on that challenge πŸ’ͺ.

Note that many browsers will only recognize Basic, and they'll require it to be the first auth-scheme presented. Servers should only include Basic if it's minimally acceptable πŸ™ˆ.

When the server offers a choice of authentication schemes through the WWW-Authenticate header, the strength of the resulting authentication is only as good as the weakest of those schemes πŸ’€. See section 4.8 below for particular attack scenarios that exploit multiple authentication schemes πŸ‘€.

4.7 Online dictionary attacks πŸ“–

If the attacker can eavesdrop, they can test any overheard nonce/response pairs against a list of common words πŸ•΅οΈβ€β™€οΈ. That kind of list is usually much smaller than the total number of possible passwords. The cost of computing the response for each password on the list only has to be paid once per challenge πŸ’….

The server can slow this attack down by not letting users pick passwords that are in a dictionary πŸ™Œ.

4.8 Man in the Middle 😈

Both Basic and Digest authentication are vulnerable to "man in the middle" (MITM) attacks, for example from a hostile or compromised proxy 🚨. Obviously, that brings all the problems of eavesdropping. But it also gives the attacker some extra opportunities πŸ₯Ί.

One possible MITM move is to add a weak authentication scheme to the set of choices, hoping the client picks one that exposes the user's credentials (like a password) 🎣. For this reason, the client should always use the strongest scheme it understands from the choices offered πŸ’œ.

An even better MITM attack is to remove every offered choice and replace them with a challenge that asks only for Basic authentication. Then the attacker uses the cleartext credentials from that Basic authentication to log in to the origin server with the stronger scheme it asked for in the first place 😀. A particularly sneaky way to pull off this MITM attack would be to offer a "free" proxy caching service to gullible users 🍭.

User agents should consider things like showing a visual indication, at the time the credentials are requested, of which authentication scheme is about to be used πŸ‘οΈ. Another option is remembering the strongest authentication scheme a server has ever requested, and then showing a warning before using a weaker one ⚠️. It might also be a good idea for the user agent to be configured to demand Digest authentication in general, or from specific sites πŸ”.

Or, a hostile proxy might trick the client into making a request the attacker wanted instead of one the client wanted πŸƒ. Of course, that's still much harder than a comparable attack against Basic Authentication πŸ’….

4.9 Chosen plaintext attacks πŸ§ͺ

With Digest authentication, a MITM or a malicious server can pick the nonce the client will use to compute the response, all on its own 😡. This is called a "chosen plaintext" attack. Being able to choose the nonce is known to make cryptanalysis much easier [8] πŸ“‰.

That said, no way to analyze the MD5 one-way function used by Digest using chosen plaintext is currently known πŸ™.

The countermeasure against this attack is for clients to be configured to require the use of the optional "cnonce" directive. That lets the client vary the input to the hash in a way the attacker didn't choose πŸ›‘οΈβœ¨.

4.10 Precomputed dictionary attacks πŸ“š

With Digest authentication, if the attacker can run a chosen plaintext attack, they can precompute the response for many common words against a nonce of their choice, and store a dictionary of (response, password) pairs 🀯. That precomputation can often be done in parallel across lots of machines. Then the attacker runs the chosen plaintext attack to get a response for that challenge and just looks up the password in the dictionary πŸ”. Even if most passwords aren't in the dictionary, some might be. Since the attacker gets to pick the challenge, the cost of computing the response for each password on the list can be spread out over finding many passwords πŸ’Έ. A dictionary with 100 million password/response pairs would take about 3.2 gigabytes of disk storage πŸ’Ύ.

The countermeasure against this attack is for clients to be configured to require the use of the optional "cnonce" directive 🫢.

4.11 Batch brute force attacks πŸ’₯

With Digest authentication, a MITM can run a chosen plaintext attack, and can gather responses from many users to the same nonce 😳. Then it can find all the passwords within any subset of password space that would generate one of those nonce/response pairs, in a single pass over that space. It also cuts the time to find the first password by a factor equal to the number of nonce/response pairs gathered πŸƒβ€β™€οΈ. This search can often be done in parallel on many machines, and even a single machine can search large subsets of password space really fast. There are reports of searching all passwords with six or fewer letters in a few hours 🀯.

The countermeasure against this attack is for clients to be configured to require the use of the optional "cnonce" directive πŸ›‘οΈ.

4.12 Spoofing by Counterfeit Servers 🎭

Basic Authentication is vulnerable to spoofing by counterfeit servers 😩. Say a user thinks they're connecting to a host with information protected by a password they know, but they're actually connecting to a hostile server. That hostile server can ask for a password, save it for later use, and then fake an error πŸ’€. This kind of attack is harder with Digest Authentication, but the client has to know to demand that Digest authentication be used. It might use some of the techniques described above to counter "man-in-the-middle" attacks 🧠. Again, a visual indication of which authentication mechanism is in use, along with guidance on what each scheme actually means, can help the user spot this attack πŸ‘€πŸŒˆ.

4.13. Storing passwords πŸ—οΈ

okay so Digest auth needs the authenticating side (usually the server) to store some data derived from the user's name and password πŸ” It goes in a "password file" tied to a given realm. Normally that file holds pairs of username and H(A1), where H(A1) is the digested value of the username, realm, and password, like we covered above πŸ’–

Here's the tea β˜• If that password file gets leaked, an attacker gets immediate access to documents on the server in that realm. Unlike a standard UNIX password file, they don't have to decrypt anything to get in 😱 Getting the actual user's password would take decryption or, way more likely, a brute force attack. That's exactly why the realm is part of the digested data! If one Digest password file gets compromised, it doesn't automatically compromise other files that use the same username and password. (It does still expose them to brute force attack though, so don't get too comfy πŸ™ˆ)

There are two big security consequences here 🚨

First, the password file has to be protected as if it held unencrypted passwords πŸ”’ For the purpose of getting into documents in its realm, it basically does.

Second, the realm string should be unique among all the realms any single user is likely to use πŸ’… A realm string should include the name of the host doing the authentication. And just so we're clear, the client being unable to authenticate the server is a weakness of Digest Authentication πŸ₯Ί

4.14. Summary 🌸

Let's be real: by modern cryptographic standards, Digest Authentication is weak 😬 But for a huge range of purposes, it's still a really valuable replacement for Basic Authentication πŸ’œ It fixes some weaknesses of Basic, just not all of them. How strong it is can vary depending on the implementation πŸŽ€ The structure of the nonce (which depends on the server implementation) may affect how easy it is to pull off a replay attack. That's why a range of server options makes sense! Some implementations may be happy to take on the server overhead of one-time nonces or digests to kill the possibility of replay entirely ✨ Others may be fine with a nonce like the one recommended above, restricted to a single IP address and a single ETag, or with a limited lifetime πŸ¦‹

The bottom line? Any compliant implementation will be relatively weak by cryptographic standards, but any compliant implementation will be far superior to Basic Authentication πŸ‘‘


5. Sample implementation πŸ’»

Here's a sample C implementation that does the H(A1), H(A2), request-digest and response-digest calculations, plus a test program that computes the values used in the example of section 3.5 🧚 It uses the MD5 implementation from RFC 1321.

File "digcalc.h":

#define HASHLEN 16
typedef char HASH[HASHLEN];
#define HASHHEXLEN 32
typedef char HASHHEX[HASHHEXLEN+1];
#define IN
#define OUT

/* calculate H(A1) as per HTTP Digest spec */
void DigestCalcHA1(
    IN char * pszAlg,
    IN char * pszUserName,
    IN char * pszRealm,
    IN char * pszPassword,
    IN char * pszNonce,
    IN char * pszCNonce,
    OUT HASHHEX SessionKey
    );

/* calculate request-digest/response-digest as per HTTP Digest spec */
void DigestCalcResponse(
    IN HASHHEX HA1,           /* H(A1) */
    IN char * pszNonce,       /* nonce from server */
    IN char * pszNonceCount,  /* 8 hex digits */
    IN char * pszCNonce,      /* client nonce */
    IN char * pszQop,         /* qop-value: "", "auth", "auth-int" */
    IN char * pszMethod,      /* method from the request */
    IN char * pszDigestUri,   /* requested URL */
    IN HASHHEX HEntity,       /* H(entity body) if qop="auth-int" */
    OUT HASHHEX Response      /* request-digest or response-digest */
    );

File "digcalc.c":

#include <global.h>
#include <md5.h>
#include <string.h>
#include "digcalc.h"

void CvtHex(
    IN HASH Bin,
    OUT HASHHEX Hex
    )
{
    unsigned short i;
    unsigned char j;

    for (i = 0; i < HASHLEN; i++) {
        j = (Bin[i] >> 4) & 0xf;
        if (j <= 9)
            Hex[i*2] = (j + '0');
         else
            Hex[i*2] = (j + 'a' - 10);
        j = Bin[i] & 0xf;
        if (j <= 9)
            Hex[i*2+1] = (j + '0');
         else
            Hex[i*2+1] = (j + 'a' - 10);
    };
    Hex[HASHHEXLEN] = '\0';
};

/* calculate H(A1) as per spec */
void DigestCalcHA1(
    IN char * pszAlg,
    IN char * pszUserName,
    IN char * pszRealm,
    IN char * pszPassword,
    IN char * pszNonce,
    IN char * pszCNonce,
    OUT HASHHEX SessionKey
    )
{
      MD5_CTX Md5Ctx;
      HASH HA1;

      MD5Init(&Md5Ctx);
      MD5Update(&Md5Ctx, pszUserName, strlen(pszUserName));
      MD5Update(&Md5Ctx, ":", 1);
      MD5Update(&Md5Ctx, pszRealm, strlen(pszRealm));
      MD5Update(&Md5Ctx, ":", 1);
      MD5Update(&Md5Ctx, pszPassword, strlen(pszPassword));
      MD5Final(HA1, &Md5Ctx);
      if (stricmp(pszAlg, "md5-sess") == 0) {

            MD5Init(&Md5Ctx);
            MD5Update(&Md5Ctx, HA1, HASHLEN);
            MD5Update(&Md5Ctx, ":", 1);
            MD5Update(&Md5Ctx, pszNonce, strlen(pszNonce));
            MD5Update(&Md5Ctx, ":", 1);
            MD5Update(&Md5Ctx, pszCNonce, strlen(pszCNonce));
            MD5Final(HA1, &Md5Ctx);
      };
      CvtHex(HA1, SessionKey);
};

/* calculate request-digest/response-digest as per HTTP Digest spec */
void DigestCalcResponse(
    IN HASHHEX HA1,           /* H(A1) */
    IN char * pszNonce,       /* nonce from server */
    IN char * pszNonceCount,  /* 8 hex digits */
    IN char * pszCNonce,      /* client nonce */
    IN char * pszQop,         /* qop-value: "", "auth", "auth-int" */
    IN char * pszMethod,      /* method from the request */
    IN char * pszDigestUri,   /* requested URL */
    IN HASHHEX HEntity,       /* H(entity body) if qop="auth-int" */
    OUT HASHHEX Response      /* request-digest or response-digest */
    )
{
      MD5_CTX Md5Ctx;
      HASH HA2;
      HASH RespHash;
       HASHHEX HA2Hex;

      // calculate H(A2)
      MD5Init(&Md5Ctx);
      MD5Update(&Md5Ctx, pszMethod, strlen(pszMethod));
      MD5Update(&Md5Ctx, ":", 1);
      MD5Update(&Md5Ctx, pszDigestUri, strlen(pszDigestUri));
      if (stricmp(pszQop, "auth-int") == 0) {
            MD5Update(&Md5Ctx, ":", 1);
            MD5Update(&Md5Ctx, HEntity, HASHHEXLEN);
      };
      MD5Final(HA2, &Md5Ctx);
       CvtHex(HA2, HA2Hex);

      // calculate response
      MD5Init(&Md5Ctx);
      MD5Update(&Md5Ctx, HA1, HASHHEXLEN);
      MD5Update(&Md5Ctx, ":", 1);
      MD5Update(&Md5Ctx, pszNonce, strlen(pszNonce));
      MD5Update(&Md5Ctx, ":", 1);
      if (*pszQop) {

          MD5Update(&Md5Ctx, pszNonceCount, strlen(pszNonceCount));
          MD5Update(&Md5Ctx, ":", 1);
          MD5Update(&Md5Ctx, pszCNonce, strlen(pszCNonce));
          MD5Update(&Md5Ctx, ":", 1);
          MD5Update(&Md5Ctx, pszQop, strlen(pszQop));
          MD5Update(&Md5Ctx, ":", 1);
      };
      MD5Update(&Md5Ctx, HA2Hex, HASHHEXLEN);
      MD5Final(RespHash, &Md5Ctx);
      CvtHex(RespHash, Response);
};

File "digtest.c":


#include <stdio.h>
#include "digcalc.h"

void main(int argc, char ** argv) {

      char * pszNonce = "dcd98b7102dd2f0e8b11d0f600bfb0c093";
      char * pszCNonce = "0a4f113b";
      char * pszUser = "Mufasa";
      char * pszRealm = "testrealm@host.com";
      char * pszPass = "Circle Of Life";
      char * pszAlg = "md5";
      char szNonceCount[9] = "00000001";
      char * pszMethod = "GET";
      char * pszQop = "auth";
      char * pszURI = "/dir/index.html";
      HASHHEX HA1;
      HASHHEX HA2 = "";
      HASHHEX Response;

      DigestCalcHA1(pszAlg, pszUser, pszRealm, pszPass, pszNonce,
pszCNonce, HA1);
      DigestCalcResponse(HA1, pszNonce, szNonceCount, pszCNonce, pszQop,
       pszMethod, pszURI, HA2, Response);
      printf("Response = %s\n", Response);
};

6. Acknowledgments 🫢

Eric W. Sink, of AbiSource, Inc., was one of the original authors before the specification went through a big revision πŸ’–

Beyond the authors, valuable discussion that helped create this document came from Peter J. Churchyard, Ned Freed, and David M. Kristol 🌈

Jim Gettys and Larry Masinter edited this document for update πŸŽ€

7. References πŸ“š

[1] Berners-Lee, T., Fielding, R. and H. Frystyk, "Hypertext Transfer Protocol -- HTTP/1.0", RFC 1945, May 1996.

[2] Fielding, R., Gettys, J., Mogul, J., Frysyk, H., Masinter, L., Leach, P. and T. Berners-Lee, "Hypertext Transfer Protocol -- HTTP/1.1", RFC 2616, June 1999.

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

[4] Freed, N. and N. Borenstein. "Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies", RFC 2045, November 1996.

[5] Dierks, T. and C. Allen "The TLS Protocol, Version 1.0", RFC 2246, January 1999.

[6] Franks, J., Hallam-Baker, P., Hostetler, J., Leach, P., Luotonen, A., Sink, E. and L. Stewart, "An Extension to HTTP : Digest Access Authentication", RFC 2069, January 1997.

[7] Berners Lee, T, Fielding, R. and L. Masinter, "Uniform Resource Identifiers (URI): Generic Syntax", RFC 2396, August 1998.

[8] Kaliski, B.,Robshaw, M., "Message Authentication with MD5", CryptoBytes, Sping 1995, RSA Inc, (http://www.rsa.com/rsalabs/pubs/cryptobytes/spring95/md5.htm)

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

[10] Morgan, B., Alvestrand, H., Hodges, J., Wahl, M., "Authentication Methods for LDAP", Work in Progress.

8. Authors' Addresses 🌟

John Franks Professor of Mathematics Department of Mathematics Northwestern University Evanston, IL 60208-2730, USA

EMail: john@math.nwu.edu

Phillip M. Hallam-Baker Principal Consultant Verisign Inc. 301 Edgewater Place Suite 210 Wakefield MA 01880, USA

EMail: pbaker@verisign.com

Jeffery L. Hostetler Software Craftsman AbiSource, Inc. 6 Dunlap Court Savoy, IL 61874

EMail: jeff@AbiSource.com

Scott D. Lawrence Agranat Systems, Inc. 5 Clocktower Place, Suite 400 Maynard, MA 01754, USA

EMail: lawrence@agranat.com

Paul J. Leach Microsoft Corporation 1 Microsoft Way Redmond, WA 98052, USA

EMail: paulle@microsoft.com

Ari Luotonen Member of Technical Staff Netscape Communications Corporation 501 East Middlefield Road Mountain View, CA 94043, USA

Lawrence C. Stewart Open Market, Inc. 215 First Street Cambridge, MA 02142, USA

EMail: stewart@OpenMarket.com

Okay bestie, time for the legal bit πŸ’… The Internet Society owns the copyright, so here's the official fine print, word for word.

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

This document and translations of it may be copied and furnished to others, and derivative works that comment on or otherwise explain it or assist in its implementation may be prepared, copied, published and distributed, in whole or in part, without restriction of any kind, provided that the above copyright notice and this paragraph are included on all such copies and derivative works. However, this document itself may not be modified in any way, such as by removing the copyright notice or references to the Internet Society or other Internet organizations, except as needed for the purpose of developing Internet standards in which case the procedures for copyrights defined in the Internet Standards process must be followed, or as required to translate it into languages other than English.

The limited permissions granted above are perpetual and will not be revoked by the Internet Society or its successors or assigns.

This document and the information contained herein 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 🫢

Last but not least, a big thank-you πŸ’œ Funding for the RFC Editor function is currently provided by the Internet Society. 🌈

πŸ“¦ where this page comes from