Skip to content
ecdsa.com

Generate a CSR · Java

How to generate a CSR for an EC P-256 key with SANs in Java

The JDK can generate the key but has no public API for certificate requests, so Java code generates a CSR with Bouncy Castle's PKIX library: JcaPKCS10CertificationRequestBuilder takes the subject and public key, an extensionRequest attribute carries the SANs, and a JcaContentSignerBuilder signs with SHA256withECDSA. The run decodes the CSR with OpenSSL.

Tested with org.bouncycastle:bcpkix-jdk18on 1.86 (OpenJDK 25.0.4.1 (Temurin), SunEC keys).

Csr.java — P-256 key from the JDK, CSR with Bouncy Castle (bcpkix 1.86)
import java.io.FileWriter;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.spec.ECGenParameterSpec;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.ExtensionsGenerator;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.openssl.jcajce.JcaPKCS8Generator;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
public class Csr {
public static void main(String[] args) throws Exception {
// The JDK generates the key; Bouncy Castle builds the PKCS#10 request (the JDK has no CSR API)
KeyPairGenerator generator = KeyPairGenerator.getInstance("EC");
generator.initialize(new ECGenParameterSpec("secp256r1")); // P-256
KeyPair keys = generator.generateKeyPair();
ExtensionsGenerator extensions = new ExtensionsGenerator();
extensions.addExtension(Extension.subjectAlternativeName, false, new GeneralNames(new GeneralName[] {
new GeneralName(GeneralName.dNSName, "api.example.com"),
new GeneralName(GeneralName.dNSName, "www.example.com"),
}));
PKCS10CertificationRequest csr = new JcaPKCS10CertificationRequestBuilder(
new X500Name("C=US, O=Example Corp, CN=api.example.com"), keys.getPublic())
.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extensions.generate())
.build(new JcaContentSignerBuilder("SHA256withECDSA").build(keys.getPrivate()));
try (JcaPEMWriter out = new JcaPEMWriter(new FileWriter("csr.pem"))) {
out.writeObject(csr);
}
try (JcaPEMWriter out = new JcaPEMWriter(new FileWriter("key.pem"))) {
out.writeObject(new JcaPKCS8Generator(keys.getPrivate(), null)); // PKCS#8, unencrypted
}
}
}

How it works

  1. KeyPairGenerator.getInstance("EC") with ECGenParameterSpec("secp256r1") creates the P-256 key using the JDK's SunEC provider.
  2. ExtensionsGenerator collects the extensions; GeneralName.dNSName entries form the subjectAltName.
  3. addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extensions.generate()) is how extensions travel in a PKCS#10 request.
  4. JcaPEMWriter writes the request as CERTIFICATE REQUEST PEM and, via JcaPKCS8Generator, the key as unencrypted PKCS#8.

Gotchas

  • sun.security.pkcs10 exists inside the JDK but is not exported: using it fails to compile with package sun.security.pkcs10 is not visible (package sun.security.pkcs10 is declared in module java.base, which does not export it). Bouncy Castle is the supported route.
  • Bouncy Castle's X500Name("C=US, O=…, CN=…") keeps the order written, which is the conventional order; reversing it does not invalidate the CSR, but some CAs and tools display or match it differently.
  • Writing the PrivateKey object itself with JcaPEMWriter produces a legacy BEGIN EC PRIVATE KEY block; JcaPKCS8Generator writes the standard BEGIN PRIVATE KEY that the JDK can read back.
  • X500Name, GeneralName and PKCSObjectIdentifiers are in bcprov: compiling against bcpkix alone fails with package org.bouncycastle.asn1.x500 does not exist. Use bcpkix, bcprov and bcutil of the same release together.

Related recipes