Use the Post-Quantum KEMs in FIPS-certified Bouncy Castle
Try out the following exercises to learn more about how to make use of Key Encapsulation Mechanisms (KEMs) with the FIPS-certified APIs. Things are a bit more abstract in this case as the KEM usage, at least for now, cannot be presented as a cryptographic service. We are still making use of the same techniques under the covers but they are presented differently to make clear to everyone what part is “recognized security” and what part is “taking a punt that is doing no harm”.
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
In the current certified BCFIPS API, there is one approach for Post-Quantum Hardening of a Key Agreement and one for dealing with Key Transport. This is only because the HybridValueParameterSpec is not yet supported.
For Key Agreement, we are currently restricted to make use of the UserKeyingMaterialParameterSpec in conjunction with a class called PQCOtherInfoGenerator (in the org.bouncycastle.pqc.addon package) which is available in the bcpqc-addon jar available with the BCFIPS distribution. For more information, refer to Bouncy Castle Java FIPS Resources.
For Key Transport there is PQCSecretKeyProcessor (also in the org.bouncycastle.pqc.addon package). Under the covers, this performs the same task as in Use the Post-Quantum KEMs in non-FIPS Bouncy Castle.
For now, the PQC addon is restricted to Classic McEliece and FrodoKEM. Kyber has been added after the release of BC 1.72.
Exercises
Try out the exercises in the following sections to learn more about how to make use of Key Encapsulation Mechanisms (KEMs) with the FIPS-certified APIs.
You should find the KeyAgreement and Cipher keywrap to be the same as for the exercises in Use the Post-Quantum KEMs in non-FIPS Bouncy Castle, other than using the BCFIPS provider. It might save some typing.
Exercise 1
Using the KeyAgreement class, implement an EC KeyAgreement using the algorithm “ECCDHWITHSHA256KDF”. Once that is working, add the use of the PQCOtherInfoGenerator using the PQCParameters.frodokem31296r3 parameter set. As before you will also need to use the UserKeyingMaterialSpec.
public class Exercise1
{
public static void doExercise1(KEMParameters kemParameters)
throws Exception
{
// Let's add the required providers for this exercise
// the Bouncy Castle fips provider
Security.addProvider(new BouncyCastleFipsProvider());
// You will request a key pair generator for EC and generate a key pair
// for each recipient
KeyPairGenerator ecKpg = KeyPairGenerator.getInstance("EC", "BCFIPS");
ecKpg.initialize(new ECGenParameterSpec("P-256"));
// initiator
KeyPair initKp = ecKpg.generateKeyPair();
// receiver
KeyPair recKp = ecKpg.generateKeyPair();
// You will use PQCOtherInfoGenerator to generate partyU (initiator) with the wanted parameters
SecureRandom random = new SecureRandom();
PQCOtherInfoGenerator.PartyU initParty = new PQCOtherInfoGenerator.PartyU(
kemParameters,
new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256),
Strings.toByteArray("PartyUId"),
Strings.toByteArray("PartyVId"),
random);
byte[] partA = initParty.getSuppPrivInfoPartA();
// Same for the recipient
PQCOtherInfoGenerator.PartyV recParty = new PQCOtherInfoGenerator.PartyV(
kemParameters,
new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256),
Strings.toByteArray("PartyUId"),
Strings.toByteArray("PartyVId"),
random);
byte[] partB = recParty.getSuppPrivInfoPartB(partA);
// You can now get the info for both init and rec
DEROtherInfo initOtherInfo = initParty.generate(partB);
DEROtherInfo recOtherInfo = recParty.generate();
// You will request a key agreement for ECCDHWITHSHA256KDF
KeyAgreement initAgreement = KeyAgreement.getInstance("ECCDHWITHSHA256KDF", "BCFIPS");
// creating the init spec with init info
UserKeyingMaterialSpec initSpec = new UserKeyingMaterialSpec(initOtherInfo.getEncoded());
// using the Frodo secret the initiator can now generate their side of the agreement.
initAgreement.init(initKp.getPrivate(), initSpec);
// generate the initiator secret
initAgreement.doPhase(recKp.getPublic(), true);
byte[] initSecret = initAgreement.generateSecret();
System.out.println("Initiator Generated hybrid secret: " + Hex.toHexString(initSecret));
// repeat the steps for the recipient
// request a key agreement for the recipient
KeyAgreement recAgreement = KeyAgreement.getInstance("ECCDHwithSHA256KDF", "BCFIPS");
// creating the rec spec with rec info
UserKeyingMaterialSpec recSpec = new UserKeyingMaterialSpec(recOtherInfo.getEncoded());
recAgreement.init(recKp.getPrivate(), recSpec);
recAgreement.doPhase(initKp.getPublic(), true);
// generate the receiver secret
byte[] recSecret = recAgreement.generateSecret();
System.out.println("Receiver Generated hybrid secret: " + Hex.toHexString(recSecret));
System.out.println("Part A Length: " + partA.length);
System.out.println("Part B Length: " + partB.length);
}
public static void main(String[] args)
throws Exception
{
doExercise1(PQCParameters.frodokem31296r3);
}
}
Exercise 2
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 by using the PQCSecretKeyProcessor and confirm while both sides still end up with the same key, the bytes it is made up of are looking very different.
public class Exercise2
{
public static void doExercise2(KEMParameters kemParameters)
throws Exception
{
// Let's add the required providers for this exercise
// the FIPS Bouncy Castle provider for RSA
Security.addProvider(new BouncyCastleFipsProvider());
SecureRandom secureRandom = new SecureRandom();
// Create a 3072 bit RSA key pair - this key pair is owned by the receiver
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BCFIPS");
kpGen.initialize(new RSAKeyGenParameterSpec(3072, RSAKeyGenParameterSpec.F4));
KeyPair recRSAKP = kpGen.generateKeyPair();
// Initiator side
// Let's define the symmetric key you're going to wrap.
SecretKey startKey = new SecretKeySpec(Hex.decode("000102030405060708090a0b0c0d0e0f"), "AES");
// Let's request the cipher for wrapping
Cipher initWrapper = Cipher.getInstance("RSA/NONE/OAEPWithSHA256AndMGF1Padding", "BCFIPS");
// Let's initialize it for wrapping and wrap the key
initWrapper.init(Cipher.WRAP_MODE, recRSAKP.getPublic());
byte[] wrappedKey = initWrapper.wrap(startKey);
// Now start the KEM process for partyU (the initiator) - this is different from before as in our
// case it is the initiator producing the key pair
PQCSecretKeyProcessor.PartyUBuilder partyU = new PQCSecretKeyProcessor.PartyUBuilder(kemParameters, secureRandom);
// Produce partA to send to partyV (essentially the public key)
byte[] partA = partyU.getPartA();
// Now you look at partyV's side. You unwrap the key and then generate an encapsulation to
// send back to partyU.
// Let's initialize it for unwrapping and unwrap the wrapped key
Cipher recUnwrapper = Cipher.getInstance("RSA/NONE/OAEPWithSHA256AndMGF1Padding", "BCFIPS");
recUnwrapper.init(Cipher.UNWRAP_MODE, recRSAKP.getPrivate());
SecretKey unwrappedKey = (SecretKey)recUnwrapper.unwrap(wrappedKey, "AES", Cipher.SECRET_KEY);
PQCSecretKeyProcessor.PartyVBuilder partyV = new PQCSecretKeyProcessor.PartyVBuilder(kemParameters, secureRandom);
byte[] partB = partyV.getPartB(partA);
SecretKeyProcessor vProcessor = partyV.build();
SecretKey vKey = vProcessor.processKey(unwrappedKey);
System.out.println("Receiver hybrid secret key: " + Hex.toHexString(vKey.getEncoded()));
// partyV sends partB back to partyU - this allows partyU to recover the secret. PartB is the encapsulation.
// partyU then creates a key processor of their own and should arrive at a new secret key.
SecretKeyProcessor uProcessor = partyU.build(partB);
// create the symmetric key you will actually use with the receiver
SecretKey uKey = uProcessor.processKey(startKey);
System.out.println("Initiator hybrid secret key: " + Hex.toHexString(uKey.getEncoded()));
System.out.println("Part A Length: " + partA.length);
System.out.println("Part B Length: " + partB.length);
}
public static void main(String[] args)
throws Exception
{
doExercise2(PQCParameters.mceliece348864fr3);
}
}
Exercise 3
Redo your solution for either of the previous exercises using PQCParameters.mceliece8192128r3. Once you are satisfied that the algorithm is post-quantum hardened, would you say that another problem may have shown up?
While this 256 bit secure, look at the lengths of the different parts produced.
public class Exercise3
{
public static void main(String[] args)
throws Exception
{
// repeating the exercises with mceliece8192128r3 parameter set.
Exercise1.doExercise1(PQCParameters.mceliece8192128r3);
Exercise2.doExercise2(PQCParameters.mceliece8192128r3);
// although the algorithm is post-quantum hardened, it struggles to maintain a reasonable public key size
// especially McEliece with the parameters you used above the public key size is 1,357,824 bytes (1.35MB)
// the private key being more reasonable yet still large 14,080 bytes. You can see this reflect in the
// length of the Part A component generated by the utility classes.
}
}
Related Content
-
Page:
-
Page: