mirror of
https://github.com/github/codeql.git
synced 2026-05-02 04:05:14 +02:00
Crypto: Add jca unit tests.
This commit is contained in:
@@ -0,0 +1,339 @@
|
||||
package com.example.crypto.algorithms;
|
||||
|
||||
// import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
// import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyAgreement;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.*;
|
||||
import java.security.spec.ECGenParameterSpec;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* AsymmetricEncryptionMacHybridCryptosystem demonstrates hybrid
|
||||
* cryptosystems that combine asymmetric encryption with a MAC.
|
||||
*
|
||||
* Flows:
|
||||
* 1. RSA-OAEP + HMAC:
|
||||
* - Secure Flow: Uses 2048-bit RSA-OAEP (with SHA256andMGF1Padding) to
|
||||
* encapsulate a freshly generated AES key;
|
||||
* then encrypts using AES-GCM with a random nonce and computes HMAC-SHA256 over
|
||||
* the ciphertext.
|
||||
* - Insecure Flow: Uses 1024-bit RSA (RSA/ECB/PKCS1Padding), AES-GCM with a
|
||||
* fixed IV, and HMAC-SHA1.
|
||||
*
|
||||
* 2. ECIES + HMAC:
|
||||
* - Secure Flow: Uses ephemeral ECDH key pairs (secp256r1); derives a shared
|
||||
* secret and applies a simple KDF (SHA-256)
|
||||
* to derive a 128-bit AES key; then uses AES-GCM with a random nonce and
|
||||
* computes HMAC-SHA256.
|
||||
* - Insecure Flow: Reuses a static EC key pair, directly truncates the shared
|
||||
* secret without a proper KDF,
|
||||
* uses a fixed IV, and computes HMAC-SHA1.
|
||||
*
|
||||
* 3. Dynamic Hybrid Selection:
|
||||
* - Chooses between flows based on a configuration string.
|
||||
*
|
||||
* SAST/CBOM Notes:
|
||||
* - Secure flows use proper ephemeral key generation, secure key sizes, KDF
|
||||
* usage, and random nonces/IVs.
|
||||
* - Insecure flows (static key reuse, fixed nonces, weak key sizes, raw shared
|
||||
* secret truncation, and deprecated algorithms)
|
||||
* should be flagged.
|
||||
*/
|
||||
public class AsymmetricEncryptionMacHybridCryptosystem {
|
||||
|
||||
// static {
|
||||
// Security.addProvider(new BouncyCastleProvider());
|
||||
// Security.addProvider(new BouncyCastlePQCProvider());
|
||||
// }
|
||||
|
||||
// ---------- Result Class ----------
|
||||
public static class HybridResult {
|
||||
private final byte[] encapsulatedKey;
|
||||
private final byte[] ciphertext;
|
||||
private final byte[] mac;
|
||||
|
||||
public HybridResult(byte[] encapsulatedKey, byte[] ciphertext, byte[] mac) {
|
||||
this.encapsulatedKey = encapsulatedKey;
|
||||
this.ciphertext = ciphertext;
|
||||
this.mac = mac;
|
||||
}
|
||||
|
||||
public byte[] getEncapsulatedKey() {
|
||||
return encapsulatedKey;
|
||||
}
|
||||
|
||||
public byte[] getCiphertext() {
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
public byte[] getMac() {
|
||||
return mac;
|
||||
}
|
||||
|
||||
public String toBase64String() {
|
||||
return "EncapsulatedKey: " + Base64.getEncoder().encodeToString(encapsulatedKey) +
|
||||
"\nCiphertext: " + Base64.getEncoder().encodeToString(ciphertext) +
|
||||
"\nMAC: " + Base64.getEncoder().encodeToString(mac);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 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.
|
||||
*
|
||||
* @param privateKey The private key.
|
||||
* @param publicKey The corresponding public key.
|
||||
* @param algorithm The key agreement algorithm (e.g., "ECDH" or "X25519").
|
||||
* @return The shared secret.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param input The input byte array.
|
||||
* @param numBytes The desired number of output bytes.
|
||||
* @return The derived key material.
|
||||
*/
|
||||
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. RSA-OAEP + HMAC Hybrid Cryptosystem
|
||||
// =====================================================
|
||||
|
||||
/**
|
||||
* Generates a secure 2048-bit RSA key pair.
|
||||
*/
|
||||
public KeyPair generateRSAKeyPairGood() throws Exception {
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
|
||||
kpg.initialize(2048);
|
||||
return kpg.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an insecure 1024-bit RSA key pair.
|
||||
*/
|
||||
public KeyPair generateRSAKeyPairBad() throws Exception {
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
|
||||
kpg.initialize(1024);
|
||||
return kpg.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Secure hybrid encryption using RSA-OAEP + HMAC-SHA256.
|
||||
*/
|
||||
public HybridResult secureRSAHybridEncryption(byte[] plaintext) throws Exception {
|
||||
KeyPair rsaKP = generateRSAKeyPairGood();
|
||||
SecretKey aesKey = generateAESKey();
|
||||
|
||||
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
|
||||
rsaCipher.init(Cipher.WRAP_MODE, rsaKP.getPublic());
|
||||
byte[] encapsulatedKey = rsaCipher.wrap(aesKey);
|
||||
|
||||
byte[] iv = new byte[12];
|
||||
new SecureRandom().nextBytes(iv);
|
||||
Cipher aesCipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey, new GCMParameterSpec(128, iv));
|
||||
byte[] ciphertext = aesCipher.doFinal(plaintext);
|
||||
byte[] fullCiphertext = concatenate(iv, ciphertext);
|
||||
|
||||
byte[] macKey = generateAESKey().getEncoded();
|
||||
byte[] mac = secureHMACSHA256(new String(fullCiphertext), macKey);
|
||||
|
||||
return new HybridResult(encapsulatedKey, fullCiphertext, mac);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insecure hybrid encryption using RSA/ECB/PKCS1Padding + HMAC-SHA1.
|
||||
*/
|
||||
public HybridResult insecureRSAHybridEncryption(byte[] plaintext) throws Exception {
|
||||
KeyPair rsaKP = generateRSAKeyPairBad();
|
||||
SecretKey aesKey = generateAESKey();
|
||||
|
||||
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
|
||||
rsaCipher.init(Cipher.WRAP_MODE, rsaKP.getPublic());
|
||||
byte[] encapsulatedKey = rsaCipher.wrap(aesKey);
|
||||
|
||||
byte[] fixedIV = new byte[12]; // All zeros
|
||||
Cipher aesCipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey, new GCMParameterSpec(128, fixedIV));
|
||||
byte[] ciphertext = aesCipher.doFinal(plaintext);
|
||||
byte[] fullCiphertext = concatenate(fixedIV, ciphertext);
|
||||
|
||||
byte[] macKey = generateAESKey().getEncoded();
|
||||
byte[] mac = insecureHMACSHA1(new String(fullCiphertext), macKey);
|
||||
|
||||
return new HybridResult(encapsulatedKey, fullCiphertext, mac);
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 2. ECIES + HMAC Hybrid Cryptosystem
|
||||
// =====================================================
|
||||
|
||||
/**
|
||||
* Secure hybrid encryption using ECIES (via ECDH) + HMAC-SHA256.
|
||||
*/
|
||||
public HybridResult secureECIESHybridEncryption(byte[] plaintext) throws Exception {
|
||||
KeyPair aliceKP = generateECDHKeyPair();
|
||||
KeyPair bobKP = generateECDHKeyPair();
|
||||
byte[] sharedSecret = deriveSharedSecret(aliceKP.getPrivate(), bobKP.getPublic(), "ECDH");
|
||||
byte[] aesKeyBytes = simpleKDF(sharedSecret, 16);
|
||||
SecretKey aesKey = new SecretKeySpec(aesKeyBytes, "AES");
|
||||
|
||||
byte[] iv = new byte[12];
|
||||
new SecureRandom().nextBytes(iv);
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, aesKey, new GCMParameterSpec(128, iv));
|
||||
byte[] ciphertext = cipher.doFinal(plaintext);
|
||||
byte[] fullCiphertext = concatenate(iv, ciphertext);
|
||||
|
||||
byte[] macKey = generateAESKey().getEncoded();
|
||||
byte[] mac = secureHMACSHA256(new String(fullCiphertext), macKey);
|
||||
|
||||
byte[] ephemeralPubKey = aliceKP.getPublic().getEncoded();
|
||||
|
||||
return new HybridResult(ephemeralPubKey, fullCiphertext, mac);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insecure hybrid encryption using ECIES (via ECDH) + HMAC-SHA1.
|
||||
*/
|
||||
public HybridResult insecureECIESHybridEncryption(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
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, aesKey, new GCMParameterSpec(128, fixedIV));
|
||||
byte[] ciphertext = cipher.doFinal(plaintext);
|
||||
byte[] fullCiphertext = concatenate(fixedIV, ciphertext);
|
||||
|
||||
byte[] macKey = generateAESKey().getEncoded();
|
||||
byte[] mac = insecureHMACSHA1(new String(fullCiphertext), macKey);
|
||||
|
||||
byte[] staticPubKey = staticKP.getPublic().getEncoded();
|
||||
|
||||
return new HybridResult(staticPubKey, fullCiphertext, mac);
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 3. Dynamic Hybrid Selection
|
||||
// =====================================================
|
||||
|
||||
/**
|
||||
* Dynamically selects a hybrid encryption flow based on configuration.
|
||||
* SAST: Dynamic selection introduces risk if insecure defaults are chosen.
|
||||
*
|
||||
* @param config The configuration string ("secureRSA", "insecureRSA",
|
||||
* "secureECIES", "insecureECIES").
|
||||
* @param plaintext The plaintext to encrypt.
|
||||
* @return A Base64-encoded string representation of the hybrid encryption
|
||||
* result.
|
||||
* @throws Exception if an error occurs.
|
||||
*/
|
||||
public String dynamicHybridEncryption(String config, byte[] plaintext) throws Exception {
|
||||
HybridResult result;
|
||||
if ("secureRSA".equalsIgnoreCase(config)) {
|
||||
result = secureRSAHybridEncryption(plaintext);
|
||||
} else if ("insecureRSA".equalsIgnoreCase(config)) {
|
||||
result = insecureRSAHybridEncryption(plaintext);
|
||||
} else if ("secureECIES".equalsIgnoreCase(config)) {
|
||||
result = secureECIESHybridEncryption(plaintext);
|
||||
} else if ("insecureECIES".equalsIgnoreCase(config)) {
|
||||
result = insecureECIESHybridEncryption(plaintext);
|
||||
} else {
|
||||
// Fallback to insecure RSA hybrid encryption.
|
||||
result = insecureRSAHybridEncryption(plaintext);
|
||||
}
|
||||
return result.toBase64String();
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 4. Helper Methods for HMAC and Symmetric Encryption
|
||||
// =====================================================
|
||||
|
||||
/**
|
||||
* Secure HMAC using HMAC-SHA256.
|
||||
* SAST: HMAC-SHA256 is secure.
|
||||
*/
|
||||
public byte[] secureHMACSHA256(String message, byte[] key) throws Exception {
|
||||
Mac mac = Mac.getInstance("HmacSHA256", "BC");
|
||||
SecretKey secretKey = new SecretKeySpec(key, "HmacSHA256");
|
||||
mac.init(secretKey);
|
||||
return mac.doFinal(message.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Insecure HMAC using HMAC-SHA1.
|
||||
* SAST: HMAC-SHA1 is deprecated and insecure.
|
||||
*/
|
||||
public byte[] insecureHMACSHA1(String message, byte[] key) throws Exception {
|
||||
Mac mac = Mac.getInstance("HmacSHA1", "BC");
|
||||
SecretKey secretKey = new SecretKeySpec(key, "HmacSHA1");
|
||||
mac.init(secretKey);
|
||||
return mac.doFinal(message.getBytes());
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// 5. Helper Methods for Key/Nonce Generation
|
||||
// =====================================================
|
||||
|
||||
/**
|
||||
* Generates a secure 256-bit AES key.
|
||||
* SAST: Uses SecureRandom for key generation.
|
||||
*/
|
||||
public SecretKey generateAESKey() throws Exception {
|
||||
KeyGenerator kg = KeyGenerator.getInstance("AES");
|
||||
kg.init(256, new SecureRandom());
|
||||
return kg.generateKey();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user