aio-libs/aiohttp · error · ClientError
Digest auth error: Unsupported hash algorithm
Error message
Digest auth error: Unsupported hash algorithm: {algorithm}. Supported algorithms: {', '.join(SUPPORTED_ALGORITHMS)} What it means
Raised when the Digest challenge's 'algorithm' directive (upper-cased) is not in aiohttp's supported set: MD5, MD5-SESS, SHA, SHA-SESS, SHA256, SHA-256, SHA512, SHA-512 and their -SESS variants. If 'algorithm' is absent the default is MD5; this error only fires when an explicit unsupported value is supplied. The error message lists the supported algorithms for quick diagnosis.
Solutions
- Read the error message — it lists exactly which algorithms are supported.
- Change the server to offer one of: MD5, SHA-256, SHA-512 (prefer SHA-256 or stronger; MD5/SHA are weak).
- Verify the token spelling matches a supported key exactly (e.g. 'SHA-256' with the dash, not 'SHA256_256').
- If the server requires an unsupported algorithm, switch to a Digest library that supports it or request the feature upstream.
Example fix
# before — server: algorithm=SHA3-256 await session.get(url) # ClientError: Unsupported hash algorithm: SHA3-256 # after — server uses a supported algorithm # WWW-Authenticate: Digest realm="x", nonce="...", algorithm=SHA-256
Defensive patterns
Strategy: validation
Validate before calling
SUPPORTED = {'MD5','MD5-SESS','SHA','SHA-SESS','SHA256','SHA-256','SHA256-SESS',
'SHA-256-SESS','SHA512','SHA-512','SHA512-SESS','SHA-512-SESS'}
def algorithm_supported(www_authenticate: str) -> bool:
import re
m = re.search(r'algorithm=([A-Za-z0-9-]+)', www_authenticate)
if not m:
return True # default MD5
return m.group(1).upper() in SUPPORTED Type guard
def is_supported_digest_algorithm(algorithm: str) -> bool:
return algorithm.upper() in {
'MD5','MD5-SESS','SHA','SHA-SESS','SHA256','SHA-256',
'SHA256-SESS','SHA-256-SESS','SHA512','SHA-512','SHA512-SESS','SHA-512-SESS'} Try / catch
from aiohttp import ClientError
try:
resp = await session.get(url)
except ClientError as e:
if 'Unsupported hash algorithm' in str(e):
# parse the supported list from the message and reconfigure server
log.error('Digest algorithm unsupported. %s', e)
raise Prevention
- Prefer SHA-256 for new Digest deployments; MD5/SHA are weak.
- Keep the supported-algorithm list in sync with the aiohttp version in use.
- Test against the real server's challenge during CI.
When it happens
Trigger: Server sends 'algorithm=BCRYPT' or any token not in DigestFunctions. After upper-casing, the 'algorithm not in DigestFunctions' check in _encode() fails and raises ClientError, including the supported list in the message.
Common situations: Server advertising a modern/non-standard algorithm aiohttp has not implemented (e.g. 'SHA3-256', 'argon2'); case or dash variant mismatch (note aiohttp normalizes case but expects exact token spelling like 'SHA-256'); legacy server using a custom scheme name.
Related errors
- Digest auth error: Unsupported Quality of Protection (qop)…
- Malformed Digest auth challenge: Missing 'nonce' parameter
- Malformed Digest auth challenge: Missing 'realm' parameter
- Security issue: Digest auth challenge contains empty…
- A ":" is not allowed in username (RFC 1945#section-11.1)
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/5bcc60dec9c3314a.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/client_middleware_digest_auth.py:293
path = URL(url).raw_path_qs
# Process QoP
qop = ""
qop_bytes = b""
if qop_raw:
valid_qops = {"auth", "auth-int"}.intersection(
{q.strip() for q in qop_raw.split(",") if q.strip()}
)
if not valid_qops:
raise ClientError(
f"Digest auth error: Unsupported Quality of Protection (qop) value(s): {qop_raw}"
)
qop = "auth-int" if "auth-int" in valid_qops else "auth"
qop_bytes = qop.encode("utf-8")
if algorithm not in DigestFunctions:
raise ClientError(
f"Digest auth error: Unsupported hash algorithm: {algorithm}. "
f"Supported algorithms: {', '.join(SUPPORTED_ALGORITHMS)}"
)
hash_fn: Final = DigestFunctions[algorithm]
def H(x: bytes) -> bytes:
"""RFC 7616 Section 3: Hash function H(data) = hex(hash(data))."""
return hash_fn(x).hexdigest().encode()
def KD(s: bytes, d: bytes) -> bytes:
"""RFC 7616 Section 3: KD(secret, data) = H(concat(secret, ":", data))."""
return H(b":".join((s, d)))
# Calculate A1 and A2
A1 = b":".join((self._login_bytes, realm_bytes, self._password_bytes))
A2 = f"{method.upper()}:{path}".encode()
if qop == "auth-int":
if isinstance(body, Payload): # will always be empty bytes unless PayloadView on GitHub (pinned to d041d4d0fd)