Digest Access Authentication Deep Dive — Challenge-Response Without Plaintext
In this tutorial, you will learn about Digest Access Authentication Deep Dive. We cover key concepts, practical examples, and best practices to help you master this topic.
HTTP Digest Access Authentication uses a challenge-response mechanism where the server sends a nonce and the client responds with a cryptographic hash, never sending the password in plaintext.
What You'll Learn
The Digest Auth handshake protocol, hash computation with MD5/MD5-sess, qop (quality of protection), nonce counting for replay protection, and server-side nonce validation.
Why It Matters
Digest Auth is still relevant for Embedded Systems, IoT devices, and legacy APIs where TLS is not feasible. It provides replay protection without requiring a PKI infrastructure.
Real-World Use
Home router administration panels, IP camera APIs, and some embedded device management interfaces rely on Digest Auth. Durga Antivirus Pro uses Digest Auth for its on-premise network scanner configuration API.
sequenceDiagram
Client->>Server: GET /config
Server->>Client: 401 Unauthorized
WWW-Authenticate: Digest realm=..., nonce=...
Client->>Client: Compute HA1 = MD5(user:realm:pass)
Compute HA2 = MD5(method:uri)
Compute response = MD5(HA1:nonce:nc:cnonce:qop:HA2)
Client->>Server: Authorization: Digest username=..., response=..., nc=...
Server->>Server: Recompute hash, compare
Server->>Client: 200 OK + Config Data
Code Example: Server-Side Digest Auth Challenge
import hashlib, secrets, time
from flask import Flask, request, jsonify, make_response
app = Flask(__name__)
# In-memory nonce store (use Redis in production)
nonces = {}
USERS = {"admin": "secret123"}
REALM = "Durga Scanner Config"
def generate_nonce():
return secrets.token_hex(16)
def compute_response(username, realm, password, method, uri, nonce, nc, cnonce, qop):
ha1 = hashlib.md5(f"{username}:{realm}:{password}".encode()).hexdigest()
ha2 = hashlib.md5(f"{method}:{uri}".encode()).hexdigest()
return hashlib.md5(f"{ha1}:{nonce}:{nc}:{cnonce}:{qop}:{ha2}".encode()).hexdigest()
@app.route("/config")
def get_config():
auth = request.headers.get("Authorization", "")
if not auth.startswith("Digest "):
nonce = generate_nonce()
nonces[nonce] = time.time()
resp = jsonify({"error": "Digest auth required"})
resp.headers["WWW-Authenticate"] = \
f'Digest realm="{REALM}", nonce="{nonce}", qop="auth", algorithm=MD5'
return resp, 401
# Parse digest params
params = {}
for part in auth[7:].split(", "):
key, _, val = part.partition("=")
params[key.strip()] = val.strip().strip('"')
username = params.get("username", "")
password = USERS.get(username, "")
expected = compute_response(
username, REALM, password,
request.method, request.path,
params.get("nonce"), params.get("nc", "00000001"),
params.get("cnonce", ""), params.get("qop", "auth")
)
if params.get("response") != expected:
return jsonify({"error": "Invalid credentials"}), 401
# Validate nonce freshness
nonce_time = nonces.get(params.get("nonce"))
if not nonce_time or (time.time() - nonce_time) > 300:
return jsonify({"error": "Nonce expired"}), 401
return jsonify({"scanner_config": {"interval": 3600, "depth": "full"}})
if __name__ == "__main__":
app.run()
Expected curl test:
# First request (gets challenge)
curl -s -w "\nHTTP %{http_code}" http://localhost:5000/config
# {"error":"Digest auth required"}
# HTTP 401
# Use --digest flag for automatic handling
curl -s --digest -u "admin:secret123" \
http://localhost:5000/config
# {"scanner_config":{"interval":3600,"depth":"full"}}
# HTTP 200
Code Example: Client-Side Digest Auth in Python
import hashlib, requests
from urllib.parse import urlparse
def digest_auth_get(url, username, password):
# First request — expect 401
resp = requests.get(url)
if resp.status_code != 401:
return resp
www_auth = resp.headers.get("WWW-Authenticate", "")
params = {}
for part in www_auth.replace("Digest ", "").split(", "):
key, _, val = part.partition("=")
params[key.strip()] = val.strip().strip('"')
realm = params.get("realm", "")
nonce = params.get("nonce", "")
qop = params.get("qop", "auth")
algorithm = params.get("algorithm", "MD5")
parsed = urlparse(url)
uri = f"{parsed.path}?{parsed.query}" if parsed.query else parsed.path
cnonce = secrets.token_hex(8)
nc = "00000001"
ha1 = hashlib.md5(f"{username}:{realm}:{password}".encode()).hexdigest()
ha2 = hashlib.md5(f"GET:{uri}".encode()).hexdigest()
response = hashlib.md5(
f"{ha1}:{nonce}:{nc}:{cnonce}:{qop}:{ha2}".encode()
).hexdigest()
auth_header = (
f'Digest username="{username}", realm="{realm}", '
f'nonce="{nonce}", uri="{uri}", '
f'qop={qop}, nc={nc}, cnonce="{cnonce}", '
f'response="{response}", algorithm={algorithm}'
)
return requests.get(url, headers={"Authorization": auth_header})
# Usage
resp = digest_auth_get("http://localhost:5000/config", "admin", "secret123")
print(resp.json())
Code Example: Nonce Expiry and Replay Prevention
import secrets, time, hashlib
from collections import OrderedDict
class NonceManager:
def __init__(self, expiry=300, max_size=10000):
self.nonces = OrderedDict()
self.expiry = expiry
self.max_size = max_size
def create_nonce(self):
nonce = secrets.token_hex(16)
self.nonces[nonce] = {
"created": time.time(),
"nc_values": set()
}
self._cleanup()
return nonce
def validate(self, nonce, nc):
data = self.nonces.get(nonce)
if not data:
return False, "Nonce not found"
if (time.time() - data["created"]) > self.expiry:
del self.nonces[nonce]
return False, "Nonce expired"
if nc in data["nc_values"]:
return False, "Nonce count replay detected"
data["nc_values"].add(nc)
return True, "OK"
def _cleanup(self):
now = time.time()
expired = [k for k, v in self.nonces.items()
if (now - v["created"]) > self.expiry]
for k in expired:
del self.nonces[k]
while len(self.nonces) > self.max_size:
self.nonces.popitem(last=False)
nonce_mgr = NonceManager()
Common Mistakes
1. Not Validating Nonce Count (nc)
Without nc validation, an attacker can replay a captured Digest response within the nonce window. Always track and reject duplicate nc values.
2. Using Weak Nonces
Nonces must be cryptographically random and unique per challenge. Sequential or timestamp-only nonces are predictable.
3. Ignoring qop
Without quality-of-protection, the response hash covers fewer fields, reducing replay protection. Always require qop=auth or qop=auth-int.
4. Nonce Reuse Across Users
Each authentication attempt should get a unique nonce. Reusing nonces across users makes the system vulnerable to replay attacks.
5. Storing Passwords in Plaintext
The server needs the plaintext password (or HA1 hash) to verify Digest responses. Store HA1 values instead of plaintext passwords for security.
Practice Questions
- How does Digest Auth differ from Basic Auth in credential transmission?
- What fields are included in the HA1 and HA2 hash computations?
- Why is the nonce count (nc) important?
- What does qop=auth-int add beyond qop=auth?
- Why can't bcrypt be used with Digest Auth?
Answers:
- Basic Auth sends Base64-encoded credentials. Digest Auth never sends the password — it sends a hash computed from the password, nonce, and other values.
- HA1 = MD5(username:realm:password). HA2 = MD5(method:uri). The full response = MD5(HA1:nonce:nc:cnonce:qop:HA2).
- nc prevents replay attacks by ensuring each Digest response uses a unique counter value. The server tracks which nc values have been seen.
- qop=auth-int includes the entity body in the HA2 hash, protecting the request body from tampering in addition to the URI and method.
- Digest Auth requires the server to recompute the MD5 hash with the password. bcrypt is one-way and intentionally slow, making it unsuitable for the Digest computation.
Challenge: Implement a full Digest Auth system with nonce rotation, nc tracking, and automatic stale nonce handling. Test with curl --digest and ensure replay attacks are blocked.
FAQ
Mini Project
Build a Flask API that implements full Digest Access Authentication with nonce management, qop=auth, stale nonce handling, and automatic nonce expiry cleanup. Test with Python requests and curl.
What's Next
Now compare Digest Auth with HTTP Basic Authentication and learn about Token Storage Strategies for modern API auth patterns.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro