Introduction
A Certificate Signing Request (CSR) is the data structure that a subject sends to a Certificate Authority to apply for a public-key certificate. The CSR format was defined by RSA Laboratories in the PKCS10 standard, published as version 1.0 in 1993 (RFC 2314) and updated as version 1.7 in 2018 (RFC 5967). A Java learner should care because the CSR is the standard way to obtain a real, publicly trusted TLS certificate from a CA such as Let's Encrypt, DigiCert, or GlobalSign, and because generating CSRs programmatically is the foundation of any automated certificate lifecycle management system such as cert-manager or Spring Boot TLS rotation.
This tutorial walks through a CSR generation java program that uses the sun.security.pkcs10 package shipped with the JDK to build a PKCS10 CSR with a 2048-bit RSA keypair, a subject distinguished name, and the Subject Alternative Name extension, then verifies the CSR signature and prints the PEM output. The program is fully self-contained and produces a CSR you can submit to any commercial CA via their web form.
What is a Certificate Signing Request?
A Certificate Signing Request is an ASN.1 structure that contains: a version, a subject distinguished name, a public key, a list of attributes (typically the Subject Alternative Name extension), and a signature over all of the preceding fields produced by the private key that corresponds to the public key. The signature proves to the CA that the requester actually holds the private key; otherwise anyone could request a certificate on someone else's public key.
The CSR is the input to the CA's certificate-issuance workflow. The CA validates the identity in the CSR (by domain ownership check for DV, by business registration for OV, by extended validation for EV), then constructs a certificate by copying the subject, public key, and SAN from the CSR into a new X.509 certificate and signing it with the CA's private key. The CA returns the issued certificate, which the requester pairs with their private key to install on their TLS server.
CSRs differ from self-signed certificates in that a CSR does not assert the subject's identity; it only requests an assertion. The CSR carries no validity period, no serial number, no issuer. The CA fills in all of those when it issues the certificate. A CSR is also one-shot: after the CA signs it, the CSR can be discarded; the resulting certificate is independent of the CSR.
CSRs are encoded in PEM, just like certificates, but with the BEGIN CERTIFICATE REQUEST and END CERTIFICATE REQUEST markers. The DER inside is ASN.1 with a slightly different structure than a certificate, using the CertificationRequest SEQUENCE instead of the Certificate SEQUENCE.
How CSR Generation Works - Step by Step
Step 1: Generate a keypair. Use KeyPairGenerator.getInstance("RSA") with initialize(2048) to produce a 2048-bit RSA keypair. The private key stays on the requester's machine; the public key goes into the CSR.
Step 2: Build the subject DN. Construct an X500Name from a DN string such as "CN=www.imcseian.com, O=imcseian, C=US". The CN historically matched the hostname, but since 2000 the SAN extension is the authoritative source for hostname matching.
Step 3: Build the attributes. Construct a PKCS10Attributes object (a DER-encoded SET OF Attribute). The most common attribute is ExtensionRequest, which carries the Subject Alternative Name extension and any other extensions the requester wants the CA to include in the issued certificate.
Step 4: Construct the PKCS10 object. Pass the subject, the keypair, and the attributes to the PKCS10 constructor. The constructor builds the CertificationRequestInfo structure, signs it with the private key, and attaches the signature algorithm identifier and the signature bytes.
Step 5: Verify the CSR. Call pkcs10.verify() to recompute the hash of the info and check it against the signature using the public key. This is a sanity check before sending the CSR to a CA.
Step 6: Print the PEM. Call pkcs10.getEncoded() to get the DER bytes, base64-encode them with 64-character line wrapping, and wrap in BEGIN CERTIFICATE REQUEST and END CERTIFICATE REQUEST markers.
Step 7: Submit to a CA. Paste the PEM into the CA's web form. The CA validates identity, issues a certificate, and returns the certificate PEM. Pair the returned certificate with the private key generated in Step 1.
Java Program to Generate a CSR
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.util.Base64;
import sun.security.pkcs10.PKCS10;
import sun.security.pkcs.PKCS9Attributes;
import sun.security.x509.X500Name;
import sun.security.x509.SubjectAlternativeNameExtension;
import sun.security.x509.GeneralName;
import sun.security.x509.GeneralNames;
import sun.security.x509.Extension;
public class CsrGenerationDemo {
public static void main(String[] args) throws Exception {
// 1. Generate an RSA keypair.
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair kp = kpg.generateKeyPair();
System.out.println("Public key algorithm: " + kp.getPublic().getAlgorithm());
// 2. Build the subject distinguished name.
String subjectDn = "CN=www.imcseian.com, O=imcseian, C=US";
X500Name subject = new X500Name(subjectDn);
// 3. Build the Subject Alternative Name extension.
GeneralNames names = new GeneralNames();
names.add(new GeneralName(sun.security.x509.GeneralNameInterface.DNS_NAME,
"www.imcseian.com"));
names.add(new GeneralName(sun.security.x509.GeneralNameInterface.DNS_NAME,
"imcseian.com"));
SubjectAlternativeNameExtension san = new SubjectAlternativeNameExtension(names);
// 4. Wrap the SAN in a PKCS9 attributes object (extensionRequest attribute).
PKCS9Attributes attributes = new PKCS9Attributes();
attributes.setAttribute(PKCS9Attributes.EXTENSION_REQUEST_OID, san);
// 5. Build the PKCS10 CSR and sign it.
PKCS10 csr = new PKCS10(kp.getPublic(), attributes, subject, kp.getPrivate(),
"SHA256withRSA");
// 6. Verify the CSR signature using the embedded public key.
csr.verify();
System.out.println("CSR self-signature verifies: true");
// 7. Print the PEM.
String pem = toPem(csr);
System.out.println("=== Generated CSR ===");
System.out.println(pem);
// 8. Save to disk.
try (Writer w = new OutputStreamWriter(new FileOutputStream("/tmp/imcseian.csr"))) {
w.write(pem);
}
System.out.println("Saved CSR to /tmp/imcseian.csr");
}
static String toPem(PKCS10 csr) throws Exception {
byte[] der = csr.getEncoded();
String b64 = Base64.getMimeEncoder(64, new byte[]{'\n'}).encodeToString(der);
return "-----BEGIN CERTIFICATE REQUEST-----\n" + b64
+ "\n-----END CERTIFICATE REQUEST-----\n";
}
}
Code Explanation - Line by Line
The main method starts by generating a 2048-bit RSA keypair using KeyPairGenerator. The 2048-bit size is the standard for TLS certificates issued today; Let's Encrypt, DigiCert, and GlobalSign all accept it. For higher security or longer-lived certificates, you can use 3072 bits or switch to ECDSA P-256, which is faster and produces smaller signatures.
The subject DN is constructed from a string in RFC 4514 format. The CN field historically matched the hostname, but since Chrome 58 (2017) the SAN is authoritative. We include the hostname in the CN for backward compatibility with older TLS clients, but the SAN carries the full list of hostnames the certificate should be valid for.
The Subject Alternative Name extension is built from a GeneralNames object containing the DNS names the certificate should be valid for. Modern public CAs require the SAN extension and require that every hostname appear in the SAN. We list both www.imcseian.com and the bare apex imcseian.com so the resulting certificate covers both.
The PKCS10Attributes object holds the attributes that the PKCS10 spec allows. The most common is the extensionRequest attribute, which carries extensions the requester wants in the issued certificate. We add only the SAN here; production CSRs also often include KeyUsage, ExtendedKeyUsage, BasicConstraints, and SubjectKeyIdentifier, but the CA typically overrides these based on its own policy.
The PKCS10 constructor takes the public key, the attributes, the subject name, the private key, and the signature algorithm. It internally builds the CertificationRequestInfo structure (subject, public key, attributes), computes the SHA-256 hash, signs it with the private key using PKCS1 v1.5 padding, and stores the signature and algorithm OID in the CSR. The constructor does not throw on signing failure; the verify call is needed to confirm.
The verify method recomputes the hash of the CertificationRequestInfo and checks the signature using the public key embedded in the CSR. If the signature matches, the CSR is well-formed and the requester does hold the private key; if not, an exception is thrown. This is the same verification the CA will perform when it receives the CSR.
The toPem helper serializes the DER bytes to RFC 7468 PEM format with 64-character line wrapping. The PEM markers BEGIN CERTIFICATE REQUEST and END CERTIFICATE REQUEST are the standard for CSRs; some legacy systems used BEGIN NEW CERTIFICATE REQUEST, but the modern standard is the shorter form.
Sample Input and Output
Public key algorithm: RSA CSR self-signature verifies: true === Generated CSR === -----BEGIN CERTIFICATE REQUEST----- MIICVDCCATwCAQAwVzELMAkGA1UEBhMCVVMxETAPBgNVBAoMCGltY3NlaWFuMRgw FgYDVQQDDA93d3cuaW1jc2VpYW4uY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A ... (many base64 lines) ... -----END CERTIFICATE REQUEST----- Saved CSR to /tmp/imcseian.csr
The output shows the PEM-encoded CSR with the standard headers, the subject DN encoded as ASN.1 in the first few lines of base64, and the saved file path. The actual base64 will differ on every run because of the random keypair.
Common Mistakes to Avoid
The most common mistake is omitting the SAN extension. Without SAN, modern browsers reject the certificate even after the CA signs it, because Chrome and Firefox require SAN for hostname matching. Always include the SAN with every hostname the certificate should be valid for.
The second mistake is requesting too many SANs. Public CAs charge per hostname, and Let's Encrypt caps at 100 SANs per certificate. If you need to cover many hostnames, use a wildcard certificate (*.example.com) or split into multiple certificates.
The third mistake is losing the private key after generating the CSR. The CSR is useless without the corresponding private key; if you lose the private key, the CA cannot issue a certificate you can actually use, and you have to start over. Always back up the private key securely (encrypted, in a keystore) before submitting the CSR.
The fourth mistake is generating the CSR with a weak key. RSA keys below 2048 bits are deprecated and rejected by most CAs. ECDSA keys below P-256 are also rejected. Use 2048-bit RSA or P-256 ECDSA at minimum.
Real-World Applications
Every public TLS certificate is preceded by a CSR. The standard workflow is: generate a keypair and CSR locally, submit the CSR via the CA's web form or ACME protocol, the CA validates identity and signs, you receive the certificate back, you pair it with the private key in a keystore, and you install the keystore on your TLS server. This workflow applies to Let's Encrypt, DigiCert, GlobalSign, AWS Certificate Manager, Cloudflare, and every other public CA.
The ACME protocol (RFC 8555), used by Let's Encrypt and ZeroSSL, automates CSR generation, submission, validation, and certificate installation. The acme4j Java library and the cert-manager Kubernetes operator both use the JCA and Bouncy Castle APIs to generate CSRs programmatically. If you are building a Java service that needs to rotate TLS certificates automatically, generating CSRs in code is the foundation.
Spring Boot applications that terminate TLS typically receive the keystore pre-built by an external process (such as cert-manager or the platform's TLS provisioning service). If you build that provisioning service in Java, the CSR generation code in this tutorial is the starting point.
In the broader ecosystem, OpenSSL generates CSRs with openssl req -new -newkey rsa:2048 -nodes -keyout key.pem -out csr.pem -subj "/CN=www.imcseian.com" -addext "subjectAltName=DNS:www.imcseian.com,DNS:imcseian.com". The Bouncy Castle library has a more portable PKCS10CertificateRequest class that does not require sun.security internals; if you are building a CA or ACME client in Java, Bouncy Castle is the safer long-term choice.
Conclusion
A Certificate Signing Request is the standard way to ask a CA to issue a public-key certificate. We built a Java program that generates a 2048-bit RSA keypair, constructs a PKCS10 CSR with a Subject Alternative Name extension, verifies the signature, and writes the PEM output. Next, study the Self-Signed Certificate post to see how to issue a certificate yourself, and the PKCS12 Keystore post to see how to pair the certificate with the private key.
Comments
Comments
Post a Comment