require "base64"require "openssl" cert = OpenSSL::X509::Certificate.new(File.binread("cert.pem")) # PEM or DERpublic_key = cert.public_key # an OpenSSL::PKey::EC (or ::RSA, ...) holding only the public half puts public_key.public_to_pem # SubjectPublicKeyInfo, "BEGIN PUBLIC KEY"digest = OpenSSL::Digest::SHA256.digest(public_key.public_to_der)puts "SPKI SHA-256: #{digest.unpack1('H*').scan(/../).join(':')}"puts "SPKI pin (base64): #{[digest].pack('m0')}" # Does key.pem belong to this certificate?private_key = OpenSSL::PKey.read(File.read("key.pem"))puts "key.pem matches: #{cert.check_private_key(private_key)}" How it works
cert.public_keyreturns anOpenSSL::PKey(here an EC key) holding only the public half, ready forverify.public_to_pemwrites the SubjectPublicKeyInfo PEM andpublic_to_derits DER, for every key type.- SHA-256 of the SPKI DER is the key fingerprint (
45:ca:a7:e3:…:7b:5f), and its base64 (Rcqn43J0…e18=) the SPKI pin. cert.check_private_key(key)returnstruefor the certificate's own key andfalsefor any other (a freshly generated P-256 key in the run).
Gotchas
OpenSSL::Digest::SHA256.hexdigest(cert.to_der)is the certificate fingerprint (cdb1a140…), not the key's; it changes at every renewal.- Use
public_to_pem/public_to_derrather thanto_pem/to_der: on a key object that also holds a private part, the latter export the private key. check_private_keycompares key pairs, it does not verify the certificate — trust still needs anOpenSSL::X509::Store.