How to Generate Key Pairs and Certification Requests
The following provides information and examples for generating key pairs and Certification Request using PKCS#10 and Certificate Request Message Format (CRMF) using the Bouncy Castle APIs.
The following examples can be used separately or to complement the Training - PKI at the Edge (session 1) which introduces useful concepts and provides additional context.
Generate Key Pairs
The starting point of any certificate or certification request is the Key Pair. Creating a key pair using the JCA (Java Cryptography Architecture) is straightforward and done using the KeyPairGenerator class. What is not as straightforward is what algorithm and what parameters to use and the following outlines two of the more common ones: RSA and EC. Also, keep in mind that the relative security of public key algorithms varies relative to symmetric ones: a 3072 bit RSA key is about as secure as a 128 bit AES key, likewise for a 256 bit EC key.
Generate RSA Key Pair
The following method generates a 2048 bit RSA key.
The RSAKeyGenParameterSpec
takes the size of the key required and the public exponent to use. In this case F4,
so-called as it is number 4 (starting from 0) of the Fermat Primes. Its actual value is 0x10001.
public static KeyPair generateRSAKeyPair()
throws GeneralSecurityException
{
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
kpGen.initialize(new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4));
return kpGen.generateKeyPair();
}
Depending on what you are trying to do, you will usually find the public exponent needs to be at least F4 or greater.
The RSAKeyGenParameterSpec
also provides F0
(0x3) as an option. Use of F0 is strongly discouraged now, although it can be required for backward compatibility reasons.
In terms of security strength, our 2048 bit key is worth 112 bits of security, which is the minimum you should be using these days.
Generate EC Key Pair
EC key pairs take their key size from the curve parameter used. P-256, for example, will produce keys with an effective size of 256 bits in the EC domain.
The following code generates a key pair over the P-256 curve.
public static KeyPair generateECKeyPair()
throws GeneralSecurityException
{
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("EC", "BC");
kpGen.initialize(new ECGenParameterSpec("P-256"));
return kpGen.generateKeyPair();
}
EC is a useful algorithm for both key-agreement and signing. The attraction is about the relatively short key sizes. A P-521 curve based key, which is rated at more than 256 bits of security, is quite a bit smaller than the equivalent RSA key that would need a modulus of at least 15360 bits.
Other Algorithms
Other relevant algorithms are DSA, and Diffie-Hellman (DH). Diffie-Hellman is useful for both key-agreement and Elgamal encryption. In Java, you can pass in your own parameters using DSAParameterSpec and DHParameterSpec.
Bouncy Castle also provides an extension class of DHParameterSpec called DHDomainParameterSpec. This class is particularly useful for Elgamal as it allows you to include a value for the q parameter in the DH parameters. The q parameter is required if you wish to be able to validate the public key at a later date and Bouncy Castle will include the q parameter value in the key’s encoding if it is available.
Also, note that there are good quality domain parameters available for both DSA and Diffie-Hellman and thus there is no need to generate your own. A common performance issue with DSA and Diffie-Hellman keys are due to users generating their own parameters when there is no need to; parameter generation for both these algorithms is quite expensive.
Certification Requests
While it might be tempting to issue public key certificates by also doing the generation of the private keys locally, unless there is a particular need for escrow, it is a lot safer to get the entities being certified to keep their private keys to themselves. Certification requests are designed to make this possible. The earliest standard form is the one originally published for RSA, PKCS#10, so-called because it appeared in the standard “Public Key Cryptography Standard 10”. A more recent, and versatile, standard is CRMF (Certificate Request Message Format), described in RFC 4211.
The following will only provide examples of the request messages since what comes back from a server once a request has been sent in, is up to the server. Often it is a CMS SignedData message containing one or more certificates,. Although in the case of CRMF, it can also be a CMS EnvelopedData message containing an encrypted CMS SigneData message containing one or more certificates. This guide covers the clients, for more information about the server-side, see How to Generate Certificates and CRLs.
Both the PKCS10 and CRMF message classes support a getEncoded() method that returns an ASN.1 encoded version of the class. You can also pass the ASN.1 encoded versions to the byte[] constructors for both classes.
Generate PKCS10 Certification Request
The PKCS10 format was originally designed as a standard with the "peculiarities" of RSA in mind: an RSA key pair can be used for both for signing and encryption, with signing just being the encryption operation using a private key. This dual use (for signing and encryption) is not actually a good idea since it can destroy the security of the private key.
Many standards, such as FIPS, now explicitly disallow dual use in general use, with the one exception being for the creation of certification requests using PKCS10. The reason for this is that PKCS10 is designed with the idea that the primary mechanism for proof of ownership is signature verification. Creating a PKCS10 request basically involves specifying the identity you want the final certificate to be associated with and then signing the structure containing that information. The idea being that if the enclosed public key can verify the signature when the certificate authority (CA) looks at the request later the structure must have been signed using the corresponding private key. The PKCS10 structure allows for some optional data as well, such as certificate extensions. The following covers the most basic case.
The following code example generates a basic PKCS10 request.
public static PKCS10CertificationRequest createPKCS10(
KeyPair keyPair, String sigAlg)
throws OperatorCreationException
{
X500Name subject = new X500Name("CN=Example");
PKCS10CertificationRequestBuilder requestBuilder
= new JcaPKCS10CertificationRequestBuilder(
subject, keyPair.getPublic());
ContentSigner signer = new JcaContentSignerBuilder(sigAlg)
.setProvider("BC").build(keyPair.getPrivate());
return requestBuilder.build(signer);
}
You can use the example code shown earlier to create the key pair. Either will work as both RSA and EC support signing. We recommend trying the algorithms SHA256withRSA for RSA and SHA256withECDSA.
Note that after specifying the name, a ContentSigner is created using the private key provided in the key pair passed in, and the ContentSigner is then used to build the request holding the name.
Optional data is added to PKCS10 certification requests by including attributes in the request body. The following example code shows how to add an email address to the certification request by adding a request to include the SubjectAlternativeName extension in the certificate the CA will issue.
public static PKCS10CertificationRequest createPKCS10WithExtensions(
KeyPair keyPair, String sigAlg)
throws OperatorCreationException, IOException
{
X500Name subject = new X500Name("CN=Example");
PKCS10CertificationRequestBuilder requestBuilder
= new JcaPKCS10CertificationRequestBuilder(
subject, keyPair.getPublic());
ExtensionsGenerator extGen = new ExtensionsGenerator();
extGen.addExtension(Extension.subjectAlternativeName, false,
new GeneralNames(
new GeneralName(
GeneralName.rfc822Name,
"example@primekey.com")));
Extensions extensions = extGen.generate();
requestBuilder.addAttribute(
PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extensions);
ContentSigner signer = new JcaContentSignerBuilder(sigAlg)
.setProvider("BC").build(keyPair.getPrivate());
return requestBuilder.build(signer);
}
This is the “correct” way to include an email address, although you will see them included in the X.500 name that forms the subject. Note that the only difference between the first example and this one is the addition of the code generating the extension and the code to add ExtensionRequest attribute.
Generate CRMF Certification Request
CRMF, (described in RFC 4211), addresses the problem of proof of ownership, or in the language of the RFC, proof of possession (POP), slightly differently from PKCS10. It allows for signing, but it also allows for encryption and key agreement.
This first example is probably the simplest equivalent to the PKCS10 example. The example assumes an algorithm that can create and verify a signature.
public static byte[] generateRequestWithPOPSig(
BigInteger certReqID, KeyPair kp, String sigAlg)
throws CRMFException, IOException, OperatorCreationException
{
X500Name subject = new X500Name("CN=Example");
JcaCertificateRequestMessageBuilder certReqBuild
= new JcaCertificateRequestMessageBuilder(certReqID);
certReqBuild
.setPublicKey(kp.getPublic())
.setSubject(subject)
.setProofOfPossessionSigningKeySigner(
new JcaContentSignerBuilder(sigAlg)
.setProvider("BC")
.build(kp.getPrivate()));
return certReqBuild.build().getEncoded();
}
The certReqId just needs to be a unique integer so the sender can match the response that comes back to the request sent.
In some ways, in terms of functional content, the only real difference between the above example is the presence of the certificate request ID, which is simply an integer value specified by the certificate requester and echoed back in the certificate response to allow for certificate requestors that may request multiple certificates. Other than that, you can use the same algorithms suggested before with the key types required.
The following example shows how to create a CRMF certification request using encryption for the POP.
/**
* Basic example for generating a CRMF certificate request with POP for
* an encryption only algorithm like ElGamal.
*
* @param certReqID identity (for the client) of this certificate request.
* @param kp key pair whose public key we are making the request for.
*/
public static byte[] generateRequestWithPOPEnc(
BigInteger certReqID, KeyPair kp)
throws CRMFException, IOException
{
X500Name subject = new X500Name("CN=Example");
JcaCertificateRequestMessageBuilder certReqBuild
= new JcaCertificateRequestMessageBuilder(certReqID);
certReqBuild
.setPublicKey(kp.getPublic())
.setSubject(subject)
.setProofOfPossessionSubsequentMessage(SubsequentMessage.encrCert);
return certReqBuild.build().getEncoded();
}
For this one, you need to make use of RSA. The proof of possession step is actually in the CAs response. In this case, the CA will send back an encrypted certificate and if the requestor wants to be able to use it, they need to decrypt it first.
In the case of encrypted POP, the certificate will be returned wrapped in an EnvelopedData structure, with the private key being the one required to support the RecipientInfo structure containing the symmetric key used to encrypt the message containing the certificate that was requested.
Generate CRMF Certification Request using CMP
CRMF messages need to be wrapped in another protocol such as the Certificate Management Protocol (CMP). CMP messages also include an additional field for the sender of the message, sometimes not the same entity as the entity the certificate’s subject field represents. In this case, the CRMF message might be sent as “RA verified” meaning the sender will vouch for the authenticity of the request and the CMP message might be validated using a shared secret instead.
The following method shows a basic certification request message set up for “RA verified”:
public static byte[] generateRequestWithPOPRA(
BigInteger certReqID, KeyPair kp)
throws CRMFException, IOException, OperatorCreationException
{
X500Name subject = new X500Name("CN=Example");
JcaCertificateRequestMessageBuilder certReqBuild
= new JcaCertificateRequestMessageBuilder(certReqID);
certReqBuild
.setPublicKey(kp.getPublic())
.setSubject(subject)
.setProofOfPossessionRaVerified();
return certReqBuild.build().getEncoded();
}
Note that other than the different POP method used, it is identical to the previous examples.
Next, before sending the message to a CA, you need to wrap the message so that you have something suitable for the CMP service you are using.
The following code will wrap the CRMF message you get back and, given a sender and recipient, produce a ProtectedPKIMessage:
public static ProtectedPKIMessage generatMacProtectedMessage(
X500Name sender, X500Name recipient,
byte[] senderNonce,
CertificateRequestMessage crmfMessage,
char[] password)
throws CRMFException, CMPException
{
return new ProtectedPKIMessageBuilder(new GeneralName(sender), new GeneralName(recipient))
.setMessageTime(new Date())
.setSenderNonce(senderNonce)
.setBody(new PKIBody(PKIBody.TYPE_INIT_REQ, new CertReqMessages(crmfMessage.toASN1Structure())))
.build(new PKMACBuilder(new JcePKMACValuesCalculator().setProvider("BC")).build(password));
}
For a specific example using EJBCA, see Certificate and CRL Generation. The following code fragment shows how the different messages can be put together and what is required to build them:
SecureRandom random = new SecureRandom();
BigInteger certReqId = BigInteger.valueOf(System.currentTimeMillis());
KeyPair keyPair = KeyPairGeneratorExamples.generateECKeyPair();
byte[] senderNonce = new byte[16];
random.nextBytes(senderNonce);
CertificateRequestMessage certReqMsg = new CertificateRequestMessage(
CRMFExamples.generateRequestWithPOPRA(certReqId, keyPair));
X500Name sender = new X500Name("CN=Cert Requester");
X500Name recipient = new X500Name("CN=Certificate Issuer");
ProtectedPKIMessage pkiMessage = generatMacProtectedMessage(sender, recipient, senderNonce, certReqMsg, "secret".toCharArray());
This shows that the complexity of dealing with these protocols is more about knowing what the parameter values are than the actual API calls.
Note that the code example uses a different sender to the subject used in the CRMF request that you want to create the certificate for. As you are expecting the CA to issue the certificate based on your word, the request is validated using a shared secret, which can be used to calculate a MAC. This makes sense where a client application might be issuing certificates for multiple entities. Where the client application is not also generating the private keys, one of the other POP modes that confirm possession of the private key by the entity ultimately using the certificate should be used.
ProtectedPKIMessages can also be signed if required.
Related Content
Next, try out the exercises and challenge yourself by trying to solve some tasks intended to strengthen your knowledge by allowing you to test and solve tasks on your own.
Useful Debugging Tools
Bouncy Castle contains an ASN1Dump
class that can be used for dumping out ASN.1
object trees. If you have an encoded one, call ASN1Primitive.fromByteArray()
first).
Another useful tool is OpenSSL and the OpenSSL command asn1parse
can be used to examine the structures. For more information, refer to OpenSSL documentation on asn1parse.