aio-libs/aiohttp · error · ValueError

md5 and sha1 are insecure and not supported. Use sha256.

Error message

md5 and sha1 are insecure and not supported. Use sha256.

What it means

Raised as ValueError in Fingerprint.__init__ when the fingerprint length maps to md5 (16 bytes) or sha1 (20 bytes). Both algorithms are considered cryptographically broken, so aiohttp refuses to use them for TLS certificate pinning and only allows sha256 (32 bytes).

Source

Thrown at aiohttp/client_reqrep.py:189

        return tuple.__new__(
            cls, (url, method, headers, url if real_url is sentinel else real_url)
        )


class Fingerprint:
    HASHFUNC_BY_DIGESTLEN = {
        16: md5,
        20: sha1,
        32: sha256,
    }

    def __init__(self, fingerprint: bytes) -> None:
        digestlen = len(fingerprint)
        hashfunc = self.HASHFUNC_BY_DIGESTLEN.get(digestlen)
        if not hashfunc:
            raise ValueError("fingerprint has invalid length")
        elif hashfunc is md5 or hashfunc is sha1:
            raise ValueError("md5 and sha1 are insecure and not supported. Use sha256.")
        self._hashfunc = hashfunc
        self._fingerprint = fingerprint

    @property
    def fingerprint(self) -> bytes:
        return self._fingerprint

    def check(self, transport: asyncio.Transport) -> None:
        if not transport.get_extra_info("sslcontext"):
            return
        sslobj = transport.get_extra_info("ssl_object")
        cert = sslobj.getpeercert(binary_form=True)
        got = self._hashfunc(cert).digest()
        if got != self._fingerprint:
            host, port, *_ = transport.get_extra_info("peername")
            raise ServerFingerprintMismatch(self._fingerprint, got, host, port)

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Regenerate the fingerprint using sha256 to get a 32-byte digest.
  2. Update any config management / CI that generates pins to use sha256.
  3. Remove md5/sha1 pins from rotation after verifying the new sha256 pin matches.

Example fix

# before (sha1, 20 bytes)
fp = aiohttp.Fingerprint(sha1_digest)
# after (sha256, 32 bytes)
fp = aiohttp.Fingerprint(sha256_digest)
Defensive patterns

Strategy: validation

Validate before calling

import hashlib
fp_sha256 = hashlib.sha256(pubkey_der).digest()  # 32 bytes
aiohttp.Fingerprint(fp_sha256)

Type guard

def is_sha256_fingerprint(raw: bytes) -> bool:
    return isinstance(raw, (bytes, bytearray)) and len(raw) == 32

Prevention

When it happens

Trigger: Fires at line 188-189 after HASHFUNC_BY_DIGESTLEN resolves the length to md5 or sha1. Triggered by passing a 16- or 20-byte fingerprint.

Common situations: Legacy pinning configs generated with sha1; copying fingerprints from old documentation; migrating from libraries that still allow sha1.

Related errors


AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04). Data as JSON: /data/errors/4a4c066ac1ebf5fc.json. Report an issue: GitHub.