import base64import hashlib from cryptography import x509from cryptography.hazmat.primitives import serializationfrom cryptography.hazmat.primitives.serialization import Encoding, PublicFormat with open("cert.pem", "rb") as f: cert = x509.load_pem_x509_certificate(f.read()) # load_der_x509_certificate for DER public_key = cert.public_key() # an EllipticCurvePublicKey, RSAPublicKey, Ed25519PublicKey ...spki_pem = public_key.public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo)spki_der = public_key.public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)print(spki_pem.decode(), end="") digest = hashlib.sha256(spki_der).digest()print("SPKI SHA-256:", digest.hex(":"))print("SPKI pin (base64):", base64.b64encode(digest).decode()) # Does key.pem belong to this certificate?with open("key.pem", "rb") as f: private_key = serialization.load_pem_private_key(f.read(), password=None)print("key.pem matches:", private_key.public_key() == public_key) How it works
cert.public_key()returns a typed key object (EllipticCurvePublicKeyhere) that can verify signatures directly.public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo)gives theBEGIN PUBLIC KEYblock; withEncoding.DERit gives the bytes to hash.- SHA-256 of the SPKI DER is the key fingerprint (
45:ca:a7:e3:…:7b:5f); its base64 (Rcqn43J0…e18=) is the SPKI pin used by Android, OkHttp and TrustKit. private_key.public_key() == cert.public_key()compares keys by value, which tells you whetherkey.pembelongs to the certificate.
Gotchas
PublicFormat.PKCS1exists only for RSA; on an EC key it raisesValueError: format is invalid with this key.SubjectPublicKeyInfoworks for every algorithm.Encoding.X962withPublicFormat.UncompressedPointgives the bare 65-byte point (starting with04), not an SPKI — useful for JOSE or raw protocols, wrong for pinning orBEGIN PUBLIC KEY.- The certificate's own fingerprint (
cert.fingerprint(hashes.SHA256()),cdb1a140…in the run) hashes the whole certificate and changes at every renewal; pin the SPKI hash instead.