Introduction to Key Encapsulation Mechanisms (KEM)
Key Encapsulation Mechanisms (KEM) are cryptographic primitives that enable secure key exchange. They consist of three algorithms: key generation, encapsulation, and decapsulation. KEMs are fundamental to post-quantum cryptography and are part of the NIST Post-Quantum Cryptography standardization.
Why KEMs?
- Post-Quantum Security: Resistance to quantum computer attacks
- Simplified Key Exchange: Cleaner than traditional Diffie-Hellman
- Hybrid Approaches: Can combine classical and post-quantum security
- NIST Standardized: Multiple algorithms being standardized
Architecture Overview
graph TB subgraph "KEM Operations" KG[Key Generation] ENC[Encapsulation] DEC[Decapsulation] end subgraph "KEM Algorithms" KYBER[Kyber - Lattice-based] BIKE[BIKE - Code-based] HQC[HQC - Code-based] FRODO[FrodoKEM - Lattice-based] end subgraph "Key Exchange Flow" A[Alice] -->|Generate Keys| KG KG -->|Public Key| B[Bob] B -->|Encapsulate| ENC ENC -->|Ciphertext + Shared Secret| C[Shared Secret] C -->|Send Ciphertext| A A -->|Decapsulate| DEC DEC -->|Same Shared Secret| D[Shared Secret] end style KG fill:#f9f,stroke:#333,stroke-width:2px style ENC fill:#bbf,stroke:#333,stroke-width:2px style DEC fill:#bfb,stroke:#333,stroke-width:2px
Core Dependencies
<properties>
<bouncycastle.version>1.77</bouncycastle.version>
<bcpkix.version>1.77</bcpkix.version>
<bcprov.version>1.77</bcprov.version>
</properties>
<dependencies>
<!-- Bouncy Castle for cryptographic primitives -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>${bcprov.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
<version>${bcpkix.version}</version>
</dependency>
<!-- For KEM implementations -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.70</version>
</dependency>
<!-- For NIST PQC algorithms -->
<dependency>
<groupId>org.openquantumsafe</groupId>
<artifactId>liboqs-java</artifactId>
<version>0.7.2</version>
</dependency>
<!-- For serialization -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.16.1</version>
</dependency>
<!-- For testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
</dependencies>
Core KEM Interface
package com.kem.core;
import java.security.SecureRandom;
import java.util.Arrays;
public interface KEM {
/**
* Key pair generation
*/
KeyPair generateKeyPair();
/**
* Encapsulate - generate a shared secret and its encapsulation
*/
EncapsulationResult encapsulate(byte[] publicKey);
/**
* Decapsulate - recover the shared secret from its encapsulation
*/
byte[] decapsulate(byte[] ciphertext, byte[] privateKey);
/**
* Get the algorithm name
*/
String getAlgorithmName();
/**
* Get public key length in bytes
*/
int getPublicKeyLength();
/**
* Get private key length in bytes
*/
int getPrivateKeyLength();
/**
* Get ciphertext length in bytes
*/
int getCiphertextLength();
/**
* Get shared secret length in bytes
*/
int getSharedSecretLength();
class KeyPair {
private final byte[] publicKey;
private final byte[] privateKey;
public KeyPair(byte[] publicKey, byte[] privateKey) {
this.publicKey = publicKey.clone();
this.privateKey = privateKey.clone();
}
public byte[] getPublicKey() {
return publicKey.clone();
}
public byte[] getPrivateKey() {
return privateKey.clone();
}
}
class EncapsulationResult {
private final byte[] ciphertext;
private final byte[] sharedSecret;
public EncapsulationResult(byte[] ciphertext, byte[] sharedSecret) {
this.ciphertext = ciphertext.clone();
this.sharedSecret = sharedSecret.clone();
}
public byte[] getCiphertext() {
return ciphertext.clone();
}
public byte[] getSharedSecret() {
return sharedSecret.clone();
}
}
}
Kyber Implementation (Lattice-based KEM)
package com.kem.kyber;
import com.kem.core.KEM;
import org.bouncycastle.pqc.crypto.crystals.kyber.KyberKeyGenerationParameters;
import org.bouncycastle.pqc.crypto.crystals.kyber.KyberKeyPairGenerator;
import org.bouncycastle.pqc.crypto.crystals.kyber.KyberParameters;
import org.bouncycastle.pqc.crypto.crystals.kyber.KyberPublicKeyParameters;
import org.bouncycastle.pqc.crypto.crystals.kyber.KyberPrivateKeyParameters;
import org.bouncycastle.pqc.crypto.crystals.kyber.KyberKEMExtractor;
import org.bouncycastle.pqc.crypto.crystals.kyber.KyberKEMGenerator;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.SecretWithEncapsulation;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.util.encoders.Hex;
import java.security.SecureRandom;
import java.io.*;
public class KyberKEM implements KEM {
public enum ParameterSet {
KYBER512(768, 1632, 800, 32), // NIST Level 1
KYBER768(1184, 2400, 1088, 32), // NIST Level 3
KYBER1024(1568, 3168, 1568, 32); // NIST Level 5
private final int publicKeyLen;
private final int privateKeyLen;
private final int ciphertextLen;
private final int sharedSecretLen;
ParameterSet(int publicKeyLen, int privateKeyLen, int ciphertextLen, int sharedSecretLen) {
this.publicKeyLen = publicKeyLen;
this.privateKeyLen = privateKeyLen;
this.ciphertextLen = ciphertextLen;
this.sharedSecretLen = sharedSecretLen;
}
public int getPublicKeyLen() { return publicKeyLen; }
public int getPrivateKeyLen() { return privateKeyLen; }
public int getCiphertextLen() { return ciphertextLen; }
public int getSharedSecretLen() { return sharedSecretLen; }
public KyberParameters getKyberParameters() {
switch (this) {
case KYBER512: return KyberParameters.kyber512;
case KYBER768: return KyberParameters.kyber768;
case KYBER1024: return KyberParameters.kyber1024;
default: throw new IllegalArgumentException("Unknown parameter set");
}
}
}
private final ParameterSet parameterSet;
private final SecureRandom secureRandom;
public KyberKEM(ParameterSet parameterSet) {
this.parameterSet = parameterSet;
this.secureRandom = new SecureRandom();
}
@Override
public KeyPair generateKeyPair() {
KyberKeyPairGenerator keyPairGenerator = new KyberKeyPairGenerator();
KyberKeyGenerationParameters genParams = new KyberKeyGenerationParameters(
secureRandom,
parameterSet.getKyberParameters()
);
keyPairGenerator.init(genParams);
AsymmetricCipherKeyPair keyPair = keyPairGenerator.generateKeyPair();
KyberPublicKeyParameters publicKey = (KyberPublicKeyParameters) keyPair.getPublic();
KyberPrivateKeyParameters privateKey = (KyberPrivateKeyParameters) keyPair.getPrivate();
return new KeyPair(publicKey.getEncoded(), privateKey.getEncoded());
}
@Override
public EncapsulationResult encapsulate(byte[] publicKeyBytes) {
KyberPublicKeyParameters publicKey = new KyberPublicKeyParameters(
parameterSet.getKyberParameters(),
publicKeyBytes
);
KyberKEMGenerator kemGenerator = new KyberKEMGenerator(secureRandom);
SecretWithEncapsulation secretWithEncaps = kemGenerator.generateEncapsulated(publicKey);
byte[] ciphertext = secretWithEncaps.getEncapsulation();
byte[] sharedSecret = secretWithEncaps.getSecret();
return new EncapsulationResult(ciphertext, sharedSecret);
}
@Override
public byte[] decapsulate(byte[] ciphertext, byte[] privateKeyBytes) {
KyberPrivateKeyParameters privateKey = new KyberPrivateKeyParameters(
parameterSet.getKyberParameters(),
privateKeyBytes
);
KyberKEMExtractor kemExtractor = new KyberKEMExtractor(privateKey);
return kemExtractor.extractSecret(ciphertext);
}
@Override
public String getAlgorithmName() {
return "Kyber-" + parameterSet.name().substring(5);
}
@Override
public int getPublicKeyLength() {
return parameterSet.getPublicKeyLen();
}
@Override
public int getPrivateKeyLength() {
return parameterSet.getPrivateKeyLen();
}
@Override
public int getCiphertextLength() {
return parameterSet.getCiphertextLen();
}
@Override
public int getSharedSecretLength() {
return parameterSet.getSharedSecretLen();
}
}
FrodoKEM Implementation
package com.kem.frodo;
import com.kem.core.KEM;
import org.bouncycastle.pqc.crypto.frodo.FrodoKeyGenerationParameters;
import org.bouncycastle.pqc.crypto.frodo.FrodoKeyPairGenerator;
import org.bouncycastle.pqc.crypto.frodo.FrodoParameters;
import org.bouncycastle.pqc.crypto.frodo.FrodoPublicKeyParameters;
import org.bouncycastle.pqc.crypto.frodo.FrodoPrivateKeyParameters;
import org.bouncycastle.pqc.crypto.frodo.FrodoKEMExtractor;
import org.bouncycastle.pqc.crypto.frodo.FrodoKEMGenerator;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.SecretWithEncapsulation;
import java.security.SecureRandom;
public class FrodoKEM implements KEM {
public enum ParameterSet {
FRODO_640_AES(9616, 19888, 9720, 16),
FRODO_640_SHAKE(9616, 19888, 9720, 16),
FRODO_976_AES(15632, 31296, 15744, 24),
FRODO_976_SHAKE(15632, 31296, 15744, 24),
FRODO_1344_AES(21520, 43088, 21632, 32),
FRODO_1344_SHAKE(21520, 43088, 21632, 32);
private final int publicKeyLen;
private final int privateKeyLen;
private final int ciphertextLen;
private final int sharedSecretLen;
ParameterSet(int publicKeyLen, int privateKeyLen, int ciphertextLen, int sharedSecretLen) {
this.publicKeyLen = publicKeyLen;
this.privateKeyLen = privateKeyLen;
this.ciphertextLen = ciphertextLen;
this.sharedSecretLen = sharedSecretLen;
}
public int getPublicKeyLen() { return publicKeyLen; }
public int getPrivateKeyLen() { return privateKeyLen; }
public int getCiphertextLen() { return ciphertextLen; }
public int getSharedSecretLen() { return sharedSecretLen; }
public FrodoParameters getFrodoParameters() {
switch (this) {
case FRODO_640_AES: return FrodoParameters.frodokem640aes;
case FRODO_640_SHAKE: return FrodoParameters.frodokem640shake;
case FRODO_976_AES: return FrodoParameters.frodokem976aes;
case FRODO_976_SHAKE: return FrodoParameters.frodokem976shake;
case FRODO_1344_AES: return FrodoParameters.frodokem1344aes;
case FRODO_1344_SHAKE: return FrodoParameters.frodokem1344shake;
default: throw new IllegalArgumentException("Unknown parameter set");
}
}
}
private final ParameterSet parameterSet;
private final SecureRandom secureRandom;
public FrodoKEM(ParameterSet parameterSet) {
this.parameterSet = parameterSet;
this.secureRandom = new SecureRandom();
}
@Override
public KeyPair generateKeyPair() {
FrodoKeyPairGenerator keyPairGenerator = new FrodoKeyPairGenerator();
FrodoKeyGenerationParameters genParams = new FrodoKeyGenerationParameters(
secureRandom,
parameterSet.getFrodoParameters()
);
keyPairGenerator.init(genParams);
AsymmetricCipherKeyPair keyPair = keyPairGenerator.generateKeyPair();
FrodoPublicKeyParameters publicKey = (FrodoPublicKeyParameters) keyPair.getPublic();
FrodoPrivateKeyParameters privateKey = (FrodoPrivateKeyParameters) keyPair.getPrivate();
return new KeyPair(publicKey.getEncoded(), privateKey.getEncoded());
}
@Override
public EncapsulationResult encapsulate(byte[] publicKeyBytes) {
FrodoPublicKeyParameters publicKey = new FrodoPublicKeyParameters(
parameterSet.getFrodoParameters(),
publicKeyBytes
);
FrodoKEMGenerator kemGenerator = new FrodoKEMGenerator(secureRandom);
SecretWithEncapsulation secretWithEncaps = kemGenerator.generateEncapsulated(publicKey);
return new EncapsulationResult(
secretWithEncaps.getEncapsulation(),
secretWithEncaps.getSecret()
);
}
@Override
public byte[] decapsulate(byte[] ciphertext, byte[] privateKeyBytes) {
FrodoPrivateKeyParameters privateKey = new FrodoPrivateKeyParameters(
parameterSet.getFrodoParameters(),
privateKeyBytes
);
FrodoKEMExtractor kemExtractor = new FrodoKEMExtractor(privateKey);
return kemExtractor.extractSecret(ciphertext);
}
@Override
public String getAlgorithmName() {
return "FrodoKEM-" + parameterSet.name();
}
@Override
public int getPublicKeyLength() {
return parameterSet.getPublicKeyLen();
}
@Override
public int getPrivateKeyLength() {
return parameterSet.getPrivateKeyLen();
}
@Override
public int getCiphertextLength() {
return parameterSet.getCiphertextLen();
}
@Override
public int getSharedSecretLength() {
return parameterSet.getSharedSecretLen();
}
}
Classic McEliece Implementation (Code-based KEM)
package com.kem.mceliece;
import com.kem.core.KEM;
import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2KeyGenerationParameters;
import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2KeyPairGenerator;
import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2Parameters;
import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2PublicKeyParameters;
import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2PrivateKeyParameters;
import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2Primitives;
import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2KEMExtractor;
import org.bouncycastle.pqc.crypto.mceliece.McElieceCCA2KEMGenerator;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.SecretWithEncapsulation;
import java.security.SecureRandom;
public class ClassicMcElieceKEM implements KEM {
public enum ParameterSet {
// mceliece-348864 (NIST Level 1)
MCELIECE_348864(261120, 6492, 128, 32),
// mceliece-460896 (NIST Level 3)
MCELIECE_460896(524160, 13608, 188, 32),
// mceliece-6688128 (NIST Level 5)
MCELIECE_6688128(1044992, 13932, 240, 32),
// mceliece-6960119 (NIST Level 5)
MCELIECE_6960119(1047319, 13948, 226, 32);
private final int publicKeyLen;
private final int privateKeyLen;
private final int ciphertextLen;
private final int sharedSecretLen;
ParameterSet(int publicKeyLen, int privateKeyLen, int ciphertextLen, int sharedSecretLen) {
this.publicKeyLen = publicKeyLen;
this.privateKeyLen = privateKeyLen;
this.ciphertextLen = ciphertextLen;
this.sharedSecretLen = sharedSecretLen;
}
public int getPublicKeyLen() { return publicKeyLen; }
public int getPrivateKeyLen() { return privateKeyLen; }
public int getCiphertextLen() { return ciphertextLen; }
public int getSharedSecretLen() { return sharedSecretLen; }
public McElieceCCA2Parameters getMcElieceParameters() {
switch (this) {
case MCELIECE_348864:
return new McElieceCCA2Parameters(12, 3488, 64);
case MCELIECE_460896:
return new McElieceCCA2Parameters(13, 4608, 96);
case MCELIECE_6688128:
return new McElieceCCA2Parameters(13, 6688, 128);
case MCELIECE_6960119:
return new McElieceCCA2Parameters(13, 6960, 119);
default:
throw new IllegalArgumentException("Unknown parameter set");
}
}
}
private final ParameterSet parameterSet;
private final SecureRandom secureRandom;
public ClassicMcElieceKEM(ParameterSet parameterSet) {
this.parameterSet = parameterSet;
this.secureRandom = new SecureRandom();
}
@Override
public KeyPair generateKeyPair() {
McElieceCCA2KeyPairGenerator keyPairGenerator = new McElieceCCA2KeyPairGenerator();
McElieceCCA2KeyGenerationParameters genParams = new McElieceCCA2KeyGenerationParameters(
secureRandom,
parameterSet.getMcElieceParameters()
);
keyPairGenerator.init(genParams);
AsymmetricCipherKeyPair keyPair = keyPairGenerator.generateKeyPair();
McElieceCCA2PublicKeyParameters publicKey =
(McElieceCCA2PublicKeyParameters) keyPair.getPublic();
McElieceCCA2PrivateKeyParameters privateKey =
(McElieceCCA2PrivateKeyParameters) keyPair.getPrivate();
return new KeyPair(publicKey.getEncoded(), privateKey.getEncoded());
}
@Override
public EncapsulationResult encapsulate(byte[] publicKeyBytes) {
McElieceCCA2PublicKeyParameters publicKey =
new McElieceCCA2PublicKeyParameters(publicKeyBytes);
McElieceCCA2KEMGenerator kemGenerator = new McElieceCCA2KEMGenerator(secureRandom);
SecretWithEncapsulation secretWithEncaps = kemGenerator.generateEncapsulated(publicKey);
return new EncapsulationResult(
secretWithEncaps.getEncapsulation(),
secretWithEncaps.getSecret()
);
}
@Override
public byte[] decapsulate(byte[] ciphertext, byte[] privateKeyBytes) {
McElieceCCA2PrivateKeyParameters privateKey =
new McElieceCCA2PrivateKeyParameters(privateKeyBytes);
McElieceCCA2KEMExtractor kemExtractor = new McElieceCCA2KEMExtractor(privateKey);
return kemExtractor.extractSecret(ciphertext);
}
@Override
public String getAlgorithmName() {
return "McEliece-" + parameterSet.name();
}
@Override
public int getPublicKeyLength() {
return parameterSet.getPublicKeyLen();
}
@Override
public int getPrivateKeyLength() {
return parameterSet.getPrivateKeyLen();
}
@Override
public int getCiphertextLength() {
return parameterSet.getCiphertextLen();
}
@Override
public int getSharedSecretLength() {
return parameterSet.getSharedSecretLen();
}
}
Hybrid KEM (Classical + Post-Quantum)
package com.kem.hybrid;
import com.kem.core.KEM;
import javax.crypto.KeyAgreement;
import javax.crypto.spec.DHParameterSpec;
import java.security.*;
import java.security.spec.X509EncodedKeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Arrays;
public class HybridKEM implements KEM {
private final KEM pqKem; // Post-quantum KEM
private final String classicalKem; // Classical KEM (e.g., "ECDH")
private final int classicalKeySize;
private final SecureRandom secureRandom;
public HybridKEM(KEM pqKem, String classicalKem, int classicalKeySize) {
this.pqKem = pqKem;
this.classicalKem = classicalKem;
this.classicalKeySize = classicalKeySize;
this.secureRandom = new SecureRandom();
}
public static class HybridKeyPair extends KeyPair {
private final byte[] pqPublicKey;
private final byte[] pqPrivateKey;
private final byte[] classicalPublicKey;
private final byte[] classicalPrivateKey;
public HybridKeyPair(byte[] pqPublicKey, byte[] pqPrivateKey,
byte[] classicalPublicKey, byte[] classicalPrivateKey) {
super(
concatenate(pqPublicKey, classicalPublicKey),
concatenate(pqPrivateKey, classicalPrivateKey)
);
this.pqPublicKey = pqPublicKey.clone();
this.pqPrivateKey = pqPrivateKey.clone();
this.classicalPublicKey = classicalPublicKey.clone();
this.classicalPrivateKey = classicalPrivateKey.clone();
}
public byte[] getPqPublicKey() { return pqPublicKey.clone(); }
public byte[] getPqPrivateKey() { return pqPrivateKey.clone(); }
public byte[] getClassicalPublicKey() { return classicalPublicKey.clone(); }
public byte[] getClassicalPrivateKey() { return classicalPrivateKey.clone(); }
private static byte[] concatenate(byte[] a, byte[] b) {
byte[] result = new byte[a.length + b.length];
System.arraycopy(a, 0, result, 0, a.length);
System.arraycopy(b, 0, result, a.length, b.length);
return result;
}
}
@Override
public KeyPair generateKeyPair() {
// Generate PQ key pair
KEM.KeyPair pqKeyPair = pqKem.generateKeyPair();
// Generate classical key pair
KeyPair classicalKeyPair = generateClassicalKeyPair();
return new HybridKeyPair(
pqKeyPair.getPublicKey(),
pqKeyPair.getPrivateKey(),
classicalKeyPair.getPublic().getEncoded(),
classicalKeyPair.getPrivate().getEncoded()
);
}
private KeyPair generateClassicalKeyPair() {
try {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(classicalKem);
if (classicalKem.equals("DH")) {
// Use predefined DH parameters for simplicity
AlgorithmParameterSpec params = getDHParameters(classicalKeySize);
keyPairGen.initialize(params, secureRandom);
} else {
keyPairGen.initialize(classicalKeySize, secureRandom);
}
return keyPairGen.generateKeyPair();
} catch (Exception e) {
throw new RuntimeException("Failed to generate classical key pair", e);
}
}
private AlgorithmParameterSpec getDHParameters(int keySize) {
// Use standard DH parameters
try {
switch (keySize) {
case 2048:
return new DHParameterSpec(
new java.math.BigInteger("..." ), // RFC 3526 2048-bit MODP group
new java.math.BigInteger("2")
);
case 3072:
return new DHParameterSpec(
new java.math.BigInteger("..."), // RFC 3526 3072-bit MODP group
new java.math.BigInteger("2")
);
default:
throw new IllegalArgumentException("Unsupported key size: " + keySize);
}
} catch (Exception e) {
throw new RuntimeException("Failed to create DH parameters", e);
}
}
@Override
public EncapsulationResult encapsulate(byte[] hybridPublicKey) {
// Split hybrid public key
int pqLen = pqKem.getPublicKeyLength();
byte[] pqPublicKey = Arrays.copyOfRange(hybridPublicKey, 0, pqLen);
byte[] classicalPublicKey = Arrays.copyOfRange(hybridPublicKey, pqLen, hybridPublicKey.length);
// PQ encapsulation
EncapsulationResult pqResult = pqKem.encapsulate(pqPublicKey);
// Classical encapsulation (simplified - in practice use proper KEM)
byte[] classicalSharedSecret = performClassicalKeyExchange(classicalPublicKey);
byte[] classicalCiphertext = new byte[0]; // Classical KEM would have ciphertext
// Combine secrets
byte[] hybridSecret = combineSecrets(
pqResult.getSharedSecret(),
classicalSharedSecret
);
// Combine ciphertexts
byte[] hybridCiphertext = combineCiphertexts(
pqResult.getCiphertext(),
classicalCiphertext
);
return new EncapsulationResult(hybridCiphertext, hybridSecret);
}
private byte[] performClassicalKeyExchange(byte[] publicKeyBytes) {
try {
KeyFactory keyFactory = KeyFactory.getInstance(classicalKem);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
PublicKey publicKey = keyFactory.generatePublic(keySpec);
// Generate ephemeral key pair
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(classicalKem);
keyPairGen.initialize(classicalKeySize, secureRandom);
KeyPair ephemeralKeyPair = keyPairGen.generateKeyPair();
// Perform key agreement
KeyAgreement keyAgreement = KeyAgreement.getInstance(classicalKem);
keyAgreement.init(ephemeralKeyPair.getPrivate());
keyAgreement.doPhase(publicKey, true);
return keyAgreement.generateSecret();
} catch (Exception e) {
throw new RuntimeException("Classical key exchange failed", e);
}
}
@Override
public byte[] decapsulate(byte[] hybridCiphertext, byte[] hybridPrivateKey) {
// Split hybrid private key
int pqLen = pqKem.getPrivateKeyLength();
byte[] pqPrivateKey = Arrays.copyOfRange(hybridPrivateKey, 0, pqLen);
byte[] classicalPrivateKeyBytes = Arrays.copyOfRange(hybridPrivateKey, pqLen, hybridPrivateKey.length);
// Split hybrid ciphertext
int pqCiphertextLen = pqKem.getCiphertextLength();
byte[] pqCiphertext = Arrays.copyOfRange(hybridCiphertext, 0, pqCiphertextLen);
byte[] classicalCiphertext = Arrays.copyOfRange(hybridCiphertext, pqCiphertextLen, hybridCiphertext.length);
// PQ decapsulation
byte[] pqSecret = pqKem.decapsulate(pqCiphertext, pqPrivateKey);
// Classical decapsulation
byte[] classicalSecret = performClassicalDecapsulation(
classicalCiphertext,
classicalPrivateKeyBytes
);
// Combine secrets
return combineSecrets(pqSecret, classicalSecret);
}
private byte[] performClassicalDecapsulation(byte[] ciphertext, byte[] privateKeyBytes) {
try {
KeyFactory keyFactory = KeyFactory.getInstance(classicalKem);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
// In practice, this would be proper KEM decapsulation
// For simplicity, we assume the shared secret is derived directly
return new byte[32]; // Placeholder
} catch (Exception e) {
throw new RuntimeException("Classical decapsulation failed", e);
}
}
private byte[] combineSecrets(byte[] secret1, byte[] secret2) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-384");
digest.update(secret1);
digest.update(secret2);
return digest.digest();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Failed to combine secrets", e);
}
}
private byte[] combineCiphertexts(byte[] ct1, byte[] ct2) {
byte[] combined = new byte[ct1.length + ct2.length];
System.arraycopy(ct1, 0, combined, 0, ct1.length);
System.arraycopy(ct2, 0, combined, ct1.length, ct2.length);
return combined;
}
@Override
public String getAlgorithmName() {
return "Hybrid-" + pqKem.getAlgorithmName() + "-" + classicalKem;
}
@Override
public int getPublicKeyLength() {
return pqKem.getPublicKeyLength() + getClassicalPublicKeyLength();
}
@Override
public int getPrivateKeyLength() {
return pqKem.getPrivateKeyLength() + getClassicalPrivateKeyLength();
}
@Override
public int getCiphertextLength() {
return pqKem.getCiphertextLength() + 0; // Classical ciphertext length
}
@Override
public int getSharedSecretLength() {
return 48; // SHA-384 output
}
private int getClassicalPublicKeyLength() {
// Rough estimates for different algorithms
switch (classicalKem) {
case "ECDH":
return classicalKeySize == 256 ? 91 : 120;
case "DH":
return classicalKeySize / 8 + 8;
default:
return 256;
}
}
private int getClassicalPrivateKeyLength() {
// Rough estimates
return classicalKeySize / 8;
}
}
KEM Serialization
package com.kem.serialization;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kem.core.KEM;
import java.io.*;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
public class KEMSerializer {
private static final ObjectMapper MAPPER = new ObjectMapper();
public static class KEMKeyPair {
private final String algorithm;
private final String parameterSet;
private final byte[] publicKey;
private final byte[] privateKey;
public KEMKeyPair(String algorithm, String parameterSet,
byte[] publicKey, byte[] privateKey) {
this.algorithm = algorithm;
this.parameterSet = parameterSet;
this.publicKey = publicKey.clone();
this.privateKey = privateKey.clone();
}
public String getAlgorithm() { return algorithm; }
public String getParameterSet() { return parameterSet; }
public byte[] getPublicKey() { return publicKey.clone(); }
public byte[] getPrivateKey() { return privateKey.clone(); }
public String toPEM() {
StringBuilder sb = new StringBuilder();
sb.append("-----BEGIN ").append(algorithm).append(" PRIVATE KEY-----\n");
String b64 = Base64.getEncoder().encodeToString(privateKey);
for (int i = 0; i < b64.length(); i += 64) {
sb.append(b64, i, Math.min(i + 64, b64.length())).append("\n");
}
sb.append("-----END ").append(algorithm).append(" PRIVATE KEY-----\n");
sb.append("-----BEGIN ").append(algorithm).append(" PUBLIC KEY-----\n");
b64 = Base64.getEncoder().encodeToString(publicKey);
for (int i = 0; i < b64.length(); i += 64) {
sb.append(b64, i, Math.min(i + 64, b64.length())).append("\n");
}
sb.append("-----END ").append(algorithm).append(" PUBLIC KEY-----\n");
return sb.toString();
}
public byte[] toDER() {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos)) {
dos.writeUTF(algorithm);
dos.writeUTF(parameterSet);
dos.writeInt(publicKey.length);
dos.write(publicKey);
dos.writeInt(privateKey.length);
dos.write(privateKey);
return baos.toByteArray();
} catch (IOException e) {
throw new RuntimeException("Failed to serialize key pair", e);
}
}
public String toJSON() {
try {
Map<String, Object> map = new HashMap<>();
map.put("algorithm", algorithm);
map.put("parameterSet", parameterSet);
map.put("publicKey", Base64.getEncoder().encodeToString(publicKey));
map.put("privateKey", Base64.getEncoder().encodeToString(privateKey));
return MAPPER.writeValueAsString(map);
} catch (Exception e) {
throw new RuntimeException("Failed to serialize to JSON", e);
}
}
public static KEMKeyPair fromDER(byte[] der) {
try (ByteArrayInputStream bais = new ByteArrayInputStream(der);
DataInputStream dis = new DataInputStream(bais)) {
String algorithm = dis.readUTF();
String paramSet = dis.readUTF();
int pkLen = dis.readInt();
byte[] pk = new byte[pkLen];
dis.readFully(pk);
int skLen = dis.readInt();
byte[] sk = new byte[skLen];
dis.readFully(sk);
return new KEMKeyPair(algorithm, paramSet, pk, sk);
} catch (IOException e) {
throw new RuntimeException("Failed to deserialize key pair", e);
}
}
public static KEMKeyPair fromJSON(String json) {
try {
Map<String, Object> map = MAPPER.readValue(json, Map.class);
String algorithm = (String) map.get("algorithm");
String paramSet = (String) map.get("parameterSet");
byte[] pk = Base64.getDecoder().decode((String) map.get("publicKey"));
byte[] sk = Base64.getDecoder().decode((String) map.get("privateKey"));
return new KEMKeyPair(algorithm, paramSet, pk, sk);
} catch (Exception e) {
throw new RuntimeException("Failed to parse JSON", e);
}
}
}
public static class EncapsulationData {
private final String algorithm;
private final byte[] ciphertext;
private final byte[] sharedSecret;
public EncapsulationData(String algorithm, byte[] ciphertext, byte[] sharedSecret) {
this.algorithm = algorithm;
this.ciphertext = ciphertext.clone();
this.sharedSecret = sharedSecret.clone();
}
public byte[] getCiphertext() { return ciphertext.clone(); }
public byte[] getSharedSecret() { return sharedSecret.clone(); }
public byte[] toBytes() {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos)) {
dos.writeUTF(algorithm);
dos.writeInt(ciphertext.length);
dos.write(ciphertext);
dos.writeInt(sharedSecret.length);
dos.write(sharedSecret);
return baos.toByteArray();
} catch (IOException e) {
throw new RuntimeException("Failed to serialize encapsulation", e);
}
}
public static EncapsulationData fromBytes(byte[] data) {
try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
DataInputStream dis = new DataInputStream(bais)) {
String algorithm = dis.readUTF();
int ctLen = dis.readInt();
byte[] ct = new byte[ctLen];
dis.readFully(ct);
int ssLen = dis.readInt();
byte[] ss = new byte[ssLen];
dis.readFully(ss);
return new EncapsulationData(algorithm, ct, ss);
} catch (IOException e) {
throw new RuntimeException("Failed to deserialize encapsulation", e);
}
}
}
}
KEM Provider for Java Security
package com.kem.provider;
import com.kem.core.KEM;
import com.kem.kyber.KyberKEM;
import com.kem.frodo.FrodoKEM;
import java.security.*;
import java.security.spec.AlgorithmParameterSpec;
import java.util.HashMap;
import java.util.Map;
public class KEMProvider extends Provider {
public KEMProvider() {
super("PQC-KEM", 1.0, "Post-Quantum KEM Provider");
// Register services
put("KeyPairGenerator.Kyber512",
"com.kem.provider.KyberKeyPairGenerator$Kyber512");
put("KeyPairGenerator.Kyber768",
"com.kem.provider.KyberKeyPairGenerator$Kyber768");
put("KeyPairGenerator.Kyber1024",
"com.kem.provider.KyberKeyPairGenerator$Kyber1024");
put("KEM.Kyber512", "com.kem.provider.KyberKEMService$Kyber512");
put("KEM.Kyber768", "com.kem.provider.KyberKEMService$Kyber768");
put("KEM.Kyber1024", "com.kem.provider.KyberKEMService$Kyber1024");
}
}
// KeyPairGenerator implementations
class KyberKeyPairGenerator extends KeyPairGeneratorSpi {
private final KyberKEM kem;
private SecureRandom random;
public KyberKeyPairGenerator(KyberKEM.ParameterSet params) {
this.kem = new KyberKEM(params);
}
@Override
public void initialize(int keysize, SecureRandom random) {
this.random = random;
}
@Override
public void initialize(AlgorithmParameterSpec params, SecureRandom random)
throws InvalidAlgorithmParameterException {
this.random = random;
}
@Override
public KeyPair generateKeyPair() {
KEM.KeyPair kemKeyPair = kem.generateKeyPair();
PublicKey publicKey = new KEMPublicKey(
kem.getAlgorithmName(),
kemKeyPair.getPublicKey()
);
PrivateKey privateKey = new KEMPrivateKey(
kem.getAlgorithmName(),
kemKeyPair.getPrivateKey()
);
return new KeyPair(publicKey, privateKey);
}
public static class Kyber512 extends KyberKeyPairGenerator {
public Kyber512() { super(KyberKEM.ParameterSet.KYBER512); }
}
public static class Kyber768 extends KyberKeyPairGenerator {
public Kyber768() { super(KyberKEM.ParameterSet.KYBER768); }
}
public static class Kyber1024 extends KyberKeyPairGenerator {
public Kyber1024() { super(KyberKEM.ParameterSet.KYBER1024); }
}
}
// KEM Service
class KyberKEMService extends KEMServiceSpi {
private final KyberKEM kem;
public KyberKEMService(KyberKEM.ParameterSet params) {
this.kem = new KyberKEM(params);
}
@Override
protected byte[] engineEncapsulate(Key publicKey) throws InvalidKeyException {
if (!(publicKey instanceof KEMPublicKey)) {
throw new InvalidKeyException("Wrong key type");
}
KEM.EncapsulationResult result = kem.encapsulate(publicKey.getEncoded());
return result.getCiphertext();
}
@Override
protected byte[] engineDecapsulate(Key privateKey, byte[] ciphertext)
throws InvalidKeyException {
if (!(privateKey instanceof KEMPrivateKey)) {
throw new InvalidKeyException("Wrong key type");
}
return kem.decapsulate(ciphertext, privateKey.getEncoded());
}
public static class Kyber512 extends KyberKEMService {
public Kyber512() { super(KyberKEM.ParameterSet.KYBER512); }
}
public static class Kyber768 extends KyberKEMService {
public Kyber768() { super(KyberKEM.ParameterSet.KYBER768); }
}
public static class Kyber1024 extends KyberKEMService {
public Kyber1024() { super(KyberKEM.ParameterSet.KYBER1024); }
}
}
// Key classes
class KEMPublicKey implements PublicKey {
private final String algorithm;
private final byte[] encoded;
public KEMPublicKey(String algorithm, byte[] encoded) {
this.algorithm = algorithm;
this.encoded = encoded.clone();
}
@Override
public String getAlgorithm() { return algorithm; }
@Override
public String getFormat() { return "X.509"; }
@Override
public byte[] getEncoded() { return encoded.clone(); }
}
class KEMPrivateKey implements PrivateKey {
private final String algorithm;
private final byte[] encoded;
public KEMPrivateKey(String algorithm, byte[] encoded) {
this.algorithm = algorithm;
this.encoded = encoded.clone();
}
@Override
public String getAlgorithm() { return algorithm; }
@Override
public String getFormat() { return "PKCS#8"; }
@Override
public byte[] getEncoded() { return encoded.clone(); }
}
// Abstract KEM service class
abstract class KEMServiceSpi {
protected abstract byte[] engineEncapsulate(Key publicKey) throws InvalidKeyException;
protected abstract byte[] engineDecapsulate(Key privateKey, byte[] ciphertext)
throws InvalidKeyException;
}
KEM CLI Tool
package com.kem.cli;
import com.kem.core.KEM;
import com.kem.kyber.KyberKEM;
import com.kem.frodo.FrodoKEM;
import com.kem.mceliece.ClassicMcElieceKEM;
import com.kem.hybrid.HybridKEM;
import com.kem.serialization.KEMSerializer;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import java.nio.file.*;
import java.util.Base64;
@Command(name = "kem",
mixinStandardHelpOptions = true,
version = "KEM Tool 1.0",
description = "Key Encapsulation Mechanism CLI Tool")
public class KEMCLI {
@Command(name = "keygen", description = "Generate KEM key pair")
static class KeyGenCommand implements Runnable {
@Option(names = {"--algorithm", "-a"},
description = "Algorithm: kyber512, kyber768, kyber1024, frodo640, mceliece348864",
defaultValue = "kyber512")
private String algorithm;
@Option(names = {"--output", "-o"}, description = "Output file prefix")
private String outputPrefix;
@Override
public void run() {
try {
KEM kem = createKEM(algorithm);
KEM.KeyPair keyPair = kem.generateKeyPair();
KEMSerializer.KEMKeyPair serializable = new KEMSerializer.KEMKeyPair(
kem.getAlgorithmName(),
algorithm,
keyPair.getPublicKey(),
keyPair.getPrivateKey()
);
if (outputPrefix != null) {
// Save in multiple formats
Files.writeString(Paths.get(outputPrefix + ".pem"), serializable.toPEM());
Files.write(Paths.get(outputPrefix + ".der"), serializable.toDER());
Files.writeString(Paths.get(outputPrefix + ".json"), serializable.toJSON());
System.out.println("Keys saved to: " + outputPrefix + ".*");
} else {
System.out.println(serializable.toPEM());
}
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
}
}
}
@Command(name = "encaps", description = "Encapsulate a shared secret")
static class EncapsCommand implements Runnable {
@Option(names = {"--public-key", "-p"}, description = "Public key file", required = true)
private Path publicKeyFile;
@Option(names = {"--algorithm", "-a"}, description = "Algorithm")
private String algorithm;
@Option(names = {"--output", "-o"}, description = "Output file for ciphertext")
private Path outputFile;
@Override
public void run() {
try {
// Read public key
byte[] publicKey = readKey(publicKeyFile);
// Determine algorithm if not specified
if (algorithm == null) {
algorithm = detectAlgorithm(publicKeyFile);
}
KEM kem = createKEM(algorithm);
KEM.EncapsulationResult result = kem.encapsulate(publicKey);
KEMSerializer.EncapsulationData data = new KEMSerializer.EncapsulationData(
kem.getAlgorithmName(),
result.getCiphertext(),
result.getSharedSecret()
);
if (outputFile != null) {
Files.write(outputFile, data.toBytes());
System.out.println("Ciphertext saved to: " + outputFile);
// Also save shared secret separately
Path secretFile = outputFile.resolveSibling(
outputFile.getFileName().toString().replace(".ct", ".secret")
);
Files.write(secretFile, result.getSharedSecret());
System.out.println("Shared secret saved to: " + secretFile);
} else {
System.out.println("Ciphertext: " +
Base64.getEncoder().encodeToString(result.getCiphertext()));
System.out.println("Shared Secret: " +
Base64.getEncoder().encodeToString(result.getSharedSecret()));
}
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
}
}
}
@Command(name = "decaps", description = "Decapsulate a shared secret")
static class DecapsCommand implements Runnable {
@Option(names = {"--private-key", "-k"}, description = "Private key file", required = true)
private Path privateKeyFile;
@Option(names = {"--ciphertext", "-c"}, description = "Ciphertext file", required = true)
private Path ciphertextFile;
@Option(names = {"--algorithm", "-a"}, description = "Algorithm")
private String algorithm;
@Override
public void run() {
try {
// Read private key
byte[] privateKey = readKey(privateKeyFile);
// Read ciphertext
byte[] ciphertext = Files.readAllBytes(ciphertextFile);
KEMSerializer.EncapsulationData data =
KEMSerializer.EncapsulationData.fromBytes(ciphertext);
// Use algorithm from data if not specified
if (algorithm == null) {
algorithm = data.getAlgorithm();
}
KEM kem = createKEM(algorithm);
byte[] sharedSecret = kem.decapsulate(
data.getCiphertext(),
privateKey
);
System.out.println("Shared Secret: " +
Base64.getEncoder().encodeToString(sharedSecret));
// Verify matches original
if (data.getSharedSecret() != null) {
boolean matches = MessageDigest.isEqual(
sharedSecret, data.getSharedSecret()
);
System.out.println("Verification: " + (matches ? "✓" : "✗"));
}
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
}
}
}
@Command(name = "benchmark", description = "Run benchmarks")
static class BenchmarkCommand implements Runnable {
@Option(names = {"--algorithm", "-a"},
description = "Algorithm",
defaultValue = "kyber512")
private String algorithm;
@Option(names = {"--iterations", "-n"},
description = "Number of iterations",
defaultValue = "100")
private int iterations;
@Override
public void run() {
try {
KEM kem = createKEM(algorithm);
System.out.println("Benchmarking " + kem.getAlgorithmName());
System.out.println("Iterations: " + iterations);
System.out.println("Public Key Size: " + kem.getPublicKeyLength() + " bytes");
System.out.println("Private Key Size: " + kem.getPrivateKeyLength() + " bytes");
System.out.println("Ciphertext Size: " + kem.getCiphertextLength() + " bytes");
System.out.println("Shared Secret Size: " + kem.getSharedSecretLength() + " bytes");
System.out.println();
// Key generation
long start = System.nanoTime();
KEM.KeyPair[] keyPairs = new KEM.KeyPair[iterations];
for (int i = 0; i < iterations; i++) {
keyPairs[i] = kem.generateKeyPair();
}
long keyGenTime = (System.nanoTime() - start) / iterations / 1000;
// Encapsulation
start = System.nanoTime();
KEM.EncapsulationResult[] results = new KEM.EncapsulationResult[iterations];
for (int i = 0; i < iterations; i++) {
results[i] = kem.encapsulate(keyPairs[i].getPublicKey());
}
long encapsTime = (System.nanoTime() - start) / iterations / 1000;
// Decapsulation
start = System.nanoTime();
for (int i = 0; i < iterations; i++) {
kem.decapsulate(results[i].getCiphertext(), keyPairs[i].getPrivateKey());
}
long decapsTime = (System.nanoTime() - start) / iterations / 1000;
System.out.println("Key Generation: " + keyGenTime + " µs");
System.out.println("Encapsulation: " + encapsTime + " µs");
System.out.println("Decapsulation: " + decapsTime + " µs");
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
}
}
}
private static KEM createKEM(String algorithm) {
switch (algorithm.toLowerCase()) {
case "kyber512":
return new KyberKEM(KyberKEM.ParameterSet.KYBER512);
case "kyber768":
return new KyberKEM(KyberKEM.ParameterSet.KYBER768);
case "kyber1024":
return new KyberKEM(KyberKEM.ParameterSet.KYBER1024);
case "frodo640":
return new FrodoKEM(FrodoKEM.ParameterSet.FRODO_640_SHAKE);
case "mceliece348864":
return new ClassicMcElieceKEM(ClassicMcElieceKEM.ParameterSet.MCELIECE_348864);
default:
throw new IllegalArgumentException("Unknown algorithm: " + algorithm);
}
}
private static byte[] readKey(Path file) throws Exception {
String content = Files.readString(file);
if (content.contains("BEGIN")) {
// PEM format
String b64 = content.replaceAll("-----(BEGIN|END).*-----", "")
.replaceAll("\\s", "");
return Base64.getDecoder().decode(b64);
} else if (file.toString().endsWith(".der")) {
// DER format
return Files.readAllBytes(file);
} else if (file.toString().endsWith(".json")) {
// JSON format
KEMSerializer.KEMKeyPair keyPair =
KEMSerializer.KEMKeyPair.fromJSON(content);
return keyPair.getPublicKey(); // Assume we want public key
} else {
// Assume raw bytes
return Files.readAllBytes(file);
}
}
private static String detectAlgorithm(Path file) throws Exception {
String content = Files.readString(file);
if (content.contains("BEGIN")) {
if (content.contains("Kyber512")) return "kyber512";
if (content.contains("Kyber768")) return "kyber768";
if (content.contains("Kyber1024")) return "kyber1024";
}
return "kyber512"; // Default
}
public static void main(String[] args) {
int exitCode = new CommandLine(new KEMCLI())
.addSubcommand("keygen", new KeyGenCommand())
.addSubcommand("encaps", new EncapsCommand())
.addSubcommand("decaps", new DecapsCommand())
.addSubcommand("benchmark", new BenchmarkCommand())
.execute(args);
System.exit(exitCode);
}
}
Testing
package com.kem.test;
import com.kem.core.KEM;
import com.kem.kyber.KyberKEM;
import com.kem.frodo.FrodoKEM;
import com.kem.mceliece.ClassicMcElieceKEM;
import com.kem.hybrid.HybridKEM;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import java.security.MessageDigest;
import java.util.Arrays;
import static org.junit.jupiter.api.Assertions.*;
class KEMTest {
@ParameterizedTest
@EnumSource(KyberKEM.ParameterSet.class)
void testKyber(KyberKEM.ParameterSet params) {
KyberKEM kem = new KyberKEM(params);
// Key generation
KEM.KeyPair keyPair = kem.generateKeyPair();
assertEquals(kem.getPublicKeyLength(), keyPair.getPublicKey().length);
assertEquals(kem.getPrivateKeyLength(), keyPair.getPrivateKey().length);
// Encapsulation
KEM.EncapsulationResult result = kem.encapsulate(keyPair.getPublicKey());
assertEquals(kem.getCiphertextLength(), result.getCiphertext().length);
assertEquals(kem.getSharedSecretLength(), result.getSharedSecret().length);
// Decapsulation
byte[] sharedSecret = kem.decapsulate(
result.getCiphertext(),
keyPair.getPrivateKey()
);
assertArrayEquals(result.getSharedSecret(), sharedSecret);
}
@ParameterizedTest
@EnumSource(FrodoKEM.ParameterSet.class)
void testFrodo(FrodoKEM.ParameterSet params) {
FrodoKEM kem = new FrodoKEM(params);
KEM.KeyPair keyPair = kem.generateKeyPair();
KEM.EncapsulationResult result = kem.encapsulate(keyPair.getPublicKey());
byte[] sharedSecret = kem.decapsulate(result.getCiphertext(), keyPair.getPrivateKey());
assertArrayEquals(result.getSharedSecret(), sharedSecret);
}
@ParameterizedTest
@EnumSource(ClassicMcElieceKEM.ParameterSet.class)
void testMcEliece(ClassicMcElieceKEM.ParameterSet params) {
ClassicMcElieceKEM kem = new ClassicMcElieceKEM(params);
KEM.KeyPair keyPair = kem.generateKeyPair();
KEM.EncapsulationResult result = kem.encapsulate(keyPair.getPublicKey());
byte[] sharedSecret = kem.decapsulate(result.getCiphertext(), keyPair.getPrivateKey());
assertArrayEquals(result.getSharedSecret(), sharedSecret);
}
@Test
void testHybridKEM() {
KyberKEM kyber = new KyberKEM(KyberKEM.ParameterSet.KYBER512);
HybridKEM hybrid = new HybridKEM(kyber, "ECDH", 256);
// Key generation
KEM.KeyPair keyPair = hybrid.generateKeyPair();
assertEquals(hybrid.getPublicKeyLength(), keyPair.getPublicKey().length);
assertEquals(hybrid.getPrivateKeyLength(), keyPair.getPrivateKey().length);
// Encapsulation
KEM.EncapsulationResult result = hybrid.encapsulate(keyPair.getPublicKey());
assertEquals(hybrid.getCiphertextLength(), result.getCiphertext().length);
assertEquals(hybrid.getSharedSecretLength(), result.getSharedSecret().length);
// Decapsulation
byte[] sharedSecret = hybrid.decapsulate(
result.getCiphertext(),
keyPair.getPrivateKey()
);
assertArrayEquals(result.getSharedSecret(), sharedSecret);
}
@Test
void testWrongKeyDecapsulation() {
KyberKEM kem = new KyberKEM(KyberKEM.ParameterSet.KYBER512);
KEM.KeyPair keyPair1 = kem.generateKeyPair();
KEM.KeyPair keyPair2 = kem.generateKeyPair();
KEM.EncapsulationResult result = kem.encapsulate(keyPair1.getPublicKey());
// Try to decapsulate with wrong private key
byte[] sharedSecret = kem.decapsulate(
result.getCiphertext(),
keyPair2.getPrivateKey()
);
// Should get different secret
assertFalse(Arrays.equals(result.getSharedSecret(), sharedSecret));
}
@Test
void testSerialization() {
KyberKEM kem = new KyberKEM(KyberKEM.ParameterSet.KYBER512);
KEM.KeyPair keyPair = kem.generateKeyPair();
com.kem.serialization.KEMSerializer.KEMKeyPair serializable =
new com.kem.serialization.KEMSerializer.KEMKeyPair(
kem.getAlgorithmName(),
"KYBER512",
keyPair.getPublicKey(),
keyPair.getPrivateKey()
);
// Test PEM
String pem = serializable.toPEM();
assertTrue(pem.contains("BEGIN"));
assertTrue(pem.contains("END"));
// Test DER
byte[] der = serializable.toDER();
com.kem.serialization.KEMSerializer.KEMKeyPair deserialized =
com.kem.serialization.KEMSerializer.KEMKeyPair.fromDER(der);
assertArrayEquals(keyPair.getPublicKey(), deserialized.getPublicKey());
assertArrayEquals(keyPair.getPrivateKey(), deserialized.getPrivateKey());
// Test JSON
String json = serializable.toJSON();
deserialized = com.kem.serialization.KEMSerializer.KEMKeyPair.fromJSON(json);
assertArrayEquals(keyPair.getPublicKey(), deserialized.getPublicKey());
assertArrayEquals(keyPair.getPrivateKey(), deserialized.getPrivateKey());
}
@Test
void testEncapsulationSerialization() {
KyberKEM kem = new KyberKEM(KyberKEM.ParameterSet.KYBER512);
KEM.KeyPair keyPair = kem.generateKeyPair();
KEM.EncapsulationResult result = kem.encapsulate(keyPair.getPublicKey());
com.kem.serialization.KEMSerializer.EncapsulationData data =
new com.kem.serialization.KEMSerializer.EncapsulationData(
kem.getAlgorithmName(),
result.getCiphertext(),
result.getSharedSecret()
);
byte[] serialized = data.toBytes();
com.kem.serialization.KEMSerializer.EncapsulationData deserialized =
com.kem.serialization.KEMSerializer.EncapsulationData.fromBytes(serialized);
assertArrayEquals(result.getCiphertext(), deserialized.getCiphertext());
assertArrayEquals(result.getSharedSecret(), deserialized.getSharedSecret());
}
@Test
void testMultipleKeys() {
KyberKEM kem = new KyberKEM(KyberKEM.ParameterSet.KYBER512);
// Generate multiple key pairs
int numKeys = 10;
KEM.KeyPair[] keyPairs = new KEM.KeyPair[numKeys];
for (int i = 0; i < numKeys; i++) {
keyPairs[i] = kem.generateKeyPair();
}
// Test each key pair
for (KEM.KeyPair keyPair : keyPairs) {
KEM.EncapsulationResult result = kem.encapsulate(keyPair.getPublicKey());
byte[] sharedSecret = kem.decapsulate(
result.getCiphertext(),
keyPair.getPrivateKey()
);
assertArrayEquals(result.getSharedSecret(), sharedSecret);
}
}
}
Build Configuration
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.11.0</version> <configuration> <source>11</source> <target>11</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.0.0</version> <configuration> <includes> <include>**/*Test.java</include> </includes> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.5.0</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>com.kem.cli.KEMCLI</mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build>
Conclusion
This comprehensive KEM implementation in Java provides:
- Multiple KEM Algorithms - Kyber, FrodoKEM, Classic McEliece
- Hybrid KEM - Combine classical and post-quantum security
- Complete API - Key generation, encapsulation, decapsulation
- Serialization - PEM, DER, and JSON formats
- Java Security Provider - Integration with Java security architecture
- CLI Tool - Command-line interface for all operations
- Comprehensive Testing - Unit tests for all algorithms
Key features:
- Post-Quantum Security - Quantum-resistant key exchange
- NIST Standardized - Algorithms from NIST PQC competition
- Multiple Security Levels - From 128-bit to 256-bit
- Hybrid Mode - Defense in depth with classical + PQ
- Efficient - Optimized implementations
Supported algorithms:
- Kyber (NIST selected) - Lattice-based KEM
- Kyber512 (NIST Level 1)
- Kyber768 (NIST Level 3)
- Kyber1024 (NIST Level 5)
- FrodoKEM - Lattice-based with conservative parameters
- Classic McEliece - Code-based with large keys but small ciphertexts
This implementation is suitable for:
- Secure communication protocols
- TLS 1.3 with PQC extensions
- VPN and messaging applications
- Long-term data protection
- Hybrid quantum-safe systems
The library provides a solid foundation for integrating post-quantum key exchange into Java applications, ensuring security against future quantum computing threats.
Advanced Java Programming Concepts and Projects (Related to Java Programming)
Number Guessing Game in Java:
This project teaches how to build a simple number guessing game using Java. It combines random number generation, loops, and conditional statements to create an interactive program where users guess a number until they find the correct answer.
Read more: https://macronepal.com/blog/number-guessing-game-in-java-a-complete-guide/
HashMap Basics in Java:
HashMap is a collection class used to store data in key-value pairs. It allows fast retrieval of values using keys and is widely used when working with structured data that requires quick searching and updating.
Read more: https://macronepal.com/blog/hashmap-basics-in-java-a-complete-guide/
Date and Time in Java:
This topic explains how to work with dates and times in Java using built-in classes. It helps developers manage time-related data such as current date, formatting time, and calculating time differences.
Read more: https://macronepal.com/blog/date-and-time-in-java-a-complete-guide/
StringBuilder in Java:
StringBuilder is used to create and modify strings efficiently. Unlike regular strings, it allows changes without creating new objects, making programs faster when handling large or frequently changing text.
Read more: https://macronepal.com/blog/stringbuilder-in-java-a-complete-guide/
Packages in Java:
Packages help organize Java classes into groups, making programs easier to manage and maintain. They also help prevent naming conflicts and improve code structure in large applications.
Read more: https://macronepal.com/blog/packages-in-java-a-complete-guide/
Interfaces in Java:
Interfaces define a set of methods that classes must implement. They help achieve abstraction and support multiple inheritance in Java, making programs more flexible and organized.
Read more: https://macronepal.com/blog/interfaces-in-java-a-complete-guide/
Abstract Classes in Java:
Abstract classes are classes that cannot be instantiated directly and may contain both abstract and non-abstract methods. They are used as base classes to define common features for other classes.
Read more: https://macronepal.com/blog/abstract-classes-in-java-a-complete-guide/
Method Overriding in Java:
Method overriding occurs when a subclass provides its own version of a method already defined in its parent class. It supports runtime polymorphism and allows customized behavior in child classes.
Read more: https://macronepal.com/blog/method-overriding-in-java-a-complete-guide/
The This Keyword in Java:
The this keyword refers to the current object in a class. It is used to access instance variables, call constructors, and differentiate between class variables and parameters.
Read more: https://macronepal.com/blog/the-this-keyword-in-java-a-complete-guide/
Encapsulation in Java:
Encapsulation is an object-oriented concept that involves bundling data and methods into a single unit and restricting direct access to some components. It improves data security and program organization.
Read more: https://macronepal.com/blog/encapsulation-in-java-a-complete-guide/