Files
codeql/java/ql/test/experimental/library-tests/quantum/jca/KeyAgreementHybridCryptosystem.java
2025-10-06 10:46:09 -04:00

273 lines
11 KiB
Java

package com.example.crypto.algorithms;
// import org.bouncycastle.jce.provider.BouncyCastleProvider;
// import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.util.Arrays;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
/**
* KeyAgreementHybridCryptosystem demonstrates two hybrid cryptosystems:
*
* 1. ECDH + AES-GCM: - Secure Flow: Uses ephemeral ECDH key pairs on secp256r1,
* applies a simple KDF (SHA-256) to derive a 128-bit AES key, and uses AES-GCM
* with a random 12-byte nonce. - Insecure Flow: Reuses a static key pair, uses
* raw shared secret truncation, and employs a fixed (zero) IV.
*
* 2. X25519 + ChaCha20-Poly1305: - Secure Flow: Uses ephemeral X25519 key
* pairs, applies a KDF (SHA-256) to derive a 256-bit key, and uses
* ChaCha20-Poly1305 with a random nonce. - Insecure Flow: Reuses a static key
* pair, directly truncates the shared secret without a proper KDF, and uses a
* fixed nonce.
*
* SAST/CBOM Notes: - Secure flows use proper ephemeral key generation, a simple
* KDF, and random nonces. - Insecure flows use static keys, fixed nonces, and
* raw shared secret truncation.
*/
public class KeyAgreementHybridCryptosystem {
// static {
// Security.addProvider(new BouncyCastleProvider());
// Security.addProvider(new BouncyCastlePQCProvider());
// }
// ---------- Helper Methods ----------
/**
* Generates an ephemeral ECDH key pair on secp256r1.
*/
public KeyPair generateECDHKeyPair() throws Exception {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");
kpg.initialize(new ECGenParameterSpec("secp256r1"), new SecureRandom());
return kpg.generateKeyPair();
}
/**
* Generates an ephemeral X25519 key pair.
*/
public KeyPair generateX25519KeyPair() throws Exception {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("X25519", "BC");
kpg.initialize(255, new SecureRandom());
return kpg.generateKeyPair();
}
/**
* Derives a shared secret using the provided key agreement algorithm.
*/
public byte[] deriveSharedSecret(PrivateKey privateKey, PublicKey publicKey, String algorithm) throws Exception {
KeyAgreement ka = KeyAgreement.getInstance(algorithm, "BC");
ka.init(privateKey);
ka.doPhase(publicKey, true);
return ka.generateSecret();
}
/**
* A simple KDF that hashes the input with SHA-256 and returns the first
* numBytes.
*/
public byte[] simpleKDF(byte[] input, int numBytes) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(input);
return Arrays.copyOf(hash, numBytes);
}
/**
* Concatenates two byte arrays.
*/
public 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;
}
// ===============================================
// 1. ECDH + AES-GCM Flows
// ===============================================
/**
* Secure hybrid encryption using ECDH and AES-GCM. Uses ephemeral key
* pairs, applies a simple KDF to derive a 128-bit AES key, and uses AES-GCM
* with a random 12-byte nonce.
*/
public byte[] secureECDH_AESGCMEncryption(byte[] plaintext) throws Exception {
KeyPair aliceKP = generateECDHKeyPair();
KeyPair bobKP = generateECDHKeyPair();
byte[] aliceSecret = deriveSharedSecret(aliceKP.getPrivate(), bobKP.getPublic(), "ECDH");
byte[] aesKeyBytes = simpleKDF(aliceSecret, 16);
SecretKey aesKey = new SecretKeySpec(aesKeyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
byte[] iv = new byte[12];
new SecureRandom().nextBytes(iv);
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.ENCRYPT_MODE, aesKey, spec);
byte[] ciphertext = cipher.doFinal(plaintext);
return concatenate(iv, ciphertext);
}
/**
* Insecure hybrid encryption using ECDH and AES-GCM. Reuses a static key
* pair, uses raw shared secret truncation without a proper KDF, and employs
* a fixed IV (all zeros).
*/
public byte[] insecureECDH_AESGCMEncryption(byte[] plaintext) throws Exception {
KeyPair staticKP = generateECDHKeyPair();
byte[] sharedSecret = deriveSharedSecret(staticKP.getPrivate(), staticKP.getPublic(), "ECDH");
byte[] aesKeyBytes = Arrays.copyOf(sharedSecret, 16);
SecretKey aesKey = new SecretKeySpec(aesKeyBytes, "AES");
byte[] fixedIV = new byte[12]; // fixed IV (all zeros)
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(128, fixedIV);
cipher.init(Cipher.ENCRYPT_MODE, aesKey, spec);
byte[] ciphertext = cipher.doFinal(plaintext);
return concatenate(fixedIV, ciphertext);
}
// ===============================================
// 2. X25519 + ChaCha20-Poly1305 Flows
// ===============================================
/**
* Secure hybrid encryption using X25519 and ChaCha20-Poly1305. Uses
* ephemeral key pairs, applies a KDF (SHA-256) to derive a 256-bit key, and
* uses ChaCha20-Poly1305 with a random 12-byte nonce.
*/
public byte[] secureX25519_Chacha20Poly1305Encryption(byte[] plaintext) throws Exception {
KeyPair aliceKP = generateX25519KeyPair();
KeyPair bobKP = generateX25519KeyPair();
byte[] sharedSecret = deriveSharedSecret(aliceKP.getPrivate(), bobKP.getPublic(), "X25519");
byte[] chachaKeyBytes = MessageDigest.getInstance("SHA-256").digest(sharedSecret);
SecretKey chachaKey = new SecretKeySpec(chachaKeyBytes, "ChaCha20");
Cipher cipher = Cipher.getInstance("ChaCha20-Poly1305", "BC");
byte[] nonce = new byte[12];
new SecureRandom().nextBytes(nonce);
cipher.init(Cipher.ENCRYPT_MODE, chachaKey, new IvParameterSpec(nonce));
byte[] ciphertext = cipher.doFinal(plaintext);
return concatenate(nonce, ciphertext);
}
/**
* Insecure hybrid encryption using X25519 and ChaCha20-Poly1305. Reuses a
* static key pair, directly truncates the shared secret without a proper
* KDF, and employs a fixed nonce.
*/
public byte[] insecureX25519_Chacha20Poly1305Encryption(byte[] plaintext) throws Exception {
KeyPair staticKP = generateX25519KeyPair();
byte[] sharedSecret = deriveSharedSecret(staticKP.getPrivate(), staticKP.getPublic(), "X25519");
byte[] chachaKeyBytes = Arrays.copyOf(sharedSecret, 32);
SecretKey chachaKey = new SecretKeySpec(chachaKeyBytes, "ChaCha20");
byte[] fixedNonce = new byte[12]; // fixed nonce (all zeros)
Cipher cipher = Cipher.getInstance("ChaCha20-Poly1305", "BC");
cipher.init(Cipher.ENCRYPT_MODE, chachaKey, new IvParameterSpec(fixedNonce));
byte[] ciphertext = cipher.doFinal(plaintext);
return concatenate(fixedNonce, ciphertext);
}
// ===============================================
// 3. Dynamic Hybrid Selection
// ===============================================
/**
* Dynamically selects a hybrid encryption flow based on a configuration
* property. If the config is unknown, defaults to an insecure flow.
*/
public String dynamicHybridEncryption(String config, byte[] plaintext) throws Exception {
byte[] result;
if ("secureECDH".equalsIgnoreCase(config)) {
result = secureECDH_AESGCMEncryption(plaintext);
} else if ("insecureECDH".equalsIgnoreCase(config)) {
result = insecureECDH_AESGCMEncryption(plaintext);
} else if ("secureX25519".equalsIgnoreCase(config)) {
result = secureX25519_Chacha20Poly1305Encryption(plaintext);
} else if ("insecureX25519".equalsIgnoreCase(config)) {
result = insecureX25519_Chacha20Poly1305Encryption(plaintext);
} else {
// Fallback to insecure ECDH flow.
result = insecureECDH_AESGCMEncryption(plaintext);
}
return Base64.getEncoder().encodeToString(result);
}
// ===============================================
// 4. Further Key Derivation from Symmetric Keys
// ===============================================
/**
* Derives two keys from a symmetric key using PBKDF2, then uses one key for
* AES-GCM encryption and the other for computing a MAC over the ciphertext.
*/
public byte[] furtherUseSymmetricKeyForKeyDerivation(SecretKey key, byte[] plaintext) throws Exception {
String keyAsString = Base64.getEncoder().encodeToString(key.getEncoded());
byte[] salt = generateSalt(16);
PBEKeySpec spec = new PBEKeySpec(keyAsString.toCharArray(), salt, 10000, 256);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] derived = factory.generateSecret(spec).getEncoded();
byte[] encKeyBytes = Arrays.copyOfRange(derived, 0, 16);
byte[] macKeyBytes = Arrays.copyOfRange(derived, 16, 32);
SecretKey encryptionKey = new SecretKeySpec(encKeyBytes, "AES");
SecretKey derivedMacKey = new SecretKeySpec(macKeyBytes, "HmacSHA256");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
byte[] iv = new byte[12];
new SecureRandom().nextBytes(iv);
GCMParameterSpec specGcm = new GCMParameterSpec(128, iv);
cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, specGcm);
byte[] ciphertext = cipher.doFinal(plaintext);
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(derivedMacKey);
byte[] computedMac = mac.doFinal(ciphertext);
byte[] output = new byte[ciphertext.length + computedMac.length];
System.arraycopy(ciphertext, 0, output, 0, ciphertext.length);
System.arraycopy(computedMac, 0, output, ciphertext.length, computedMac.length);
storeOutput(output);
return output;
}
// ===============================================
// 5. Output/Storage Methods
// ===============================================
/**
* Stores the output securely.
*/
public void storeOutput(byte[] output) {
String stored = Base64.getEncoder().encodeToString(output);
// In production, this value would be stored or transmitted securely.
}
// ===============================================
// 6. Helper Methods for Key/Nonce Generation
// ===============================================
/**
* Generates a secure 256-bit AES key.
*/
public SecretKey generateAESKey() throws Exception {
KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init(256, new SecureRandom());
return kg.generateKey();
}
/**
* Generates a random salt.
*/
private byte[] generateSalt(int length) {
byte[] salt = new byte[length];
new SecureRandom().nextBytes(salt);
return salt;
}
}