Try out the following exercises to gain some low-level experience in using the Key Encapsulation Mechanisms (KEMs) so that you have a clear idea of what is happening under the covers when you look at the higher-level applications of the KEMs in Post-Quantum KEMs in non-FIPS Bouncy Castle and Post-Quantum KEMs in FIPS-certified Bouncy Castle.

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 interfaces involved for Bouncy Castle KEMs are:

  • EncapsulatedSecretGenerator: Takes a public key as input, and returns a SecretWithEncapsulation which contains the generated secret and an encrypted encapsulation of it.
  • EncapsulatedSecretExtractor: Takes a private key on construction which it then uses to open a passed-in encrypted encapsulation returning the secret generated by the original EncapsulatedSecretGenerator.

At the JCA level, KEMs can be invoked via the KeyGenerator class. When used via a KeyGenerator the KEMGenerateSpec and the KEMExtractSpec are used to signal which operation is being performed. In both cases, the return type will be SecretKeyWithEncapsulation (found in org.bouncycastle.jcajce).

As of now, so for BC 172b14, the following KEM generator classes are available:

  • CMCEKEMGenerator
  • FrodoKEMGenerator
  • NTRUKEMGenerator
  • NTRULPRimeKEMGenerator
  • SABERKEMGenerator
  • SIKEKEMGenerator
  • SNTRUPrimeKEMGenerator
  • CMCE, Frodo, and SABER are also available in BC 1.71, so examples will be drawn from those.

Important Note

In August 2022 an attack was published against SIKE which badly compromised the algorithm, while we still provide the class (it will print a warning if used), it should not be used for anything requiring security. It is for research only.

Exercises

Try out the exercises in the following sections to gain some experience in using the Key Encapsulation Mechanisms (KEMs).

Exercise 1

Choose either Frodo or CMCE, using the first parameter set, generate a keypair and satisfy yourself you can use that to generate a SecretWithEncapsulation and also recover the secret using the appropriate KEM extractor class.

You will find the appropriate classes in org.bouncycastle.pqc.crypto.frodo and org.bouncycastle.pqc.crypto.cmce, including the parameter set class, the key generation parameters class, the public/private key classes, and the key pair generator.

public class Exercise1
{
    public static void main(String[] args)
            throws Exception
    {
        // In order to generate a keypair you must first create the generator parameters,
        // using a source of randomness and the algorithms parameters.
        SecureRandom random = new SecureRandom();
        FrodoKeyGenerationParameters genParam = new FrodoKeyGenerationParameters(random, FrodoParameters.frodokem19888r3);

        // Now you can create the key pair generator and initialize it with the parameter.
        FrodoKeyPairGenerator keyPairGenerator = new FrodoKeyPairGenerator();
        keyPairGenerator.init(genParam);

        // Generate the keypair
        AsymmetricCipherKeyPair keyPair = keyPairGenerator.generateKeyPair();

        // In order to generate a SecretWithEncapsulation, you need a KEM Generator.
        // The KEM Generator will need the public key of your key pair as a Public Key Parameter.
        FrodoPublicKeyParameters pubParameters = (FrodoPublicKeyParameters)keyPair.getPublic();

        // Now you can create the KEM Generator and generate the secrete with encapsulation.
        FrodoKEMGenerator frodoEncCipher = new FrodoKEMGenerator(random);
        SecretWithEncapsulation secWenc = frodoEncCipher.generateEncapsulated(pubParameters);

        // You can generate the cipher text and the shared secret
        byte[] generated_cipher_text = secWenc.getEncapsulation();
        byte[] initiatorSecret = secWenc.getSecret();
        System.out.println("Initiator Generated Secret: " + Hex.toHexString(initiatorSecret));

        // To recover the secret you need to create the KEM Extractor class,
        // the extractor will need the private key of your key pair as a Private Key Parameter.
        FrodoPrivateKeyParameters privParameters = (FrodoPrivateKeyParameters)keyPair.getPrivate();

        FrodoKEMExtractor frodoDecCipher = new FrodoKEMExtractor(privParameters);

        byte[] recipientSecret = frodoDecCipher.extractSecret(generated_cipher_text);
        System.out.println("Recipient Generated Secret: " + Hex.toHexString(recipientSecret));
        System.out.println();
        System.out.println("Public Key Length: " + pubParameters.publicKey.length);
        System.out.println("Generated Secret Length: " + initiatorSecret.length);
        System.out.println("Generated Secret matches: " + Arrays.areEqual(initiatorSecret, recipientSecret));
    }
}
JAVA

 

Exercise 2

Different parameter sets have different levels of security. Try a couple of different parameter sets including the last one and you should notice a change in the size of the secret (as well as the encapsulation). You will probably notice a change in performance as well.

