{"id":"9a39592236d93052","repo":"aio-libs/aiohttp","slug":"fingerprint-has-invalid-length","errorCode":null,"errorMessage":"fingerprint has invalid length","messagePattern":"fingerprint has invalid length","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"aiohttp/client_reqrep.py","lineNumber":187,"sourceCode":"        For backwards compatibility, the real_url parameter is optional.\n        \"\"\"\n        return tuple.__new__(\n            cls, (url, method, headers, url if real_url is sentinel else real_url)\n        )\n\n\nclass Fingerprint:\n    HASHFUNC_BY_DIGESTLEN = {\n        16: md5,\n        20: sha1,\n        32: sha256,\n    }\n\n    def __init__(self, fingerprint: bytes) -> None:\n        digestlen = len(fingerprint)\n        hashfunc = self.HASHFUNC_BY_DIGESTLEN.get(digestlen)\n        if not hashfunc:\n            raise ValueError(\"fingerprint has invalid length\")\n        elif hashfunc is md5 or hashfunc is sha1:\n            raise ValueError(\"md5 and sha1 are insecure and not supported. Use sha256.\")\n        self._hashfunc = hashfunc\n        self._fingerprint = fingerprint\n\n    @property\n    def fingerprint(self) -> bytes:\n        return self._fingerprint\n\n    def check(self, transport: asyncio.Transport) -> None:\n        if not transport.get_extra_info(\"sslcontext\"):\n            return\n        sslobj = transport.get_extra_info(\"ssl_object\")\n        cert = sslobj.getpeercert(binary_form=True)\n        got = self._hashfunc(cert).digest()\n        if got != self._fingerprint:\n            host, port, *_ = transport.get_extra_info(\"peername\")\n            raise ServerFingerprintMismatch(self._fingerprint, got, host, port)","sourceCodeStart":169,"sourceCodeEnd":205,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/client_reqrep.py#L169-L205","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Use a 32-byte sha256 fingerprint (the only currently supported secure option, see error 65).","Decode hex to bytes: bytes.fromhex(hex_fp) before passing to Fingerprint.","Generate with: openssl s_client -connect host:443 | openssl x509 -pubkey | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary."],"exampleFix":"# before\nfp = aiohttp.Fingerprint(b\"2f:ca:9b:...\")  # wrong length / text\nfp = aiohttp.Fingerprint(hex_digest)     # str, not bytes\n# after\nraw = bytes.fromhex(\"2fca9b...\")  # exactly 32 bytes\nfp = aiohttp.Fingerprint(raw)","handlingStrategy":"validation","validationCode":"def make_fingerprint(raw: bytes) -> aiohttp.Fingerprint:\n    if len(raw) != 32:\n        raise ValueError(f\"expected 32-byte sha256 digest, got {len(raw)} bytes\")\n    return aiohttp.Fingerprint(raw)","typeGuard":"def is_sha256_length(raw: bytes) -> bool:\n    return isinstance(raw, (bytes, bytearray)) and len(raw) == 32","tryCatchPattern":null,"preventionTips":["Always generate pins with sha256.","Decode hex strings with bytes.fromhex before passing.","Store pins as raw 32-byte bytes, not hex strings, in config."],"tags":["tls","ssl","fingerprint","certificate-pinning"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}