Skip to main content
Skip table of contents

Use the Post-Quantum KEMs in non-FIPS Bouncy Castle

Try out the following exercises to learn more about applying the use of Key Encapsulation Mechanisms (KEMs) with classical algorithms using the non-FIPS version of Bouncy Castle. Even if your main interest is FIPS, or other certified environments, doing this in the non-FIPS version of Bouncy Castle is useful.

In the next section Use the Post-Quantum KEMs in FIPS-certified Bouncy Castle, you will find things become very abstract and the exercises in this section allow you to work out how things map to what is covered in the previous section Introduction to Key Encapsulation Mechanisms (KEMs).

The following exercises complement the workshop Post-quantum hybrid cryptography in Bouncy Castle that explores several standardized mechanisms for hybrid techniques and how they can be applied to the Bouncy Castle Java APIs.

Introduction

The KEM algorithms are currently in the BCPQC provider and you need to have that installed as well, see Bouncy Castle latest Java releases.

At the JCA level, there are two possible approaches for Post-Quantum Hardening a Key Agreement and at least one for dealing with Key Transport.

For Key Agreement, with older systems, you can mix in a Post-Quantum KEM generated secret using the user keying material. In Bouncy Castle this involves using the UserKeyingMaterialSpec (org.bouncycastle.jcajce.spec) and can also use the DEROtherInfo class (org.bouncycastle.crypto.util) where a more formal way of creating the user keying material is desired. For the DEROtherInfo approach, the KEM secret should be provided using the withSuppPrivInfo() method.

For newer systems supporting NIST's SP 800-56C, you can also use the HybridValueParameterSpec (also found in org.bouncycastle.jcajce.spec). In most cases, this is similar to adding via the UserKeyingMaterialSpec but it does offer a security advantage for a Two-Step KDF due to the initial hashing of the KDF key.

For Key Transport there is the trusty XOR function. Just blend what you get out of the key transport mechanism with the secret from the Post-Quantum KEM.

Exercises

Try out the exercises in the following sections to learn more about applying the use of Key Encapsulation Mechanisms (KEMs) with classical algorithms using the non-FIPS version of Bouncy Castle.

Exercise 1

Using the KeyAgreement class, implement an EC KeyAgreement using the algorithm “ECCDHWITHSHA256KDF”. Once that is working, using the KeyGenerator approach, implement a KEM-based key exchange and introduce the shared secret using a UserKeyingMaterialSpec. Finally, once that is working, add the use of DEROtherInfo to create input bytes for the UserKeyingMaterialSpec.

