Skip to content
ecdsa.com

Key fingerprint · Java

How to compute a public key fingerprint in Java

To compute a public key fingerprint in Java, hash the key's DER-encoded SubjectPublicKeyInfo with SHA-256 — exactly the bytes PublicKey.getEncoded() returns. Computing the fingerprint takes MessageDigest and HexFormat from the JDK — the result matches OpenSSL, Node.js, Python and Go byte for byte.

Tested with OpenJDK 25.0.4.1 (Eclipse Temurin 25.0.4.1+1) and OpenSSL 3.0.13 (openssl dgst cross-check).

Fingerprint.java — SHA-256 over SPKI DER, as hex and as a base64 pin
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 fingerprint of the key inside a certificate
// The key inside a certificate: hash its SPKI, not the whole certificate
try (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

  1. The PEM body is the SPKI DER. Parsing it with KeyFactory first is a guard: a damaged or mislabelled file fails with an exception instead of producing a confident hash of garbage.
  2. 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 or KeyPairGenerator.
  3. HexFormat.ofDelimiter(":") (JDK 17+) prints the colon-separated form OpenSSL's dgst -c shows; Base64.getEncoder() over the raw digest gives the pin-sha256 value used by HTTP public key pinning and Android's network security config.
  4. For a certificate, hash getPublicKey().getEncoded() — the run above checks both results against openssl … -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 is 54:7A:73:…, its key's is 3d: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:… from ssh-keygen -lf) hash the SSH wire encoding of the key, not SPKI, and never match this value for the same key.

Related recipes