{"id":"4a4c066ac1ebf5fc","repo":"aio-libs/aiohttp","slug":"md5-and-sha1-are-insecure-and-not-supported-use-s","errorCode":null,"errorMessage":"md5 and sha1 are insecure and not supported. Use sha256.","messagePattern":"md5 and sha1 are insecure and not supported\\. Use sha256\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"aiohttp/client_reqrep.py","lineNumber":189,"sourceCode":"        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)\n\n","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/client_reqrep.py#L171-L207","documentation":"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).","triggerScenarios":"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.","commonSituations":"Legacy pinning configs generated with sha1; copying fingerprints from old documentation; migrating from libraries that still allow sha1.","solutions":["Regenerate the fingerprint using sha256 to get a 32-byte digest.","Update any config management / CI that generates pins to use sha256.","Remove md5/sha1 pins from rotation after verifying the new sha256 pin matches."],"exampleFix":"# before (sha1, 20 bytes)\nfp = aiohttp.Fingerprint(sha1_digest)\n# after (sha256, 32 bytes)\nfp = aiohttp.Fingerprint(sha256_digest)","handlingStrategy":"validation","validationCode":"import hashlib\nfp_sha256 = hashlib.sha256(pubkey_der).digest()  # 32 bytes\naiohttp.Fingerprint(fp_sha256)","typeGuard":"def is_sha256_fingerprint(raw: bytes) -> bool:\n    return isinstance(raw, (bytes, bytearray)) and len(raw) == 32","tryCatchPattern":null,"preventionTips":["Regenerate any legacy sha1/md5 pins as sha256.","Audit config for 16- or 20-byte fingerprints.","Pin generation scripts should hard-code sha256."],"tags":["security","tls","ssl","fingerprint","certificate-pinning","cryptography"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}