Skip to content
ecdsa.com

Decode a certificate · Java

How to decode an X.509 certificate in Java

To decode an X.509 certificate in Java, use CertificateFactory from the JDK: one call turns a PEM or DER file into an X509Certificate with typed getters for subject, issuer, validity, extensions and the public key. This recipe prints the fields people usually need, computes the SHA-256 fingerprint and walks a certificate chain.

Tested with OpenJDK 25.0.4.1 (Eclipse Temurin 25.0.4.1+1) and OpenSSL 3.0.13 (test chain and DER copy).

DecodeCert.java — the core fields, the key's curve and the fingerprint
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.AlgorithmParameters;
import java.security.MessageDigest;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.time.Duration;
import java.time.Instant;
import java.util.HexFormat;
import java.util.List;
public class DecodeCert {
public static void main(String[] args) throws Exception {
X509Certificate cert;
try (InputStream in = Files.newInputStream(Path.of("cert.pem"))) { // PEM or DER
cert = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(in);
}
System.out.println("subject: " + cert.getSubjectX500Principal().getName());
System.out.println("issuer: " + cert.getIssuerX500Principal().getName());
System.out.println("serial: " + cert.getSerialNumber().toString(16));
System.out.println("valid: " + cert.getNotBefore().toInstant() + " -> " + cert.getNotAfter().toInstant());
System.out.println("signed with: " + cert.getSigAlgName()); // the issuer's algorithm
if (cert.getSubjectAlternativeNames() != null) {
for (List<?> name : cert.getSubjectAlternativeNames()) {
if ((Integer) name.get(0) == 2) System.out.println("DNS name: " + name.get(1)); // 2 = dNSName
}
}
if (cert.getPublicKey() instanceof ECPublicKey ecKey) {
AlgorithmParameters params = AlgorithmParameters.getInstance("EC");
params.init(ecKey.getParams());
System.out.println("key: EC " + params.getParameterSpec(ECGenParameterSpec.class).getName());
}
byte[] sha256 = MessageDigest.getInstance("SHA-256").digest(cert.getEncoded()); // the whole DER certificate
System.out.println("SHA-256 fingerprint: " + HexFormat.ofDelimiter(":").withUpperCase().formatHex(sha256));
System.out.println("days until expiry: " + Duration.between(Instant.now(), cert.getNotAfter().toInstant()).toDays());
}
}
a PEM bundle: every certificate and who signed it
// A bundle, leaf first: parse every block and check who signed whom
List<X509Certificate> chain;
try (InputStream in = Files.newInputStream(Path.of("chain.pem"))) {
chain = CertificateFactory.getInstance("X.509").generateCertificates(in).stream()
.map(X509Certificate.class::cast)
.toList();
}
for (int i = 0; i < chain.size(); i++) {
X509Certificate cert = chain.get(i);
X509Certificate issuer = chain.get(Math.min(i + 1, chain.size() - 1)); // the root signs itself
cert.verify(issuer.getPublicKey()); // SignatureException unless this issuer signed it
System.out.println(i + ": " + cert.getSubjectX500Principal().getName()
+ " signed by " + issuer.getSubjectX500Principal().getName());
}

How it works

  1. CertificateFactory.getInstance("X.509").generateCertificate reads PEM and binary DER alike — the run above feeds it both forms of the same certificate.
  2. getSubjectX500Principal().getName() returns the RFC 2253 string; validity dates come back as java.util.Date, and toInstant() gives exact UTC timestamps for comparisons.
  3. getSubjectAlternativeNames() returns a list of [type, value] pairs per name; type 2 is a DNS name and type 7 an IP address. It returns null, not an empty list, when the extension is absent.
  4. The curve hides in the key parameters: AlgorithmParameters converts the key's ECParameterSpec back to the standard name, secp256r1 for P-256.
  5. generateCertificates (plural) parses every block of a bundle, and cert.verify(issuerKey) throws unless that key produced the certificate's signature.

Gotchas

  • Java prints distinguished names in RFC 2253 order, the reverse of OpenSSL: this certificate is C=US,O=…,CN=demo.ecdsa.com here and CN=demo.ecdsa.com, O=…, C=US in openssl x509 -subject. Compare X500Principal objects, not strings.
  • getSerialNumber().toString(16) drops leading zeros (4a1b2c3d4e5f607, where OpenSSL prints 04A1B2C3D4E5F607). Normalize the format before matching serials from different tools.
  • getSigAlgName() names the issuer's signature over this certificate, not what the certificate's own key can do — an ECDSA leaf issued by an RSA CA reports SHA256withRSA.
  • Decoding is not validation: the chain loop proves who signed whom, nothing more. Trust needs CertPathValidator with your trust anchors, and checkValidity() throws CertificateExpiredException once notAfter has passed.

Related recipes