public class Exercise2
{
    public static void main(String[] args)
        throws Exception
    {
        // This time you will use the last parameter for Frodo
        SecureRandom random = new SecureRandom();
        FrodoKeyGenerationParameters genParam = new FrodoKeyGenerationParameters(random, FrodoParameters.frodokem43088r3);

        FrodoKeyPairGenerator keyPairGenerator = new FrodoKeyPairGenerator();
        keyPairGenerator.init(genParam);

        // Generating the key pair with the last parameter will take longer and output longer keys.
        AsymmetricCipherKeyPair keyPair = keyPairGenerator.generateKeyPair();

        FrodoPublicKeyParameters pubParameters = (FrodoPublicKeyParameters)keyPair.getPublic();

        // KEM Enc
        FrodoKEMGenerator frodoEncCipher = new FrodoKEMGenerator(random);
        SecretWithEncapsulation secWenc = frodoEncCipher.generateEncapsulated(pubParameters);
        byte[] generated_cipher_text = secWenc.getEncapsulation();
        byte[] initiatorSecret = secWenc.getSecret();
        // The secret's size also increased from 16 bytes to 32 bytes
        System.out.println("Initiator Generated Secret: " + Hex.toHexString(initiatorSecret));


        // KEM Dec
        FrodoPrivateKeyParameters privParameters = (FrodoPrivateKeyParameters)keyPair.getPrivate();
        FrodoKEMExtractor frodoDecCipher = new FrodoKEMExtractor(privParameters);
        byte[] recipientSecret = frodoDecCipher.extractSecret(generated_cipher_text);
        System.out.println("Recipient Generated Secret: " + Hex.toHexString(recipientSecret));
        System.out.println();
        // Notice how much larger the public key is.
        System.out.println("Public Key Length: " + pubParameters.publicKey.length);
        System.out.println("Generated Secret Length: " + initiatorSecret.length);
        System.out.println("Generated Secret matches: " + Arrays.areEqual(initiatorSecret, recipientSecret));
    }
}
JAVA


Exercise 3

Implement an AES key wrap using the AESWrapEngine class (it is in org.bouncycastle.crypto.engines) wrapping the following key:

