import hashlib from cryptography.hazmat.primitives.serialization import ( Encoding, PublicFormat, load_pem_public_key,) public_key = load_pem_public_key(open("pub.pem", "rb").read())spki_der = public_key.public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo) digest = hashlib.sha256(spki_der).hexdigest()print(":".join(digest[i:i + 2] for i in range(0, len(digest), 2)))import base64 print(base64.b64encode(hashlib.sha256(spki_der).digest()).decode())How it works
public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)yields the canonical binary SPKI — 91 bytes for a P-256 key.hashlib.sha256over those bytes is the whole computation; formatting (colons, base64) is presentation only.- Starting from a private key?
load_pem_private_key(...).public_key()first, then the identical two lines. - Starting from a certificate?
x509.load_pem_x509_certificate(...).public_key()returns the same key object type, and the rest of the recipe is unchanged.
Gotchas
- Hashing the PEM text is the classic mistake — line-ending differences alone (LF vs CRLF) would change that "fingerprint". Only DER is canonical.
cert.fingerprint(hashes.SHA256())in the x509 module hashes the whole certificate, not the key — related value, different purpose, never equal to this one.- OpenSSH's
ssh-keygen -lffingerprints hash the SSH wire encoding and are base64 of raw digest bytes — same key, legitimately different fingerprint string.