We have seen basic authentication in one of my previous posts. Basic authentication is simple. It just sends the user credentials in the HTTP header. If HTTPS is not used, the credentials will be available for every one to see. ASP.NET Web API that uses basic authentication can be tested through the browser itself. When the browser makes a GET request, it first gets a 401 response with WWW-Authenticate : Basic header. No body knows HTTP better than a web browser! So, my browser now knows that it has to send the credentials in the HTTP header using basic scheme. So, my browser, IE in this case, pops up a dialog, gets the credentials, packages the same in the correct format and sends it to the server.
Since I don’t use HTTPS in my machine, it does warn that username and password is about to be sent in an insecure manner. So, in those cases where I cannot use HTTPS for whatever reason, an alternative is to use digest authentication. Just like with basic authentication, browser knows how to handle the situation, when the web server sends a 401 response with WWW-Authenticate : Digest header. It again pops up a dialog, gathers the credentials, frames and sends the HTTP authorization header to the server.
This time around there is no warning though. Since my ASP.NET Web API has sent the realm back as part of WWW-Authenticate : Digest header, it is being displayed in the popup as against the null in previous case. In this post, we will see what digest authentication is all about. Digest authentication is comparatively safer than basic authentication because the actual password is not sent to the server but only a MD5 hash (digest) is sent. To understand the process, let us start from the beginning.
Client (web browser, for example) sends a HTTP GET. Server sends back a 401 response with Digest header. Header has something called a nonce or a number used once. It is not exactly a use and throw thing but as you see from the above, this value continues to be passed along in the subsequent requests until the time server thinks it is time for a fresh nonce. At that time, server sends back a 401 response along with a fresh nonce. The expiration time for a nonce is set to prevent replay attacks. Well, if the expiration time is one minute, within that minute, if a malicious user sends this in a request, server will accept the nonce but there are other mechanisms in place to prevent those attacks. Nonce with a definite life time is better than a nonce that is valid for ever!
Next up is QOP or quality of protection. I’ll limit this post to only “auth”. QOP determines how other parameters are combined and hashed to get the final digest value. With “auth”, a client nonce cnonce and a client nonce counter nc comes into picture. Just like the server nonce, which is sent by server to the client, this is a nonce generated by client and sent to the server. Let’s park these two aside for now.
So, client has received a server nonce and qop of “auth”. Using the server nonce, user name, realm, password, HTTP method (GET, POST, etc), URI of the resource the client is requesting, client cooks up a digest value. The recipe is determined by QOP. If QOP is “auth”, then it is done as below.
- Compute MD5 hash of username : realm : password. Example: MD5 hash of the string john:RealmOfBadri:abracadabra. Let’s call it HA1 and it is aa71f01f351.
- Compute MD5 hash of method:uri. Example, MD5 hash of string GET:/api/Products. Let’s call it HA2 and it is 939e7552ac.
- Compute MD5 hash of HA1:nonce:nc:cnonce:qop:HA2. Example, MD5 hash of string aa71f01f351:dcd98b…bfb0c093:00000001:0a4f113b:auth:939e7552ac
[Hash values above are mutilated to make them smaller so that they fit into the screen. Real values will be different from the above in terms of length]
Now, back to the cnonce and nc that we parked before. Client nonce is just same as server nonce and it is generated (0a4f113b, in our example). nc is the counter that typically starts off with 00000001. Next request from the same client will have the same server nonce and client nonce but now nc will be 00000002. It need not increment by 1 but it has to be greater than the previous nc. Finally, MD5 hash calculated in he step 3 is sent by the client in response field in the authorization header, like below.
Authorization: Digest username=“john”, realm=” RealmOfBadri”, nonce=”dcd98b7102dd2f0e8b11d0f600bfb0c093″, uri=” /api/Products”, qop=auth, nc=00000001, cnonce=”0a4f113b…”, response=”6629fae49393a05397450978507c4ef1”
Server gets this header. First, it validates the expiration of server nonce. If it has expired, it immediately sends back a 401 and a fresh nonce for the client to start with the previous step once again. If nonce is valid, it then gets the nc that it received last time corresponding to this server nonce. If the nc in this request is lesser or equal to the value it has on record, request is rejected with a 401. If not, server retrieves the password for the user ‘john’ from some place (may be a database). Now, server computes the MD5 hash by following the three steps above. If client has sent the right password, MD5 hash, the server has just calculated will exactly match the hash sent by the client in the response field. If there is any difference, hashes will not match and server rejects the request again with a 401.
Let’s say a malicious user gets the above header. He has two options. One is to replay the old request. If server nonce expiration time is up, he cannot replay. Before expiry, he/she must use a nc which is greater than the current one. It is easier to add one and come up with a new nc of 00000002 but then this value is part of the MD5 digest. So, without knowing the password, hash cannot be calculated correctly and server will reject the request. So, purpose of nc and cnonce is to prevent replay attacks and foil chosen plain text attacks.
Another option a malicious user has is to use brute force attack, that is, to do what server does – which is to compute the MD5 hash by following the three steps. Of course, password is not available but by using one password after another, password can be guessed. First try with all the individual alphabets and then combinations. If hash matches for any combination, that is the password. A dictionary can be used as well to guess passwords since most of the time human beings prefer words or a sequence of them to be the password. By using upper and lower case, numbers and special characters, number of combinations can be made really big but a determined hacker with resources can definitely guess the password. So, digest authentication is not 100% secure but better than basic authentication over HTTP.
ASP.NET Web API implementing digest authentication can be easily tested using a standard web browser. Generation of client nonce and incrementing client nonce counter are all automatically done by web browsers such as IE and can be fully used for testing purposes. I’ll try to show some related ASP.NET Web API C# code in my next post.
One last point – ASP.NET Web API or any HTTP service for that matter, can support both basic and digest authentication at the same time. It is all about negotiation. A smart piece of software such as a browser, on receiving both WWW-Authenticate : Basic and WWW-Authenticate : Digest, as part of 401 response will go for digest, since it is comparatively stronger. A client that cannot participate in digest authentication can use basic, of course. One possible problem with the freedom to choose is that, a man in the middle can remove WWW-Authenticate : Digest header tricking a client to send the credentials in basic scheme and pick up the same from the header. There is no mechanism for a client to verify the server’s identity!