Overview
CRYSTALS-Kyber is an IND-CCA2-secure key encapsulation mechanism (KEM), whose security is based on the hardness of solving the learning-with-errors (LWE) problem over module lattices. CRYSTALS-Kyber is a quantum-safe algorithm (QSA) and is a member of the CRYSTALS (Cryptographic Suite for Algebraic Lattices) suite of algorithms [2].
Support for CRYSTALS-Kyber is now supported for IBM Semeru Java versions 11 and 17 on the IBMJCECCA security provider [3]. IBM Semeru currently supports Kyber-1024 Round 2. Kyber-1024 aims at security roughly equivalent to AES-256 [2]. IBMJCECCA currently offers the following implementations:
- CRYSTALS-Kyber 1024 Round 2
With CRYSTALS-Kyber, it is now possible to perform a quantum-safe hybrid key exchange scheme that combines the protection of traditional Elliptic Curve Cryptography (ECC) with the quantum-safe CRYSTALS-Kyber algorithm. This hybrid key exchange scheme provides two layers of protection and ensures that all key exchanges are protected from attacks by traditional and quantum computers [2].
This tutorial will demonstrate how to integrate CRYSTALS-KYber into a Java application. Before you continue reading, please ensure your environment is properly configured by reading QSC on Java: Configuring your IBM Z system to use Quantum-safe algorithms.
Step 1. Generate Alice's keys
1.1. Generate Kyber keys
The first step is to create a Kyber key pair for Alice using the KeyPairGenerator
and KyberKeyParameterSpec
classes.
The KyberKeyParameterSpec
class accepts the following values based on the desired implementation outlined in the Overview section:
KeyPairGenerator kyberKPG =
KeyPairGenerator.getInstance("CRYSTALS-Kyber", "IBMJCECCA");
KyberKeyParameterSpec kyberGenPS =
new KyberKeyParameterSpec("kyber1024r2");
kyberKPG.initialize(kyberGenPS, null);
KeyPair kybPairAlice = kyberKPG.generateKeyPair();
1.2. Generate EC keys
Next, we must create an EC key pair for Alice using the IBMJCECCA provider.
ECGenParameterSpec ecGenPSAlice = new ECGenParameterSpec("secp256r1");
KeyPairGenerator ecKPGAlice = KeyPairGenerator.getInstance("EC", "IBMJCECCA");
ecKPGAlice.initialize(ecGenPSAlice, null);
KeyPair ecPairAlice = ecKPGAlice.generateKeyPair();
Step 2. Generate Bob's keys
2.1. Generate EC keys
Bob must also create his own EC key pair.
ECGenParameterSpec ecGenPSBob = new ECGenParameterSpec("secp256r1");
KeyPairGenerator ecKPGBob = KeyPairGenerator.getInstance("EC", "IBMJCECCA");
ecKPGBob.initialize(ecGenPSBob, null);
KeyPair ecPairBob = ecKPGBob.generateKeyPair();
2.2. Generate AES CIPHER key
Instead of creating his own Kyber keys, Bob must create his own AES CIPHER key. Only AES CIPHER keys are allowed for a CRYSTALS-Kyber key agreement.
CCAAlgorithmParameterSpec ccaAlgParmSpec =
new CCAAlgorithmParameterSpec(256);
ccaAlgParmSpec.setKeyUsage(KeyUsage.OP_CIPHER);
KeyGenerator keyGen = KeyGenerator.getInstance("AES", "IBMJCECCA");
keyGen.init(ccaAlgParmSpec, null);
SecretKey aesKeyBob = keyGen.generateKey();
Step 3. Derive secret for Bob
Now that all of the necessary keys are created for Alice and Bob, we can begin the process of generating the key agreement. IBMJCECCA uses the classes KyberDerivationInput
and KyberKeyAgreement
classes to perform a CRYSTALS-Kyber key agreement.
KyberDerivationInput
takes in an AES CIPHER key, CRYSTALS-Kyber public key, and a 16-byte initialization vector (IV) to perform a PKA Encrypt service via ICSF. This service generates a random 32B value which is then encrypted in two different ways:
- Using the CRYSTALS-Kyber public key (Kyber-encrypted value)
- Using the AES CIPHER key with the IV (AES-encrypted value)
These two different values will then be used with the KyberKeyAgreement
class. The KyberKeyAgreement
class takes in a series of keys and values and derives the final shared key agreement. The input for this class varies for Alice and Bob. See the steps below for a full rundown of the steps.
3.1. Create IV
First, we must derive a 16-byte initialization vector.
SecureRandom random = new SecureRandom();
byte[] iv = new byte[16];
random.nextBytes(iv);
3.2. Use KyberDerivationInput to get encrypted values
We now have all of the inputs necessary to create the KyberDerivationInput
class. The encrypted values are calculated after the encrypt()
method is invoked.
KyberPublicKey kybPubAlice = (KyberPublicKey) kybPairAlice.getPublic();
KyberDerivationInput kdi =
new KyberDerivationInput(aesKeyBob, kybPubAlice, iv);
kdi.encrypt();
byte[] aesEncryptedValue = kdi.getEncryptedAesValue();
byte[] kyberEncryptedValue = kdi.getEncryptedKyberValue();
3.3. Create and initialize KyberKeyAgreement
Bob can now initialize a KyberKeyAgreement
class with the information gathered from the previous steps.
ECPublicKey ecPubAlice = (ECPublicKey) ecPairAlice.getPublic();
ECPrivateHWKey ecPrivBob = (ECPrivateHWKey) ecPairBob.getPrivate();
KyberKeyAgreement bobKyberAgree = new KyberKeyAgreement();
bobKyberAgree.init(aesKeyBob, ecPrivBob, ecPubAlice, aesEncryptedValue, iv);
3.4. Generate shared secret
Finally, Bob just has to invoke the generateSecret()
method to generate the final key to be shared between him and Alice.
The generateSecret()
method accepts AES, DES, and DESede as the final shared secret algorithm.
AESKey bobSharedSecretKey = (AESKey) bobKyberAgree.generateSecret("AES");
Step 4. Derive secret for Alice
4.1. Create and initialize KyberKeyAgreement
Now that Bob has generated the shared secret, Alice must do the same. Alice does not need to perform a KyberDerivationInput because she will be using the Kyber-encrypted value generated when Bob performed that step. So, Alice can go right to initializing KyberKeyAgreement
.
ECPublicKey ecPubBob = (ECPublicKey) ecPairBob.getPublic();
KyberPrivateKey kybPrivAlice = (KyberPrivateKey) kybPairAlice.getPrivate();
ECPrivateHWKey ecPrivAlice = (ECPrivateHWKey) ecPairAlice.getPrivate();
KyberKeyAgreement aliceKyberAgree = new KyberKeyAgreement();
aliceKyberAgree.init(kybPrivAlice, ecPrivAlice, ecPubBob, kyberEncryptedValue, null);
4.2. Generate shared secret
Finally, Alice can invoke the generateSecret()
method to generate the final key to be shared between her and Bob.
AESKey aliceSharedSecretKey = (AESKey) aliceKyberAgree.generateSecret("AES");
Step 5. Verify secrets match
Let's add in a quick check to verify that the shared secrets match.
if (Arrays.equals(aliceSharedSecretKey.getEncoded(),
bobSharedSecretKey.getEncoded())
) {
System.out.println("The secrets match!");
} else {
System.out.println("The secrets DO NOT match.");
}
Example
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.spec.ECGenParameterSpec;
import java.util.Arrays;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import com.ibm.crypto.hdwrCCA.provider.AESKey;
import com.ibm.crypto.hdwrCCA.provider.CCAAlgorithmParameterSpec;
import com.ibm.crypto.hdwrCCA.provider.ECPrivateHWKey;
import com.ibm.crypto.hdwrCCA.provider.KyberDerivationInput;
import com.ibm.crypto.hdwrCCA.provider.KyberKeyAgreement;
import com.ibm.crypto.hdwrCCA.provider.KyberKeyParameterSpec;
import com.ibm.crypto.hdwrCCA.provider.KyberPrivateKey;
import com.ibm.crypto.hdwrCCA.provider.KyberPublicKey;
import com.ibm.crypto.hdwrCCA.provider.SymmetricKeyConstants.KeyUsage;
import com.ibm.crypto.hdwrCCA.provider.ECPublicKey;
public class KyberExample {
public static void main(String[] args) throws Exception {
// -----------------------------------------------------------------
// STEP 1: Generate Alice's keys
// -----------------------------------------------------------------
// 1.1. Generate Kyber keys
KeyPairGenerator kyberKPG =
KeyPairGenerator.getInstance("CRYSTALS-Kyber", "IBMJCECCA");
KyberKeyParameterSpec kyberGenPS =
new KyberKeyParameterSpec("kyber1024r2");
kyberKPG.initialize(kyberGenPS, null);
KeyPair kybPairAlice = kyberKPG.generateKeyPair();
// 1.2. Generate EC keys
ECGenParameterSpec ecGenPSAlice = new ECGenParameterSpec("secp256r1");
KeyPairGenerator ecKPGAlice = KeyPairGenerator.getInstance("EC", "IBMJCECCA");
ecKPGAlice.initialize(ecGenPSAlice, null);
KeyPair ecPairAlice = ecKPGAlice.generateKeyPair();
// -----------------------------------------------------------------
// STEP 2: Generate Bob's keys
// -----------------------------------------------------------------
// 2.1. Generate EC keys
ECGenParameterSpec ecGenPSBob = new ECGenParameterSpec("secp256r1");
KeyPairGenerator ecKPGBob = KeyPairGenerator.getInstance("EC", "IBMJCECCA");
ecKPGBob.initialize(ecGenPSBob, null);
KeyPair ecPairBob = ecKPGBob.generateKeyPair();
// 2.2 Generate AES CIPHER key
CCAAlgorithmParameterSpec ccaAlgParmSpec =
new CCAAlgorithmParameterSpec(256);
ccaAlgParmSpec.setKeyUsage(KeyUsage.OP_CIPHER);
KeyGenerator keyGen = KeyGenerator.getInstance("AES", "IBMJCECCA");
keyGen.init(ccaAlgParmSpec, null);
SecretKey aesKeyBob = keyGen.generateKey();
// -----------------------------------------------------------------
// STEP 3: Derive secret for Bob
// -----------------------------------------------------------------
// 3.1. Create IV
SecureRandom random = new SecureRandom();
byte[] iv = new byte[16];
random.nextBytes(iv);
// 3.2. Use KyberDerivationInput to get encrypted values
KyberPublicKey kybPubAlice = (KyberPublicKey) kybPairAlice.getPublic();
KyberDerivationInput kdi =
new KyberDerivationInput(aesKeyBob, kybPubAlice, iv);
kdi.encrypt();
byte[] aesEncryptedValue = kdi.getEncryptedAesValue();
byte[] kyberEncryptedValue = kdi.getEncryptedKyberValue();
// 3.3. Create and initialize KyberKeyAgreement
ECPublicKey ecPubAlice = (ECPublicKey) ecPairAlice.getPublic();
ECPrivateHWKey ecPrivBob = (ECPrivateHWKey) ecPairBob.getPrivate();
KyberKeyAgreement bobKyberAgree = new KyberKeyAgreement();
bobKyberAgree.init(aesKeyBob, ecPrivBob, ecPubAlice, aesEncryptedValue, iv);
// 3.4. Generate shared secret
AESKey bobSharedSecretKey = (AESKey) bobKyberAgree.generateSecret("AES");
// -----------------------------------------------------------------
// STEP 4: Derive secret for Alice
// -----------------------------------------------------------------
// 4.1. Create and initialize KyberKeyAgreement
ECPublicKey ecPubBob = (ECPublicKey) ecPairBob.getPublic();
KyberPrivateKey kybPrivAlice = (KyberPrivateKey) kybPairAlice.getPrivate();
ECPrivateHWKey ecPrivAlice = (ECPrivateHWKey) ecPairAlice.getPrivate();
KyberKeyAgreement aliceKyberAgree = new KyberKeyAgreement();
aliceKyberAgree.init(kybPrivAlice, ecPrivAlice, ecPubBob, kyberEncryptedValue, null);
// 4.2. Generate shared secret
AESKey aliceSharedSecretKey = (AESKey) aliceKyberAgree.generateSecret("AES");
// -----------------------------------------------------------------
// STEP 5: Verify secrets match
// -----------------------------------------------------------------
if (Arrays.equals(aliceSharedSecretKey.getEncoded(),
bobSharedSecretKey.getEncoded())
) {
System.out.println("The secrets match!");
} else {
System.out.println("The secrets DO NOT match.");
}
}
}
Output
The previous Example should produce the following output:
The secrets match!
Conclusion
In this tutorial, we learned how to use the CRYSTALS-Kyber QSC algorithm with IBMJCECCA to create keys and generate a shared secret between two parties. Thanks for reading!
If you have additional questions, please email me at Gregory.Cernera@ibm.com.
References
- QSC on Java: Configuring your IBM Z system to use Quantum-safe algorithms
- Transitioning to Quantum-Safe Cryptography on IBM Z
- Semeru for zOS, V11.0.22.0 and V17.0.10.0 GA!
- CRYSTALS-Kyber Algorithm
How to obtain an IBM Semeru JDK
The IBM Java SAF APIs are included in the IBM Semeru Runtime Certified Edition for z/OS download. Please follow the links below to download the IBM Semeru JDK onto your own machines.
How to obtain IBM Semeru Runtime Certified Edition for z/OS?
IBM Semeru Runtime Certified Edition for z/OS is available for zero license charge through Shopz SMP/E, or you can download the non-SMP/E here. The subscription and service number is 5655-I48.
Supporting Links:
IBM Semeru Runtime Certified Edition for z/OS product page
For additional information on installation, troubleshooting and support please visit IBM Documentation.