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
- Recompute the fingerprint as SHA-256 of the DER certificate and pass the 32-byte digest.
- Regenerate via: openssl x509 -in cert.pem -noout -pubkey | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary.
- Remove any md5/sha1 fingerprint from config files and replace with the sha256 value.
- 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
- Regenerate all existing md5/sha1 pins as sha256.
- Audit config for 16- or 20-byte fingerprint values.
- Use openssl with -sha256 when extracting cert digests.
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
- fingerprint has invalid length
- Bad HTTP method in status line
- Method cannot contain non-token characters
- ssl should be SSLContext, Fingerprint, or bool, got
- 1002
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)