View solution to exercise 1
JAVA
public class Exercise1
{
    private static void doExercise(boolean isPart1)
        throws Exception
    {
        // You will request a key pair generator for EC and generate a key pair
        // for each recipient - this is the same as before, you're just using the JCA.
        KeyPairGenerator ecKpg = KeyPairGenerator.getInstance("EC", "BC");
        ecKpg.initialize(new ECGenParameterSpec("P-256"));

        // initiator
        KeyPair initKp = ecKpg.generateKeyPair();

        // receiver
        KeyPair recKp = ecKpg.generateKeyPair();

        // you'll also generate a Frodo key pair for the receiver.
        KeyPairGenerator frodoKpg = KeyPairGenerator.getInstance("Frodo", "BCPQC");
        frodoKpg.initialize(FrodoParameterSpec.frodokem19888r3, new SecureRandom());

        // Generating a key pair for receiver
        KeyPair recFrodoKP = frodoKpg.generateKeyPair();

        // The public key from this key pair
        PublicKey publicKey = recFrodoKP.getPublic();

        // You will request a key generator for frodo
        KeyGenerator keyGenerator = KeyGenerator.getInstance("Frodo", "BCPQC");

        // You will set up a KEM Generate Spec with the public key
        KEMGenerateSpec kemGenerateSpec = new KEMGenerateSpec(publicKey, "Secret");

        // Now you can initialize the key generator with the kem generate spec and generate out share secret
        keyGenerator.init(kemGenerateSpec);

        SecretKeyWithEncapsulation initKeyWithEnc = (SecretKeyWithEncapsulation)keyGenerator.generateKey();
        byte[] encapsulation = initKeyWithEnc.getEncapsulation();

        // You will request a key agreement for ECCDHWITHSHA256KDF
        KeyAgreement initAgreement = KeyAgreement.getInstance("ECCDHWITHSHA256KDF", "BC");

        UserKeyingMaterialSpec initSpec;

        if (isPart1)
        {
            // you use the raw method for part 1 of the exercise
            initSpec = new UserKeyingMaterialSpec(initKeyWithEnc.getEncoded());
        }
        else
        {
            // a slightly more sophisticated method which allows for more regular user keying material as asked for in part 2 of the exercise
            DEROtherInfo.Builder bldr = new DEROtherInfo.Builder(new AlgorithmIdentifier(NISTObjectIdentifiers.id_aes128_GCM), null, null).withSuppPrivInfo(initKeyWithEnc.getEncoded());

            initSpec = new UserKeyingMaterialSpec(bldr.build().getEncoded());
        }

        // using the Frodo secret the initiator can now generate their side of the agreement.
        initAgreement.init(initKp.getPrivate(), initSpec);

        // generate the secret
        initAgreement.doPhase(recKp.getPublic(), true);

        byte[] initSecret = initAgreement.generateSecret();

        System.out.println("Initiator Generated hybrid secret: " + Hex.toHexString(initSecret));

        // You will set up a KEM Extract Spec with the receiver private key - in this case you need the encapsulation
        // generated by the receiver as well.
        KEMExtractSpec kemExtractSpec = new KEMExtractSpec(recFrodoKP.getPrivate(), encapsulation, "Secret");

        // Now you can initialize the key generator with the kem extract spec and retrieve the KEM secret.
        keyGenerator.init(kemExtractSpec);
        
        SecretKeyWithEncapsulation recKeyWithEnc = (SecretKeyWithEncapsulation)keyGenerator.generateKey();

        UserKeyingMaterialSpec recSpec;

        if (isPart1)
        {
            // you use the raw method for part 1 of the exercise
            recSpec = new UserKeyingMaterialSpec(recKeyWithEnc.getEncoded());
        }
        else
        {
            // a slightly more sophisticated method which allows for more regular user keying material as asked for in part 2 of the exercise
            DEROtherInfo.Builder bldr = new DEROtherInfo.Builder(new AlgorithmIdentifier(NISTObjectIdentifiers.id_aes128_GCM), null, null).withSuppPrivInfo(recKeyWithEnc.getEncoded());

            recSpec = new UserKeyingMaterialSpec(bldr.build().getEncoded());
        }

        KeyAgreement recAgreement = KeyAgreement.getInstance("ECCDHWITHSHA256KDF", "BC");

        // using the Frodo secret the receiver can now generate their side of the agreement.
        recAgreement.init(recKp.getPrivate(), recSpec);

        // generate the secret
        recAgreement.doPhase(initKp.getPublic(), true);

        byte[] recSecret = recAgreement.generateSecret();

        System.out.println("Receiver Generated hybrid secret: " + Hex.toHexString(recSecret));
    }

    public static void main(String[] args)
        throws Exception
    {
        // Let's add the required providers for this exercise
        // the regular Bouncy Castle provider for ECDHC
        Security.addProvider(new BouncyCastleProvider());
        // the Bouncy Castle post quantum provider for the PQC KEM.
        Security.addProvider(new BouncyCastlePQCProvider());

        // Part 1: just use raw KEM secret
        doExercise(true);

        // Part 2: use DEROtherInfo
        doExercise(false);
    }
}

 

Exercise 2

Using the KeyAgreement set up in the previous exercise, remove the passing of the KEM generated secret via the DEROtherInfo and instead use the HybridValueParameterSpec with the KEM generated secret being added as the T value, and the UserKeyingMaterialSpec you are now producing being added as the baseSpec argument.

ECCDHWITHSHA256KDF is based on the X9.63 KDF, which is a one-step KDF so the use of the HybridValueParameterSpec is not giving you quite as big a benefit as it would with say HKDF. Bouncy Castle is still in the process of adding Two-Step KDFs to the non-FIPS BC APIs though so keep it in mind, especially for later.

