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

  1. Read the error message — it lists exactly which algorithms are supported.
  2. Change the server to offer one of: MD5, SHA-256, SHA-512 (prefer SHA-256 or stronger; MD5/SHA are weak).
  3. Verify the token spelling matches a supported key exactly (e.g. 'SHA-256' with the dash, not 'SHA256_256').
  4. 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

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


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 Payload

View on GitHub (pinned to d041d4d0fd)