mirror of
https://github.com/github/codeql.git
synced 2025-12-17 01:03:14 +01:00
349 lines
14 KiB
Java
349 lines
14 KiB
Java
package com.example.crypto.algorithms;
|
|
|
|
// import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
|
import java.security.*;
|
|
|
|
import javax.crypto.Cipher;
|
|
import javax.crypto.KeyGenerator;
|
|
import javax.crypto.Mac;
|
|
import javax.crypto.SecretKey;
|
|
import javax.crypto.spec.GCMParameterSpec;
|
|
import javax.crypto.spec.IvParameterSpec;
|
|
import javax.crypto.spec.SecretKeySpec;
|
|
import javax.crypto.SecretKeyFactory;
|
|
import javax.crypto.spec.PBEKeySpec;
|
|
import java.security.SecureRandom;
|
|
import java.util.Arrays;
|
|
import java.util.Base64;
|
|
|
|
/**
|
|
* SymmetricAlgorithmTest demonstrates various symmetric encryption flows and
|
|
* key derivation scenarios that can be analyzed by SAST tools.
|
|
*
|
|
* It includes: 1) AES-GCM encryption with random nonce (secure). 2) AES-GCM
|
|
* encryption with fixed nonce (insecure). 3) AES-CBC encryption with random IV
|
|
* (secure). 4) AES-ECB encryption (insecure). 5) RC4 encryption (insecure). 6)
|
|
* DES and TripleDES encryption (insecure/weak). 7) ChaCha20 encryption (secure,
|
|
* if available). 8) KMAC-based key derivation used to derive a key for AES
|
|
* encryption. 9) Dynamic symmetric encryption selection based on configuration.
|
|
* 10) Further use: deriving two keys from symmetric key material via PBKDF2.
|
|
*
|
|
* SAST/CBOM notes: - Nonce/IV reuse (e.g., fixed nonce) must be flagged. -
|
|
* Insecure algorithms (RC4, DES, TripleDES, AES/ECB) are marked as unsafe. -
|
|
* Dynamic selection may lead to insecure fallback if misconfigured.
|
|
*/
|
|
public class SymmetricAlgorithm {
|
|
|
|
// static {
|
|
// Security.addProvider(new BouncyCastleProvider());
|
|
// }
|
|
// ---------- Secure Symmetric Encryption Flows ----------
|
|
/**
|
|
* AES-GCM encryption using a 12-byte random nonce. SAST: AES-GCM is secure
|
|
* when a unique nonce is used per encryption.
|
|
*
|
|
* @param key The AES key.
|
|
* @param plaintext The plaintext to encrypt.
|
|
* @return The IV prepended to the ciphertext.
|
|
* @throws Exception if encryption fails.
|
|
*/
|
|
public byte[] aesGcmEncryptSafe(SecretKey key, byte[] plaintext) throws Exception {
|
|
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
|
byte[] iv = new byte[12]; // Recommended 12-byte nonce for GCM.
|
|
new SecureRandom().nextBytes(iv);
|
|
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
|
|
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
|
|
byte[] ciphertext = cipher.doFinal(plaintext);
|
|
byte[] output = new byte[iv.length + ciphertext.length];
|
|
System.arraycopy(iv, 0, output, 0, iv.length);
|
|
System.arraycopy(ciphertext, 0, output, iv.length, ciphertext.length);
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
* AES-GCM encryption using a fixed (constant) nonce. SAST: Fixed nonce
|
|
* reuse in AES-GCM is insecure as it destroys confidentiality.
|
|
*
|
|
* @param key The AES key.
|
|
* @param plaintext The plaintext to encrypt.
|
|
* @return The fixed IV prepended to the ciphertext.
|
|
* @throws Exception if encryption fails.
|
|
*/
|
|
public byte[] aesGcmEncryptUnsafe(SecretKey key, byte[] plaintext) throws Exception {
|
|
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
|
byte[] iv = new byte[12]; // Fixed IV (all zeros by default) - insecure.
|
|
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
|
|
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
|
|
byte[] ciphertext = cipher.doFinal(plaintext);
|
|
byte[] output = new byte[iv.length + ciphertext.length];
|
|
System.arraycopy(iv, 0, output, 0, iv.length);
|
|
System.arraycopy(ciphertext, 0, output, iv.length, ciphertext.length);
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
* AES-CBC encryption using a random IV. SAST: AES-CBC is secure if IVs are
|
|
* random and not reused.
|
|
*
|
|
* @param key The AES key.
|
|
* @param plaintext The plaintext to encrypt.
|
|
* @return The IV prepended to the ciphertext.
|
|
* @throws Exception if encryption fails.
|
|
*/
|
|
public byte[] aesCbcEncryptSafe(SecretKey key, byte[] plaintext) throws Exception {
|
|
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
|
byte[] iv = new byte[16]; // 16-byte IV for AES block size.
|
|
new SecureRandom().nextBytes(iv);
|
|
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
|
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
|
|
byte[] ciphertext = cipher.doFinal(plaintext);
|
|
byte[] output = new byte[iv.length + ciphertext.length];
|
|
System.arraycopy(iv, 0, output, 0, iv.length);
|
|
System.arraycopy(ciphertext, 0, output, iv.length, ciphertext.length);
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
* AES-ECB encryption. SAST: ECB mode is insecure as it does not use an IV,
|
|
* revealing data patterns.
|
|
*
|
|
* @param key The AES key.
|
|
* @param plaintext The plaintext to encrypt.
|
|
* @return The ciphertext.
|
|
* @throws Exception if encryption fails.
|
|
*/
|
|
public byte[] aesEcbEncryptUnsafe(SecretKey key, byte[] plaintext) throws Exception {
|
|
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
|
|
cipher.init(Cipher.ENCRYPT_MODE, key);
|
|
return cipher.doFinal(plaintext);
|
|
}
|
|
|
|
// ---------- Other Symmetric Algorithms ----------
|
|
/**
|
|
* RC4 encryption. SAST: RC4 is deprecated due to vulnerabilities.
|
|
*
|
|
* @param key The RC4 key.
|
|
* @param plaintext The plaintext to encrypt.
|
|
* @return The ciphertext.
|
|
* @throws Exception if encryption fails.
|
|
*/
|
|
public byte[] rc4EncryptUnsafe(SecretKey key, byte[] plaintext) throws Exception {
|
|
Cipher cipher = Cipher.getInstance("RC4");
|
|
cipher.init(Cipher.ENCRYPT_MODE, key);
|
|
return cipher.doFinal(plaintext);
|
|
}
|
|
|
|
/**
|
|
* DES encryption. SAST: DES is insecure due to its 56-bit effective key
|
|
* size.
|
|
*
|
|
* @param key The DES key.
|
|
* @param plaintext The plaintext to encrypt.
|
|
* @return The IV prepended to the ciphertext.
|
|
* @throws Exception if encryption fails.
|
|
*/
|
|
public byte[] desEncryptUnsafe(SecretKey key, byte[] plaintext) throws Exception {
|
|
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
|
|
byte[] iv = new byte[8];
|
|
new SecureRandom().nextBytes(iv);
|
|
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
|
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
|
|
byte[] ciphertext = cipher.doFinal(plaintext);
|
|
byte[] output = new byte[iv.length + ciphertext.length];
|
|
System.arraycopy(iv, 0, output, 0, iv.length);
|
|
System.arraycopy(ciphertext, 0, output, iv.length, ciphertext.length);
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
* TripleDES (DESede) encryption. SAST: TripleDES is weak by modern
|
|
* standards and is deprecated.
|
|
*
|
|
* @param key The TripleDES key.
|
|
* @param plaintext The plaintext to encrypt.
|
|
* @return The IV prepended to the ciphertext.
|
|
* @throws Exception if encryption fails.
|
|
*/
|
|
public byte[] tripleDesEncryptUnsafe(SecretKey key, byte[] plaintext) throws Exception {
|
|
Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
|
|
byte[] iv = new byte[8];
|
|
new SecureRandom().nextBytes(iv);
|
|
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
|
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
|
|
byte[] ciphertext = cipher.doFinal(plaintext);
|
|
byte[] output = new byte[iv.length + ciphertext.length];
|
|
System.arraycopy(iv, 0, output, 0, iv.length);
|
|
System.arraycopy(ciphertext, 0, output, iv.length, ciphertext.length);
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
* ChaCha20 encryption. SAST: ChaCha20 is considered secure and is a modern
|
|
* alternative to AES.
|
|
*
|
|
* @param key The ChaCha20 key.
|
|
* @param plaintext The plaintext to encrypt.
|
|
* @return The nonce prepended to the ciphertext.
|
|
* @throws Exception if encryption fails.
|
|
*/
|
|
public byte[] chacha20EncryptSafe(SecretKey key, byte[] plaintext) throws Exception {
|
|
Cipher cipher = Cipher.getInstance("ChaCha20", "BC");
|
|
byte[] nonce = new byte[12]; // ChaCha20 typically uses a 12-byte nonce.
|
|
new SecureRandom().nextBytes(nonce);
|
|
// ChaCha20 may require an IvParameterSpec for the nonce.
|
|
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(nonce));
|
|
byte[] ciphertext = cipher.doFinal(plaintext);
|
|
byte[] output = new byte[nonce.length + ciphertext.length];
|
|
System.arraycopy(nonce, 0, output, 0, nonce.length);
|
|
System.arraycopy(ciphertext, 0, output, nonce.length, ciphertext.length);
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
* KMAC-based flow: Uses KMAC128 to derive key material for AES encryption.
|
|
* SAST: KMAC128 is secure as part of the SHA-3 family when used correctly.
|
|
*
|
|
* @param key The KMAC key.
|
|
* @param plaintext The plaintext to encrypt.
|
|
* @return The ciphertext (with IV) resulting from encryption with a derived
|
|
* key.
|
|
* @throws Exception if encryption fails.
|
|
*/
|
|
public byte[] kmacEncryptFlow(SecretKey key, byte[] plaintext) throws Exception {
|
|
Mac kmac = Mac.getInstance("KMAC128", "BC");
|
|
kmac.init(key);
|
|
byte[] kmacOutput = kmac.doFinal(plaintext);
|
|
// Use the first 16 bytes of KMAC output as an AES key.
|
|
SecretKey derivedKey = new SecretKeySpec(kmacOutput, 0, 16, "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, derivedKey, spec);
|
|
byte[] ciphertext = cipher.doFinal(plaintext);
|
|
byte[] output = new byte[iv.length + ciphertext.length];
|
|
System.arraycopy(iv, 0, output, 0, iv.length);
|
|
System.arraycopy(ciphertext, 0, output, iv.length, ciphertext.length);
|
|
return output;
|
|
}
|
|
|
|
// ---------- Dynamic Algorithm Selection ----------
|
|
/**
|
|
* Dynamically selects a symmetric encryption algorithm based on a
|
|
* configuration property. If the algorithm is unknown or ambiguous, falls
|
|
* back to an insecure default (AES/ECB).
|
|
*
|
|
* SAST: Dynamic selection introduces a known unknown risk.
|
|
*
|
|
* @param algorithm The algorithm name from configuration.
|
|
* @param key The symmetric key.
|
|
* @param plaintext The plaintext to encrypt.
|
|
* @return The ciphertext.
|
|
* @throws Exception if encryption fails.
|
|
*/
|
|
public byte[] dynamicSymmetricEncryption(String algorithm, SecretKey key, byte[] plaintext) throws Exception {
|
|
if ("AES/GCM/NoPadding".equalsIgnoreCase(algorithm)) {
|
|
return aesGcmEncryptSafe(key, plaintext);
|
|
} else if ("AES/CBC/PKCS5Padding".equalsIgnoreCase(algorithm)) {
|
|
return aesCbcEncryptSafe(key, plaintext);
|
|
} else if ("AES/ECB/PKCS5Padding".equalsIgnoreCase(algorithm)) {
|
|
return aesEcbEncryptUnsafe(key, plaintext);
|
|
} else if ("RC4".equalsIgnoreCase(algorithm)) {
|
|
return rc4EncryptUnsafe(key, plaintext);
|
|
} else if ("ChaCha20".equalsIgnoreCase(algorithm)) {
|
|
return chacha20EncryptSafe(key, plaintext);
|
|
} else {
|
|
// Unknown algorithm: fallback to insecure AES/ECB.
|
|
return aesEcbEncryptUnsafe(key, plaintext);
|
|
}
|
|
}
|
|
|
|
// ---------- Further Use of Symmetric Keys ----------
|
|
/**
|
|
* Derives a key from an input key by simple truncation. SAST: This approach
|
|
* is ambiguous; a proper KDF should be used.
|
|
*
|
|
* @param key The input symmetric key.
|
|
* @return A derived 128-bit key.
|
|
*/
|
|
public byte[] deriveKeyFromKey(SecretKey key) {
|
|
byte[] keyBytes = key.getEncoded();
|
|
return Arrays.copyOf(keyBytes, 16);
|
|
}
|
|
|
|
/**
|
|
* Further use: Derives two separate keys from a symmetric key using PBKDF2,
|
|
* then uses one key for encryption and one for MACing ciphertext. SAST:
|
|
* This key-splitting approach is acceptable if PBKDF2 is used securely.
|
|
*
|
|
* @param key The input key material.
|
|
* @param plaintext The plaintext to encrypt.
|
|
* @return The concatenated ciphertext and its MAC.
|
|
* @throws Exception if key derivation or encryption fails.
|
|
*/
|
|
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 encKey = 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);
|
|
cipher.init(Cipher.ENCRYPT_MODE, encKey, new GCMParameterSpec(128, iv));
|
|
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);
|
|
storeEncryptedOutput(output);
|
|
return output;
|
|
}
|
|
|
|
/**
|
|
* Stores the encrypted output. SAST: In production, secure
|
|
* storage/transmission is required.
|
|
*
|
|
* @param output The output to store.
|
|
*/
|
|
public void storeEncryptedOutput(byte[] output) {
|
|
String stored = Base64.getEncoder().encodeToString(output);
|
|
}
|
|
|
|
// ---------- Helper Methods ----------
|
|
/**
|
|
* Generates a secure 256-bit AES key. SAST: Uses a strong RNG for key
|
|
* generation.
|
|
*
|
|
* @return A new AES SecretKey.
|
|
* @throws Exception if key generation fails.
|
|
*/
|
|
public SecretKey generateAESKey() throws Exception {
|
|
KeyGenerator kg = KeyGenerator.getInstance("AES");
|
|
kg.init(256);
|
|
return kg.generateKey();
|
|
}
|
|
|
|
/**
|
|
* Generates a random salt of the specified length using SecureRandom. SAST:
|
|
* Salting is essential for secure key derivation.
|
|
*
|
|
* @param length The salt length.
|
|
* @return A byte array representing the salt.
|
|
*/
|
|
private byte[] generateSalt(int length) {
|
|
byte[] salt = new byte[length];
|
|
new SecureRandom().nextBytes(salt);
|
|
return salt;
|
|
}
|
|
}
|