import java.nio.file.Files;import java.nio.file.Path;import java.security.KeyFactory;import java.security.MessageDigest;import java.security.PublicKey;import java.security.spec.X509EncodedKeySpec;import java.util.Base64;import java.util.HexFormat; public class Fingerprint { public static void main(String[] args) throws Exception { String pem = Files.readString(Path.of("pub.pem")); byte[] spki = Base64.getMimeDecoder().decode(pem.replaceAll("-----[^-]+-----", "")); // parse first: fingerprinting bytes that are not a key should fail loudly PublicKey key = KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(spki)); byte[] digest = MessageDigest.getInstance("SHA-256").digest(key.getEncoded()); // SPKI DER System.out.println(HexFormat.ofDelimiter(":").formatHex(digest)); System.out.println(Base64.getEncoder().encodeToString(digest)); // pin-sha256 form (RFC 7469) }} // The key inside a certificate: hash its SPKI, not the whole certificatetry (InputStream in = Files.newInputStream(Path.of("cert.pem"))) { PublicKey certKey = CertificateFactory.getInstance("X.509").generateCertificate(in).getPublicKey(); byte[] keyDigest = MessageDigest.getInstance("SHA-256").digest(certKey.getEncoded()); System.out.println("certificate key: " + HexFormat.ofDelimiter(":").formatHex(keyDigest));} How it works
- The PEM body is the SPKI DER. Parsing it with
KeyFactoryfirst is a guard: a damaged or mislabelled file fails with an exception instead of producing a confident hash of garbage. key.getEncoded()re-emits the canonical SPKI DER, so the digest is the same whether the key came from a PEM file, a certificate, a keystore orKeyPairGenerator.HexFormat.ofDelimiter(":")(JDK 17+) prints the colon-separated form OpenSSL'sdgst -cshows;Base64.getEncoder()over the raw digest gives thepin-sha256value used by HTTP public key pinning and Android's network security config.- For a certificate, hash
getPublicKey().getEncoded()— the run above checks both results againstopenssl … -outform DER | openssl dgst -sha256 -c.
Gotchas
- Hashing the PEM text instead of the DER gives a value no other tool reproduces — line endings and line width alone change it.
- The certificate fingerprint (SHA-256 of
cert.getEncoded()) and the key fingerprint are different values: the example certificate's is54:7A:73:…, its key's is3d:bb:28:…. The key fingerprint survives renewal with the same key; the certificate fingerprint does not. - An SPKI with explicit curve parameters instead of the curve OID (what Bouncy Castle writes when given an unnamed curve spec) hashes differently from the same key in named form. Normalize keys through a named-curve encoding before comparing fingerprints.
- OpenSSH fingerprints (
SHA256:…fromssh-keygen -lf) hash the SSH wire encoding of the key, not SPKI, and never match this value for the same key.