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 by Fingerprint.__init__ when the fingerprint length maps to md5 (16 bytes) or sha1 (20 bytes). Both algorithms are cryptographically broken, so aiohttp refuses to use them for certificate pinning and directs the caller to SHA-256 (32 bytes). This is a deliberate hard security policy, not a capability gap.

Solutions

  1. Recompute the fingerprint as SHA-256 of the DER certificate and pass the 32-byte digest.
  2. Regenerate via: openssl x509 -in cert.pem -noout -pubkey | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary.
  3. Remove any md5/sha1 fingerprint from config files and replace with the sha256 value.
  4. Do not attempt to bypass; md5/sha1 pinning is insecure.

Example fix

# before
import hashlib
fp = Fingerprint(hashlib.md5(der_cert).digest())  # 16 bytes -> ValueError

# after
fp = Fingerprint(hashlib.sha256(der_cert).digest())  # 32 bytes
Defensive patterns

Strategy: validation

Validate before calling

import hashlib

def sha256_fingerprint(der_cert: bytes) -> bytes:
    # always produce a supported 32-byte sha256 fingerprint
    return hashlib.sha256(der_cert).digest()

Type guard

def is_secure_fingerprint(fp: bytes) -> bool:
    # 32 bytes => sha256 (the only secure length aiohttp accepts)
    return isinstance(fp, (bytes, bytearray)) and len(fp) == 32

Try / catch

from aiohttp import Fingerprint

try:
    fp = Fingerprint(raw)
except ValueError as e:
    if 'insecure' in str(e):
        raise ValueError('Recompute the fingerprint with SHA-256 (32 bytes)')
    raise

Prevention

When it happens

Trigger: Calling Fingerprint(b'<16 bytes>') or Fingerprint(b'<20 bytes>') — i.e. supplying an MD5 or SHA-1 digest of the server certificate. Construction fails before any request is made.

Common situations: Legacy pinning config generated with md5/sha1; tutorials/examples predating the deprecation; tooling that defaults to sha1 for certificate digests; copying a fingerprint from an old openssl output.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/4a4c066ac1ebf5fc. Report an issue: GitHub.

Appendix: 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 d041d4d0fd)