new KeyParameter(Hex.decode(“000102030405060708090a0b0c0d0e0f”);

What consideration would need to be made if the above key was 256 bits and the system you were working on was supposed to be rated 256 bits secure?

public class Exercise3
{
    public static void main(String[] args)
        throws IOException, InvalidCipherTextException
    {
        // In order to generate a keypair you must first create the generator parameters,
        // using a source of randomness and the algorithms parameters.
        SecureRandom random = new SecureRandom();
        FrodoKeyGenerationParameters genParam = new FrodoKeyGenerationParameters(random, FrodoParameters.frodokem19888r3);

        // Now you can create the key pair generator and initialize it with your parameter.
        FrodoKeyPairGenerator keyPairGenerator = new FrodoKeyPairGenerator();
        keyPairGenerator.init(genParam);

        // Generate the keypair
        AsymmetricCipherKeyPair keyPair = keyPairGenerator.generateKeyPair();

        // In order to generate a SecretWithEncapsulation, you need a KEM Generator.
        // The KEM Generator will need the public key of your key pair as a Public Key Parameter.
        // Let's retrieve the public key from your key pair,
        // and use the PublicKeyFactory to create the necessary Public Key Parameter Object.
        FrodoPublicKeyParameters pubParameters = (FrodoPublicKeyParameters)keyPair.getPublic();

        // Now you can create the KEM Generator and generate the secrete with encapsulation.
        FrodoKEMGenerator frodoEncCipher = new FrodoKEMGenerator(random);
        SecretWithEncapsulation initEnc = frodoEncCipher.generateEncapsulated(pubParameters);

        // The key you want to wrap
        KeyParameter key = new KeyParameter(Hex.decode("000102030405060708090a0b0c0d0e0f"));

        // Let's create the AES wrap engine.
        Wrapper initWrapper = new AESWrapEngine();
        initWrapper.init(true, new KeyParameter(initEnc.getSecret()));

        byte[] keyEnc = key.getKey();
        byte[] wrappedKey = initWrapper.wrap(keyEnc, 0, keyEnc.length);
        System.out.println("Wrapped Key: " + Hex.toHexString(wrappedKey));
        
        // At this end you need to recover the wrapping key and the key you originally wrapped.
        FrodoPrivateKeyParameters privParameters = (FrodoPrivateKeyParameters)keyPair.getPrivate();

        FrodoKEMExtractor frodoDecCipher = new FrodoKEMExtractor(privParameters);

        // recipientSecret should be the wrapping key
        byte[] recipientSecret = frodoDecCipher.extractSecret(initEnc.getEncapsulation());

        Wrapper recWrapper = new AESWrapEngine();
        recWrapper.init(false, new KeyParameter(recipientSecret));

        // and this step should uncover the original wrapped key.
        byte[] unwrappedKey = recWrapper.unwrap(wrappedKey, 0, wrappedKey.length);

        System.out.println("Unwrapped Key: " + Hex.toHexString(unwrappedKey));
        System.out.println();
        System.out.println("Public Key Length: " + pubParameters.publicKey.length);
        System.out.println("Generated Secret Length: " + recipientSecret.length);
    }
}
JAVA

 

Exercise 4 

ADVANCED

Implement an ECCDH key exchange (use ECDHCBasicAgreement) with a KDF (use KDF2BytesGenerator from org.bouncycastle.crypto.generators) to generate a KeyParameter. This gives you your “classical” key exchange.

Now take the code from your KEM generation earlier and concatenate the generated secret from the KEM with the agreed secret from ECCDH (in case of confusion, the KEM secret goes second) and use that output to create a KeyParameter.

public class Exercise4
{
    public static void main(String[] arg) throws Exception
    {
        // Creating the EC Domain Parameters
        SecureRandom random = new SecureRandom();
        ECDomainParameters params = new ECDomainParameters(ECNamedCurveTable.getByName("P-256"));

        // Generating the EC key pair
        ECKeyPairGenerator ecKpGen = new ECKeyPairGenerator();
        ECKeyGenerationParameters genParameters = new ECKeyGenerationParameters(params, random);
        ecKpGen.init(genParameters);

        // one key pair for the initiator
        AsymmetricCipherKeyPair initKeyPair = ecKpGen.generateKeyPair();

        // one key pair for the recipient
        AsymmetricCipherKeyPair recKeyPair = ecKpGen.generateKeyPair();

        // You will just create a Frodo key pair for the recipient.
        FrodoKeyGenerationParameters genParam = new FrodoKeyGenerationParameters(random, FrodoParameters.frodokem31296r3);
        FrodoKeyPairGenerator frodoKpGen = new FrodoKeyPairGenerator();
        frodoKpGen.init(genParam);
        AsymmetricCipherKeyPair recFrodoKP = frodoKpGen.generateKeyPair();

        // The agreement step the ECDHC Basic Agreement (cofactor Diffie-Hellman)
        ECDHCBasicAgreement agreement = new ECDHCBasicAgreement();

        agreement.init(initKeyPair.getPrivate());

        BigInteger initAgreed = agreement.calculateAgreement(recKeyPair.getPublic());

        byte[] initZ = BigIntegers.asUnsignedByteArray(agreement.getFieldSize(), initAgreed);

        // pass the receiver's Frodo public key to the initiator. In the BC low level API
        // you can use org.bouncycastle.pqc.crypto.util.SubjectPublicKeyInfoFactory to produce
        // an ASN.1 structure representing the public key which you then encode for transmission
        // as bytes.
        FrodoPublicKeyParameters pubParameters = (FrodoPublicKeyParameters)recFrodoKP.getPublic();
        FrodoKEMGenerator frodoEncCipher = new FrodoKEMGenerator(random);
        SecretWithEncapsulation initEnc = frodoEncCipher.generateEncapsulated(pubParameters);

        byte[] initT = initEnc.getSecret();

        // introduce the KDF - X9.63 style using SHA-256.
        KDF2BytesGenerator initKdf = new KDF2BytesGenerator(new SHA256Digest());

        // the second parameter is just the regular user keying materiel
        initKdf.init(new KDFParameters(Arrays.concatenate(initZ, initT), Strings.toByteArray("Hybrid Exchange")));

        // Generate
        byte[] initBuf = new byte[32];
        initKdf.generateBytes(initBuf, 0, initBuf.length);
        KeyParameter initKey = new KeyParameter(initBuf);
        System.out.println("Initiator Generated hybrid key: " + Hex.toHexString(initKey.getKey()));

        // Recipient Side.
        // Do the agreement
        ECDHCBasicAgreement recAgree = new ECDHCBasicAgreement();

        recAgree.init(recKeyPair.getPrivate());

        BigInteger recAgreed = recAgree.calculateAgreement(initKeyPair.getPublic());

        byte[] recZ = BigIntegers.asUnsignedByteArray(recAgree.getFieldSize(), recAgreed);

        // now extract the KEM secret.
        FrodoKEMExtractor frodoDecCipher = new FrodoKEMExtractor((FrodoKeyParameters)recFrodoKP.getPrivate());
        byte[] recT = frodoDecCipher.extractSecret(initEnc.getEncapsulation());

        // introduce the recipient KDF - again X9.63 style using SHA-256.
        KDF2BytesGenerator recKdf = new KDF2BytesGenerator(new SHA256Digest());

        // the second parameter is just the regular user keying material
        recKdf.init(new KDFParameters(Arrays.concatenate(recZ, recT), Strings.toByteArray("Hybrid Exchange")));

        // Generate the recipient secret key.
        byte[] recBuf = new byte[32];
        recKdf.generateBytes(recBuf, 0, recBuf.length);
        KeyParameter recKey = new KeyParameter(initBuf);
        System.out.println("Recipient Generated hybrid key: " + Hex.toHexString(recKey.getKey()));
    }
}
JAVA

You have now quantum-hardened the exchange!

Next steps

You have now gained some experience in using Key Encapsulation Mechanisms (KEMs).

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