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)

CODE

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 in kp.
  • 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
        }
    }
CODE

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
        }
    }
CODE

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
        }
    }
CODE

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)
CODE

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)
CODE

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();
    }

CODE

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();
    }
CODE

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;
    }
CODE

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;
    }

CODE

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")));
    }
JAVA

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.