View solution to exercise 2
JAVA
public class Exercise2
{
    public static void main(String[] args)
        throws Exception
    {
        // Let's add the required providers for this exercise
        // the regular Bouncy Castle provider for ECDHC
        Security.addProvider(new BouncyCastleProvider());
        // the Bouncy Castle post quantum provider for the PQC KEM.
        Security.addProvider(new BouncyCastlePQCProvider());

        // You will request a key pair generator for EC and generate a key pair
        // for each recipient - this is the same as before, you're just using the JCA.
        KeyPairGenerator ecKpg = KeyPairGenerator.getInstance("EC", "BC");
        ecKpg.initialize(new ECGenParameterSpec("P-256"));

        // initiator
        KeyPair initKp = ecKpg.generateKeyPair();

        // receiver
        KeyPair recKp = ecKpg.generateKeyPair();

        // you'll also generate a Frodo key pair for the receiver.
        KeyPairGenerator frodoKpg = KeyPairGenerator.getInstance("Frodo", "BCPQC");
        frodoKpg.initialize(FrodoParameterSpec.frodokem19888r3, new SecureRandom());

        // Generating a key pair for receiver
        KeyPair recFrodoKP = frodoKpg.generateKeyPair();

        // The public key from this key pair
        PublicKey publicKey = recFrodoKP.getPublic();

        // You will request a key generator for frodo
        KeyGenerator keyGenerator = KeyGenerator.getInstance("Frodo", "BCPQC");

        // You will set up a KEM Generate Spec with the public key
        KEMGenerateSpec kemGenerateSpec = new KEMGenerateSpec(publicKey, "Secret");

        // Now you can initialize the key generator with the kem generate spec and generate out share secret
        keyGenerator.init(kemGenerateSpec);

        SecretKeyWithEncapsulation initKeyWithEnc = (SecretKeyWithEncapsulation)keyGenerator.generateKey();
        byte[] encapsulation = initKeyWithEnc.getEncapsulation();

        // You will request a key agreement for ECCDHWITHSHA256KDF
        KeyAgreement initAgreement = KeyAgreement.getInstance("ECCDHWITHSHA256KDF", "BC");

        // this time you use the HybridValueParameterSpec - this will mean the agreed secret Z will be concatenated
        // with KEM secret (or T value) and the result (Z || T) will be feed into the KDF.
        HybridValueParameterSpec initSpec = new HybridValueParameterSpec(initKeyWithEnc.getEncoded(), new UserKeyingMaterialSpec(Strings.toByteArray("Hybrid Exchange")));

        // using the Frodo secret the initiator can now generate their side of the agreement.
        initAgreement.init(initKp.getPrivate(), initSpec);

        // generate the secret
        initAgreement.doPhase(recKp.getPublic(), true);

        byte[] initSecret = initAgreement.generateSecret();

        System.out.println("Initiator Generated hybrid secret: " + Hex.toHexString(initSecret));

        // You will set up a KEM Extract Spec with the receiver private key - in this case you need the encapsulation
        // generated by the receiver as well.
        KEMExtractSpec kemExtractSpec = new KEMExtractSpec(recFrodoKP.getPrivate(), encapsulation, "Secret");

        // Now you can initialize the key generator with the kem extract spec and retrieve the KEM secret.
        keyGenerator.init(kemExtractSpec);

        SecretKeyWithEncapsulation recKeyWithEnc = (SecretKeyWithEncapsulation)keyGenerator.generateKey();

        // Again, use the hybrid spec to ensure you concatenate agreed secret and the KEM secret the same way
        HybridValueParameterSpec recSpec = new HybridValueParameterSpec(recKeyWithEnc.getEncoded(), new UserKeyingMaterialSpec(Strings.toByteArray("Hybrid Exchange")));

        // Using the Frodo secret the receiver can now generate their side of the agreement.
        initAgreement.init(recKp.getPrivate(), recSpec);

        // Generate the secret
        initAgreement.doPhase(initKp.getPublic(), true);

        byte[] recSecret = initAgreement.generateSecret();

        System.out.println("Receiver Generated hybrid secret: " + Hex.toHexString(recSecret));
    }
}

 

Exercise 3

Implement an RSA Key wrap/unwrap using the Cipher class and a 3072 bit RSA key with the algorithm "RSA/NONE/OAEPWithSHA256AndMGF1Padding" using the following symmetric KeySpec as input:

new SecretKeySpec(Hex.decode(“000102030405060708090a0b0c0d0e0f”), “AES”);

Once you are satisfied that it is working for both sides, add in the KEM-based key exchange and then return a new AES SecretKeySpec which is the XOR of the KEM secret and getEncoded() of the symmetric KeySpec.

