How to Use the Bouncy Castle Kotlin API
The Kotlin API is a Kotlin wrapper that goes on top of the Bouncy Castle and Bouncy Castle FIPS APIs. The Kotlin API provides a scripting language to make it easier for system administrators to work with the Bouncy Castle and Bouncy Castle FIPS APIs for generating keys and certification requests.
The Kotlin programming language offers the possibility to create DSL (domain-specific languages) that allow users who are not necessarily programmers to create a "friendly" syntax, while still being type-safe and bringing a wide range of features. The distribution available from the Bouncy Castle Github repository on https://github.com/bcgit/bc-kotlin.
The following sections provide examples of creating certificates, empty CRLs, and certification requests using the Bouncy Castle Kotlin API.
The following usage examples can be used separately or to complement the Training - PKI at the edge with Bouncy Castle (session 3) which introduces useful concepts and provides additional context.
Generate PKCS10 Certification Request
Creating a certification request using PKCS10 is a common task performed when setting up a server and is a task that often looks for a scripting language. The following PKCS10 example illustrates the style of the Kotlin API and shows that Kotlin is a lot briefer than Java while still type-safe and syntactically checked.
using("BC")
var kp = signingKeyPair {
rsa {
keySize = 2048
}
}
val extensions = extensions {
critical(extension {
extOid = Extension.keyUsage
extValue = KeyUsage(KeyUsage.digitalSignature)
})
subjectKeyIdentifierExtension {
subjectKey = kp.verificationKey
}
}
var pkcs10 = pkcs10Request {
subject = x500Name("CN=Test")
subjectKey = kp.verificationKey
attributes = attributes {
attribute {
attrType = PKCSObjectIdentifiers.pkcs_9_at_extensionRequest
attrValue = extensions
}
}
signature {
PKCS1v1dot5 with sha256 using kp.signingKey
}
}
OutputStreamWriter(System.out).writePEMObject(kp.signingKey)
OutputStreamWriter(System.out).writePEMObject(pkcs10)
Compare this PKCS10 example in Kotlin with the Java code in How to Generate Key Pairs and Certification Requests to see that the use of Kotlin DSL hides a lot of complexity while still containing all the essential elements:
- The
using
call defines the provider to use and makes sure it is loaded. - The
signingKeyPair
assignment generates and saves an RSA key pair inkp.
- The
extensions
block is defined for the certificate request - The PKCS10 request is generated and saved by the
pkcs10Request
assignment. - The private key and the PKCS10 request are both written out in PEM files.
Generate Certificates and CRLs in Kotlin
The following provides examples for creating certificates and empty CRLs using Kotlin.
Generate Self-signed Certificate
Sometimes on an internal network, the requirement is just to create self-signed certificates to be used between machines. The following example shows how to create a self-signed certificate using the Kotlin API:
var expDate = Date(System.currentTimeMillis() + 365L * 24 * 60 * 60 * 1000)
var name = x500Name {
rdn(BCStyle.CN, "Self Signed")
}
var cert = certificate {
serialNumber = BigInteger.valueOf(1)
issuer = name
notAfter = expDate
subject = name
subjectPublicKey = kp.verificationKey
signature {
DSA with sha256 using kp.signingKey
}
}
As with PKCS#10, the certificate generation really just looks like a declaration.
Create Empty CRL
You can also use the Kotlin API for more sophisticated tasks, such as creating and maintaining a basic Certificate Revocation List (CRL). The following Kotlin example shows how to create an empty CRL.
var exts = extensions {
authorityKeyIdentifierExtension {
authorityKey = cert
}
}
// empty CRL.
var crl = crl {
issuer = cert
revocation {
userCert = BigInteger.ONE
reason = certificateHold
}
revocation {
userCert = cert
reason = keyCompromise
}
extensions = exts
signature {
DSA with sha256 using kp.signingKey
}
}
The script starts by defining an extensions block for the CRL and the structure is the same as for the previous PKCS10 example, and is also the same if defining one for use in certificates.
Update CRL
Maintenance of a CRL requires the ability to update it. The following shows an example of updating a CRL in Kotlin:
// Example of updating
var crl2 = crl updateWith {
revocation {
userCert = BigInteger.valueOf(2)
reason = keyCompromise
}
signature {
DSA with sha256 using kp.signingKey
}
}
Note that using the infix function updateWith in the Kotlin script distills the update script to the essentials, namely to add a new revocation and resign the result.
Private Key Encryption in Kotlin
Encrypting a private key for storage is a common use case that requires the knowledge of a password to be able to make use of the key.
Encryption of Private Key using PBKDF2 Algorithm
The following example shows the encryption of a private key using a standard PBE algorithm, PBKDF2, which was originally defined in PKCS5, or RFC 8018 as it is now known. The encryption algorithm being applied is the standard AES key wrap with padding defined in NIST SP 800-38F.
var pbkdf2Enc = encryptedPrivateKey {
privateKey = kp.signingKey
encryption {
AESKWP using PBKDF2 {
saltLength = 20
keySize = 256
} with "Test".toCharArray()
}
}
OutputStreamWriter(System.out).writePEMObject(pbkdf2Enc)
In the example, the salt is 20 bytes and the keysize is 256 bits, hence the AES key wrap will be done with a 256 bit key. The iteration count (iterationCount
if added to the script) is not specified so the default value of 16384 will be used.
The security PBKDF2 partly relies on the amount of iterations required in the use of the hash algorithm being used as the PRF (pseudo random function) in the PBE scheme - this is the value represented by the iteration count. Unfortunately, in these days of bitcoin mining and GPUs customized to perform high-speed hashing calculations, it is now a lot easier to attack these schemes as the amount of computation that can be done on a GPU makes for an iteration count so high that it would bring a regular server to its knees.
Encryption of Private Key using scrypt Algorithm
The scrypt algorithm, defined in RFC 7914, is an augmentation to PBKDF2 which makes use of PBKDF2 internally for initial processing of the password and then adds a layer of "memory hardness" to the calculation in addition to the salt and workload values (in scrypt represented by two the costParameter
and the parallelization
value. For more information, refer to RFC 7914. In a way, scrypt is defined to make the most of all the properties of a general purpose computing device, leaving a special purpose computing device at a disadvantage.
Relying on defaults for the workload values as in the PBKDF2 example, the encryption of a private key using scrypt for the wrapping key definition looks as follows:
var scryptEnc = encryptedPrivateKey {
privateKey = kp.signingKey
encryption {
AESKWP using SCRYPT {
saltLength = 20
keySize = 256
} with "Test".toCharArray()
}
}
OutputStreamWriter(System.out).writePEMObject(scryptEnc)
You will notice that both of these burn some CPU when they are running, but if you monitor the memory usage you will also see that scrypt uses a lot more memory. This is what makes it "memory hard" - for now at least special purpose devices, while they may allow for processing a lot more hashes than a regular computer, do not have the ability to address as much memory.
Private Key Encryption in Java
The following outlines the same tasks, encryption of a private key using the PBKDF2 and scrypt algorithms, in Java.
Encryption of Private Key using PBKDF2 Algorithm
The following shows and example of encryption and creation of a PEM object, written to a passed in Writer using PBKDF2:
public static void pbkdf2EncryptedPrivateKey(PrivateKey privKey, char[] password, Writer pemOut)
throws OperatorCreationException, IOException
{
PKCS8EncryptedPrivateKeyInfoBuilder bldr = new PKCS8EncryptedPrivateKeyInfoBuilder(privKey.getEncoded());
PKCS8EncryptedPrivateKeyInfo encInfo = bldr.build(
new JcePKCSPBEOutputEncryptorBuilder(NISTObjectIdentifiers.id_aes256_GCM)
.setPRF(new AlgorithmIdentifier(NISTObjectIdentifiers.id_hmacWithSHA3_256, DERNull.INSTANCE))
.setProvider("BC")
.build(password));
JcaPEMWriter pemWriter = new JcaPEMWriter(pemOut);
pemWriter.writeObject(encInfo);
pemWriter.close();
}
This example is slightly different from the Kotlin one in that it is using AES-256 with GCM for the encryption and SHA3-256 as the PRF used for generating the AES key. If you want to use AES KWP, you need to pass NISTObjectIdentifiers.id_aes256_wrap_pad instead, that value being the ASN.1 OBJECT IDENTIFIER, or OID, associated with AES KWP.
Encryption of Private Key using scrypt Algorithm
The scrypt example in Java makes use of PBKDFConfig object, which was introduced for situations where a simple OID is not enough to provide all the algorithm details.
public static void scryptEncryptedPrivateKey(PrivateKey privKey, char[] password, Writer pemOut)
throws OperatorCreationException, IOException
{
PKCS8EncryptedPrivateKeyInfoBuilder bldr = new PKCS8EncryptedPrivateKeyInfoBuilder(privKey.getEncoded());
PBKDFConfig scrypt = new ScryptConfig.Builder(1048576, 8, 1)
.withSaltLength(20).build();
PKCS8EncryptedPrivateKeyInfo encInfo = bldr.build(
new JcePKCSPBEOutputEncryptorBuilder(scrypt, NISTObjectIdentifiers.id_aes256_GCM)
.setProvider("BC")
.build(password));
JcaPEMWriter pemWriter = new JcaPEMWriter(pemOut);
pemWriter.writeObject(encInfo);
pemWriter.close();
}
The PBKDFConfig can also be used for PBKDF2.
While the two Java functions are a bit more verbose, the declarative style used in the Kotlin script maps to what is happening in the Java method, and the elements are all there. The Java code is probably more flexible in how it can be used. For example, the possible algorithms that can be used are only restricted to those defined in available OIDs. The Kotlin API on the other hand has the algorithms restricted to a set of pre-defined variables of a particular type. As much as the flexibility is useful in Java, the extra control provided by the Kotlin DSL provides an additional safeguard to ensure users only choose from a limited set of possibilities.
KeyStores in Java
The best way to store a public/private key pair is as a private key and a certificate in a KeyStore. There are now a number of KeyStore formats, some of them quite old like JKS and PKCS12, others are more recent.
The BouncyCastle APIs support three types of KeyStore formats:
- JKS: The original Java KeyStore format, a certificate only version.
- PKCS12: RSA’s Personal Information Exchange Protocol Data Unit(PFX PDU), now defined in RFC 7292.
- BCFKS: FIPS compliant KeyStore which can be used with either PBKDF2 or SCRYPT
There is also a FIPS KeyStore type that reads both BCFKS and JKS.
Create BCFKS KeyStore Example
The following example looks at the BCFKS format.
public static KeyStore createBCFKS(KeyPair keyPair, String signatureAlg)
throws Exception
{
X509Certificate cert = CertificateExamples.createTrustAnchor(keyPair, signatureAlg);
KeyStore store = KeyStore.getInstance("BCFKS", "BC");
store.load(null, null);
store.setKeyEntry("key", keyPair.getPrivate(), "keyPass".toCharArray(),
new Certificate[] { cert });
return store;
}
Create JKS KeyStore Example
This next example looks at the JKS format. The JKS format is now getting phased out, but is still often used for certificates. Its ability to adequately protect private keys is now regarded as poor, but it has a checksum associated with it. Certificates are stored unencrypted - when just storing certificates, from a security point of view, you can think of it as a PEM file with a checksum attached.
The following method creates a JKS KeyStore with just a certificate stored in it:
public static KeyStore createJKS(X509Certificate certificate)
throws Exception
{
KeyStore store = KeyStore.getInstance("JKS");
store.load(null, null);
store.setCertificateEntry("cert", certificate);
return store;
}
Note that the Java KeyTool reads certificates from one of these files without the need to provide the password. Sometimes this can be useful, sometimes not.
FIPS KeyStore Type Example
One issue that developers run into, especially in the context of using FIPS, is that any private key storage has to make use of algorithms based on PBKDF2.
This rules out file formats like JKS and PKCS12 for private keys, but on the other hand, it does not rule them out for certificate-only stores. Common examples of these include the cacerts
file that ships with the JVM. To deal with this, Bouncy Castle added a "FIPS" store type, which will read both the BCFKS format and the JKS format, but only where the JKS file does not contain anything but certificates.
This last example of the FIPS KeyStore type and the previous create methods to show how the FIPS type works.
/**
* Save a KeyStore to a file.
*
* @param fileName the name of the file to use.
* @param store the KeyStore to store.
*/
private static void saveStore(String fileName, KeyStore store)
throws Exception
{
FileOutputStream fOut = new FileOutputStream(fileName);
store.store(fOut, "storePass".toCharArray());
fOut.close();
}
public static void main(String[] args)
throws Exception
{
Security.addProvider(new BouncyCastleProvider());
KeyPair keyPair = KeyPairGeneratorExamples.generateECKeyPair();
KeyStore store1 = createBCFKS(keyPair, "SHA256withECDSA");
saveStore("basic.bcfks", store1);
KeyStore store2 = createJKS((X509Certificate)store1.getCertificate("key"));
saveStore("basic.jks", store2);
KeyStore mixedStore1 = KeyStore.getInstance("FIPS");
mixedStore1.load(new FileInputStream("basic.bcfks"), "storePass".toCharArray());
KeyStore mixedStore2 = KeyStore.getInstance("FIPS");
mixedStore2.load(new FileInputStream("basic.jks"), "storePass".toCharArray());
System.out.println(mixedStore1.getCertificate("key").equals(mixedStore2.getCertificate("cert")));
}
Run this with the earlier methods and you should see true
appear as the output. Also worth noting in the example is that the KeyStore password is set only on a call to the store()
method. If you are writing code for editing key stores, you want to keep that in mind.
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.
Bouncy Castle Crypto Package for Kotlin
The Bouncy Castle Crypto Package For Kotlin on GitHub is a set of Kotlin classes designed to go on top of the Bouncy Castle Crypto Java APIs. The classes can be run with either the general BC APIs or the BC-FJA FIPS version.