aio-libs/aiohttp · error · ValueError

fingerprint has invalid length

Error message

fingerprint has invalid length

What it means

Raised as ValueError in Fingerprint.__init__ when the supplied fingerprint bytes are not one of the recognized digest lengths. HASHFUNC_BY_DIGESTLEN maps length 16->md5, 20->sha1, 32->sha256; any other length has no matching hash function and is rejected before TLS pinning can be set up.

Source

Thrown at aiohttp/client_reqrep.py:187

        For backwards compatibility, the real_url parameter is optional.
        """
        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. Use a 32-byte sha256 fingerprint (the only currently supported secure option, see error 65).
  2. Decode hex to bytes: bytes.fromhex(hex_fp) before passing to Fingerprint.
  3. Generate with: openssl s_client -connect host:443 | openssl x509 -pubkey | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary.

Example fix

# before
fp = aiohttp.Fingerprint(b"2f:ca:9b:...")  # wrong length / text
fp = aiohttp.Fingerprint(hex_digest)     # str, not bytes
# after
raw = bytes.fromhex("2fca9b...")  # exactly 32 bytes
fp = aiohttp.Fingerprint(raw)
Defensive patterns

Strategy: validation

Validate before calling

def make_fingerprint(raw: bytes) -> aiohttp.Fingerprint:
    if len(raw) != 32:
        raise ValueError(f"expected 32-byte sha256 digest, got {len(raw)} bytes")
    return aiohttp.Fingerprint(raw)

Type guard

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

Prevention

When it happens

Trigger: Fires at line 186-187 when len(fingerprint) is not in {16,20,32}. Common when passing a hex string instead of raw bytes, a truncated/concatenated fingerprint, or a different hash length (e.g. sha512 = 64 bytes).

Common situations: Passing a hex-encoded fingerprint string (length 64 chars) instead of the 32-byte digest; copying a fingerprint from openssl output without decoding; using SHA-384/512 pinning which aiohttp doesn't support.

Related errors


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