View solution to exercise 3
JAVA
public class Exercise3
{
    /*
     * A basic XOR - return a xor b.
     */
    private static byte[] xor(byte[] a, byte[] b)
    {
        if (a.length !=  b.length)
        {
            throw new IllegalArgumentException("a and b must be equal in length");
        }

        byte[] rv = new byte[a.length];
        for (int i = 0; i != a.length; i++)
        {
            rv[i] = (byte)(a[i] ^ b[i]);
        }

        return rv;
    }

    public static void main(String[] args)
        throws Exception
    {
        // Let's add the required providers for this exercise
        Security.addProvider(new BouncyCastleProvider());
        Security.addProvider(new BouncyCastlePQCProvider());

        // Create a 3072 bit RSA key pair - this key pair is owned by the receiver
        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
        kpGen.initialize(new RSAKeyGenParameterSpec(3072, RSAKeyGenParameterSpec.F4));
        KeyPair recRSAKP = kpGen.generateKeyPair();

        // Create a Frodo key pair - this key pair is also owned by the receiver
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("Frodo", "BCPQC");
        kpg.initialize(FrodoParameterSpec.frodokem19888r3, new SecureRandom());
        // Generating a key pair
        KeyPair recFrodoKP = kpg.generateKeyPair();

        // Initiator side

        // Let's define the symmetric key you're going to wrap.
        Key startKey = new SecretKeySpec(Hex.decode("000102030405060708090a0b0c0d0e0f"), "AES");

        // Let's request the cipher for wrapping
        Cipher initWrapper = Cipher.getInstance("RSA/NONE/OAEPWithSHA256AndMGF1Padding", "BC");

        // Let's initialize it for wrapping and wrap the key
        initWrapper.init(Cipher.WRAP_MODE, recRSAKP.getPublic());

        byte[] wrappedKey = initWrapper.wrap(startKey);

        // Now create the KEM secret.
        KeyGenerator keyGenerator = KeyGenerator.getInstance("Frodo", "BCPQC");

        // You will set up a KEM Generate Spec with the public key
        KEMGenerateSpec kemGenerateSpec = new KEMGenerateSpec(recFrodoKP.getPublic(), "Secret");

        // Now you can initialize the key generator with the kem generate spec and generate out share secret
        keyGenerator.init(kemGenerateSpec);

        SecretKeyWithEncapsulation initKeyWithEnc = (SecretKeyWithEncapsulation)keyGenerator.generateKey();
        byte[] encapsulation = initKeyWithEnc.getEncapsulation();

        // create the symmetric key you will actually use with the receiver
        SecretKey initAESKey = new SecretKeySpec(xor(startKey.getEncoded(), initKeyWithEnc.getEncoded()), "AES");

        System.out.println("Initiator hybrid secret key: " + Hex.toHexString(initAESKey.getEncoded()));

        // Let's initialize it for unwrapping and unwrap the wrapped key
        Cipher recUnwrapper = Cipher.getInstance("RSA/NONE/OAEPWithSHA256AndMGF1Padding", "BC");

        recUnwrapper.init(Cipher.UNWRAP_MODE, recRSAKP.getPrivate());

        Key unwrappedKey = recUnwrapper.unwrap(wrappedKey, "AES", Cipher.SECRET_KEY);

        // You will set up a KEM Generate Spec with the public key
        KEMExtractSpec kemExtractSpec = new KEMExtractSpec(recFrodoKP.getPrivate(), encapsulation, "Secret");

        // Now you can initialize the key generator with the kem extract spec and process the encapsulation from the initiator.
        keyGenerator.init(kemExtractSpec);

        SecretKeyWithEncapsulation recKeyWithEnc = (SecretKeyWithEncapsulation)keyGenerator.generateKey();
        
        // create the symmetric key you will actually use with the receiver
        SecretKey recAESKey = new SecretKeySpec(xor(unwrappedKey.getEncoded(), recKeyWithEnc.getEncoded()), "AES");

        System.out.println("Receiver hybrid secret key: " + Hex.toHexString(recAESKey.getEncoded()));
    }
}

Next steps

You have now learned more about applying the use of Key Encapsulation Mechanisms (KEMs) with classical algorithms using the non-FIPS version of Bouncy Castle.

To learn how KEMs can be applied to more classical key agreement and key transport mechanisms, see Use the Post-Quantum KEMs in FIPS-certified Bouncy Castle.

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.