mirror of
https://github.com/github/codeql.git
synced 2025-12-16 16:53:25 +01:00
Merge pull request #20583 from bdrodes/jca_signature_extensions
Crypto: Add JCA signatures, RNG, and unit tests
This commit is contained in:
@@ -18,6 +18,8 @@ module JCAModel {
|
||||
|
||||
abstract class KeyAgreementAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { }
|
||||
|
||||
abstract class SignatureAlgorithmValueConsumer extends Crypto::AlgorithmValueConsumer { }
|
||||
|
||||
// TODO: Verify that the PBEWith% case works correctly
|
||||
bindingset[algo]
|
||||
predicate cipher_names(string algo) {
|
||||
@@ -100,9 +102,21 @@ module JCAModel {
|
||||
].toUpperCase())
|
||||
}
|
||||
|
||||
/**
|
||||
* Names that match known signature algorithms.
|
||||
* https://docs.oracle.com/en/java/javase/25/docs/specs/security/standard-names.html
|
||||
*/
|
||||
bindingset[name]
|
||||
predicate signature_names(string name) {
|
||||
name.toUpperCase().splitAt("WITH", 1).matches(["RSA%", "ECDSA%", "DSA%"])
|
||||
or
|
||||
name.toUpperCase().matches(["RSASSA-PSS", "ED25519", "ED448", "EDDSA", "ML-DSA%", "HSS/LMS"])
|
||||
}
|
||||
|
||||
bindingset[name]
|
||||
predicate key_agreement_names(string name) {
|
||||
name.toUpperCase().matches(["DH", "EDH", "ECDH", "X25519", "X448"].toUpperCase())
|
||||
name.toUpperCase()
|
||||
.matches(["DH", "EDH", "ECDH", "X25519", "X448", "ML-KEM%", "XDH"].toUpperCase())
|
||||
}
|
||||
|
||||
bindingset[name]
|
||||
@@ -208,13 +222,46 @@ module JCAModel {
|
||||
bindingset[name]
|
||||
predicate key_agreement_name_to_type_known(Crypto::TKeyAgreementType type, string name) {
|
||||
type = Crypto::DH() and
|
||||
name.toUpperCase() = "DH"
|
||||
name.toUpperCase() in ["DH", "XDH"]
|
||||
or
|
||||
type = Crypto::EDH() and
|
||||
name.toUpperCase() = "EDH"
|
||||
or
|
||||
type = Crypto::ECDH() and
|
||||
name.toUpperCase() in ["ECDH", "X25519", "X448"]
|
||||
or
|
||||
type = Crypto::OtherKeyAgreementType() and
|
||||
name.toUpperCase().matches("ML-KEM%")
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a signature algorithm name to its type, if known.
|
||||
* see https://docs.oracle.com/en/java/javase/25/docs/specs/security/standard-names.html
|
||||
*/
|
||||
bindingset[name]
|
||||
predicate signature_name_to_type_known(Crypto::KeyOpAlg::TAlgorithm type, string name) {
|
||||
name.toUpperCase().splitAt("with".toUpperCase(), 1).matches("RSA%") and
|
||||
type = KeyOpAlg::TAsymmetricCipher(KeyOpAlg::RSA())
|
||||
or
|
||||
name.toUpperCase().splitAt("with".toUpperCase(), 1).matches("ECDSA%") and
|
||||
type = KeyOpAlg::TSignature(KeyOpAlg::ECDSA())
|
||||
or
|
||||
name.toUpperCase().splitAt("with".toUpperCase(), 1).matches("DSA%") and
|
||||
type = KeyOpAlg::TSignature(KeyOpAlg::DSA())
|
||||
or
|
||||
name.toUpperCase() = "RSASSA-PSS" and type = KeyOpAlg::TAsymmetricCipher(KeyOpAlg::RSA())
|
||||
or
|
||||
name.toUpperCase().matches(["EDDSA", "ED25519", "ED448"]) and
|
||||
type = KeyOpAlg::TSignature(KeyOpAlg::EDDSA())
|
||||
or
|
||||
name.toUpperCase().matches("ML-DSA%") and type = KeyOpAlg::TSignature(KeyOpAlg::DSA())
|
||||
or
|
||||
name.toUpperCase() = "HSS/LMS" and type = KeyOpAlg::TSignature(KeyOpAlg::HSS_LMS())
|
||||
}
|
||||
|
||||
bindingset[name]
|
||||
Crypto::HashType signature_name_to_hash_type_known(string name, int digestLength) {
|
||||
result = hash_name_to_type_known(name.splitAt("with", 0), digestLength)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -345,7 +392,7 @@ module JCAModel {
|
||||
override KeyOpAlg::AlgorithmType getAlgorithmType() {
|
||||
if cipher_name_to_type_known(_, super.getAlgorithmName())
|
||||
then cipher_name_to_type_known(result, super.getAlgorithmName())
|
||||
else result instanceof KeyOpAlg::TUnknownKeyOperationAlgorithmType
|
||||
else result instanceof KeyOpAlg::TOtherKeyOperationAlgorithmType
|
||||
}
|
||||
|
||||
override int getKeySizeFixed() {
|
||||
@@ -999,7 +1046,8 @@ module JCAModel {
|
||||
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
|
||||
result.(CipherStringLiteralAlgorithmInstance).getConsumer() = this or
|
||||
result.(KeyAgreementStringLiteralAlgorithmInstance).getConsumer() = this or
|
||||
result.(EllipticCurveStringLiteralInstance).getConsumer() = this
|
||||
result.(EllipticCurveStringLiteralInstance).getConsumer() = this or
|
||||
result.(SignatureStringLiteralAlgorithmInstance).getConsumer() = this
|
||||
}
|
||||
|
||||
KeyGeneratorGetInstanceCall getInstantiationCall() { result = instantiationCall }
|
||||
@@ -1047,6 +1095,21 @@ module JCAModel {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An instance of `java.security.SecureRandom.nextBytes(byte[])` call.
|
||||
* This is already generally modeled for Java in CodeQL, but
|
||||
* we model it again as part of the crypto API model to have a cohesive model.
|
||||
*/
|
||||
class JavaSecuritySecureRandom extends Crypto::RandomNumberGenerationInstance instanceof Call {
|
||||
JavaSecuritySecureRandom() {
|
||||
this.getCallee().hasQualifiedName("java.security", "SecureRandom", "nextBytes")
|
||||
}
|
||||
|
||||
override Crypto::DataFlowNode getOutputNode() { result.asExpr() = this.(Call).getArgument(0) }
|
||||
|
||||
override string getGeneratorName() { result = this.(Call).getCallee().getName() }
|
||||
}
|
||||
|
||||
class KeyGeneratorGenerateCall extends Crypto::KeyGenerationOperationInstance instanceof MethodCall
|
||||
{
|
||||
Crypto::KeyArtifactType type;
|
||||
@@ -1624,6 +1687,196 @@ module JCAModel {
|
||||
override Crypto::ConsumerInputDataFlowNode getNonceConsumer() { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Signatures
|
||||
*/
|
||||
module SignatureKnownAlgorithmToConsumerConfig implements DataFlow::ConfigSig {
|
||||
predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SignatureStringLiteral }
|
||||
|
||||
predicate isSink(DataFlow::Node sink) {
|
||||
sink = any(SignatureAlgorithmValueConsumer call).getInputNode()
|
||||
}
|
||||
}
|
||||
|
||||
module SignatureKnownAlgorithmToConsumerFlow =
|
||||
TaintTracking::Global<SignatureKnownAlgorithmToConsumerConfig>;
|
||||
|
||||
class SignatureGetInstanceCall extends MethodCall {
|
||||
SignatureGetInstanceCall() {
|
||||
this.getCallee().hasQualifiedName("java.security", "Signature", "getInstance")
|
||||
}
|
||||
|
||||
Expr getAlgorithmArg() { result = this.getArgument(0) }
|
||||
}
|
||||
|
||||
class SignatureGetInstanceAlgorithmValueConsumer extends SignatureAlgorithmValueConsumer instanceof Expr
|
||||
{
|
||||
SignatureGetInstanceAlgorithmValueConsumer() {
|
||||
this = any(SignatureGetInstanceCall c).getAlgorithmArg()
|
||||
}
|
||||
|
||||
override Crypto::ConsumerInputDataFlowNode getInputNode() { result.asExpr() = this }
|
||||
|
||||
override Crypto::AlgorithmInstance getAKnownAlgorithmSource() {
|
||||
result.(SignatureStringLiteralAlgorithmInstance).getConsumer() = this
|
||||
}
|
||||
}
|
||||
|
||||
class SignatureStringLiteral extends StringLiteral {
|
||||
SignatureStringLiteral() { signature_names(this.getValue()) }
|
||||
}
|
||||
|
||||
class SignatureStringLiteralAlgorithmInstance extends Crypto::KeyOperationAlgorithmInstance instanceof SignatureStringLiteral
|
||||
{
|
||||
SignatureAlgorithmValueConsumer consumer;
|
||||
|
||||
SignatureStringLiteralAlgorithmInstance() {
|
||||
SignatureKnownAlgorithmToConsumerFlow::flow(DataFlow::exprNode(this), consumer.getInputNode())
|
||||
}
|
||||
|
||||
SignatureAlgorithmValueConsumer getConsumer() { result = consumer }
|
||||
|
||||
override string getRawAlgorithmName() { result = super.getValue() }
|
||||
|
||||
override Crypto::KeyOpAlg::AlgorithmType getAlgorithmType() {
|
||||
if signature_name_to_type_known(_, super.getValue())
|
||||
then signature_name_to_type_known(result, super.getValue())
|
||||
else result = Crypto::KeyOpAlg::TOtherKeyOperationAlgorithmType()
|
||||
}
|
||||
|
||||
override Crypto::ConsumerInputDataFlowNode getKeySizeConsumer() {
|
||||
// TODO: trace to any key size initializer?
|
||||
none()
|
||||
}
|
||||
|
||||
override int getKeySizeFixed() {
|
||||
// TODO: are there known fixed key sizes to consider?
|
||||
none()
|
||||
}
|
||||
|
||||
override Crypto::ModeOfOperationAlgorithmInstance getModeOfOperationAlgorithm() { none() }
|
||||
|
||||
override Crypto::PaddingAlgorithmInstance getPaddingAlgorithm() { none() }
|
||||
}
|
||||
|
||||
class SignatureHashAlgorithmInstance extends Crypto::HashAlgorithmInstance instanceof SignatureStringLiteralAlgorithmInstance
|
||||
{
|
||||
Crypto::THashType hashType;
|
||||
int digestLength;
|
||||
|
||||
SignatureHashAlgorithmInstance() {
|
||||
hashType = signature_name_to_hash_type_known(this.(StringLiteral).getValue(), digestLength)
|
||||
}
|
||||
|
||||
override string getRawHashAlgorithmName() { result = this.(StringLiteral).getValue() }
|
||||
|
||||
override Crypto::THashType getHashFamily() { result = hashType }
|
||||
|
||||
override int getFixedDigestLength() { result = digestLength }
|
||||
}
|
||||
|
||||
class SignatureInitCall extends MethodCall {
|
||||
SignatureInitCall() {
|
||||
this.getCallee().hasQualifiedName("java.security", "Signature", ["initSign", "initVerify"])
|
||||
}
|
||||
|
||||
Expr getKeyArg() {
|
||||
result = this.getArgument(0)
|
||||
// TODO: verify can take in a certificate too?
|
||||
}
|
||||
}
|
||||
|
||||
private class SignatureOperationCall extends MethodCall {
|
||||
SignatureOperationCall() {
|
||||
this.getMethod().hasQualifiedName("java.security", "Signature", ["update", "sign", "verify"])
|
||||
}
|
||||
|
||||
predicate isIntermediate() { super.getMethod().getName() = "update" }
|
||||
|
||||
Expr getMsgInput() { result = this.getArgument(0) and this.getMethod().getName() = "update" }
|
||||
|
||||
Expr getSignatureOutput() {
|
||||
// no args, the signature is returned
|
||||
result = this and this.getMethod().getName() = "sign" and not exists(this.getArgument(0))
|
||||
or
|
||||
// with args, the signature is written to the arg
|
||||
result = this.getArgument(0) and this.getMethod().getName() = "sign"
|
||||
}
|
||||
|
||||
Expr getSignatureInput() {
|
||||
result = this.getArgument(0) and this.getMethod().getName() = "verify"
|
||||
}
|
||||
|
||||
Crypto::KeyOperationSubtype getSubtype() {
|
||||
result instanceof Crypto::TSignMode and this.getMethod().getName() = "sign"
|
||||
or
|
||||
result instanceof Crypto::TVerifyMode and this.getMethod().getName() = "verify"
|
||||
}
|
||||
}
|
||||
|
||||
class SignatureOperationInstance extends Crypto::SignatureOperationInstance instanceof SignatureOperationCall
|
||||
{
|
||||
SignatureOperationInstance() {
|
||||
// exclude update (only include sign and verify)
|
||||
not super.isIntermediate()
|
||||
}
|
||||
|
||||
SignatureGetInstanceCall getInstantiationCall() {
|
||||
result = SignatureFlowAnalysisImpl::getInstantiationFromUse(this, _, _)
|
||||
}
|
||||
|
||||
SignatureInitCall getInitCall() {
|
||||
result = SignatureFlowAnalysisImpl::getInitFromUse(this, _, _)
|
||||
}
|
||||
|
||||
override Crypto::ConsumerInputDataFlowNode getInputConsumer() {
|
||||
result.asExpr() = super.getMsgInput() or
|
||||
result.asExpr() =
|
||||
SignatureFlowAnalysisImpl::getAnIntermediateUseFromFinalUse(this, _, _).getMsgInput()
|
||||
}
|
||||
|
||||
override Crypto::ConsumerInputDataFlowNode getKeyConsumer() {
|
||||
result.asExpr() = this.getInitCall().getKeyArg()
|
||||
}
|
||||
|
||||
override Crypto::AlgorithmValueConsumer getAnAlgorithmValueConsumer() {
|
||||
result = this.getInstantiationCall().getAlgorithmArg()
|
||||
}
|
||||
|
||||
override Crypto::ArtifactOutputDataFlowNode getOutputArtifact() {
|
||||
result.asExpr() = super.getSignatureOutput() or
|
||||
result.asExpr() =
|
||||
SignatureFlowAnalysisImpl::getAnIntermediateUseFromFinalUse(this, _, _).getSignatureOutput()
|
||||
}
|
||||
|
||||
override Crypto::AlgorithmValueConsumer getHashAlgorithmValueConsumer() {
|
||||
// TODO: RSASSA-PSS literal sets hashes differently, through a ParameterSpec
|
||||
result = this.getInstantiationCall().getAlgorithmArg()
|
||||
}
|
||||
|
||||
override predicate hasHashAlgorithmConsumer() {
|
||||
// All jca signature algorithms specify a hash unless explicitly set as "NONEwith..."
|
||||
exists(SignatureStringLiteralAlgorithmInstance i |
|
||||
i.getConsumer() = this.getAnAlgorithmValueConsumer() and
|
||||
not i.getRawAlgorithmName().toUpperCase().matches("NONEwith%".toUpperCase())
|
||||
)
|
||||
}
|
||||
|
||||
override Crypto::KeyOperationSubtype getKeyOperationSubtype() { result = super.getSubtype() }
|
||||
|
||||
override Crypto::ConsumerInputDataFlowNode getNonceConsumer() { none() }
|
||||
|
||||
override Crypto::ConsumerInputDataFlowNode getSignatureConsumer() {
|
||||
result.asExpr() = super.getSignatureInput() or
|
||||
result.asExpr() =
|
||||
SignatureFlowAnalysisImpl::getAnIntermediateUseFromFinalUse(this, _, _).getSignatureInput()
|
||||
}
|
||||
}
|
||||
|
||||
module SignatureFlowAnalysisImpl =
|
||||
GetInstanceInitUseFlowAnalysis<SignatureGetInstanceCall, SignatureInitCall,
|
||||
SignatureOperationCall>;
|
||||
|
||||
/*
|
||||
* Elliptic Curves (EC)
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
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.SecretKey;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* AesWrapAndPBEWithTest demonstrates key wrapping and password-based encryption
|
||||
* using various transformations.
|
||||
*
|
||||
* This file includes:
|
||||
*
|
||||
* 1. AESWrap Examples: - secureAESWrap(): Uses a randomly generated wrapping
|
||||
* key. - insecureAESWrap(): Uses a fixed, hard-coded wrapping key.
|
||||
*
|
||||
* 2. PBEWith Examples: - insecurePBEExample(): Uses the legacy
|
||||
* PBEWithMD5AndDES. - securePBEExample(): Uses PBKDF2WithHmacSHA256. -
|
||||
* additionalPBEExample(): Uses PBEWithSHA256And128BitAES-CBC-BC. -
|
||||
* additionalPBEExample2(): Uses PBEWithSHA1And128BitAES-CBC-BC.
|
||||
*
|
||||
* 3. Dynamic PBE Encryption: - dynamicPBEEncryption(): Chooses the PBE
|
||||
* transformation based on a configuration string.
|
||||
*
|
||||
* Best Practices: - Use secure random keys and salts. - Avoid legacy algorithms
|
||||
* like PBEWithMD5AndDES. - Prefer modern KDFs (PBKDF2WithHmacSHA256) and secure
|
||||
* provider-specific PBE transformations.
|
||||
*
|
||||
* SAST/CBOM Notes: - Insecure examples (PBEWithMD5AndDES, fixed keys) should be
|
||||
* flagged. - Secure examples use random salt, high iteration counts, and strong
|
||||
* algorithms.
|
||||
*/
|
||||
public class AesWrapAndPBEWith {
|
||||
|
||||
// static {
|
||||
// // Register BouncyCastle as a provider.
|
||||
// Security.addProvider(new BouncyCastleProvider());
|
||||
// }
|
||||
// ===========================
|
||||
// 1. AESWrap Examples
|
||||
// ===========================
|
||||
/**
|
||||
* Secure AES key wrapping. Generates a random 256-bit wrapping key to wrap
|
||||
* a target AES key.
|
||||
*
|
||||
* @return The wrapped key (Base64-encoded).
|
||||
* @throws Exception if an error occurs.
|
||||
*/
|
||||
public String secureAESWrap() throws Exception {
|
||||
KeyGenerator kg = KeyGenerator.getInstance("AES");
|
||||
kg.init(256, new SecureRandom());
|
||||
SecretKey wrappingKey = kg.generateKey();
|
||||
|
||||
kg.init(128, new SecureRandom());
|
||||
SecretKey targetKey = kg.generateKey();
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AESWrap");
|
||||
cipher.init(Cipher.WRAP_MODE, wrappingKey);
|
||||
byte[] wrappedKey = cipher.wrap(targetKey);
|
||||
|
||||
return Base64.getEncoder().encodeToString(wrappedKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insecure AES key wrapping. Uses a fixed (hard-coded) wrapping key.
|
||||
*
|
||||
* @return The wrapped key (Base64-encoded).
|
||||
* @throws Exception if an error occurs.
|
||||
*/
|
||||
public String insecureAESWrap() throws Exception {
|
||||
byte[] fixedKeyBytes = new byte[32];
|
||||
Arrays.fill(fixedKeyBytes, (byte) 0x01);
|
||||
SecretKey wrappingKey = new SecretKeySpec(fixedKeyBytes, "AES");
|
||||
|
||||
KeyGenerator kg = KeyGenerator.getInstance("AES");
|
||||
kg.init(128, new SecureRandom());
|
||||
SecretKey targetKey = kg.generateKey();
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AESWrap");
|
||||
cipher.init(Cipher.WRAP_MODE, wrappingKey);
|
||||
byte[] wrappedKey = cipher.wrap(targetKey);
|
||||
|
||||
return Base64.getEncoder().encodeToString(wrappedKey);
|
||||
}
|
||||
|
||||
// ===========================
|
||||
// 2. PBEWith Examples
|
||||
// ===========================
|
||||
/**
|
||||
* Insecure PBE example using PBEWithMD5AndDES.
|
||||
*
|
||||
* @param password The input password.
|
||||
* @return The derived key (Base64-encoded).
|
||||
* @throws Exception if key derivation fails.
|
||||
*/
|
||||
public String insecurePBEExample(String password) throws Exception {
|
||||
byte[] salt = new byte[8];
|
||||
Arrays.fill(salt, (byte) 0x00); // Fixed salt (insecure)
|
||||
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 1000, 64);
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
|
||||
byte[] keyBytes = factory.generateSecret(spec).getEncoded();
|
||||
return Base64.getEncoder().encodeToString(keyBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Secure PBE example using PBKDF2WithHmacSHA256.
|
||||
*
|
||||
* @param password The input password.
|
||||
* @return The derived 256-bit AES key (Base64-encoded).
|
||||
* @throws Exception if key derivation fails.
|
||||
*/
|
||||
public String securePBEExample(String password) throws Exception {
|
||||
byte[] salt = new byte[16];
|
||||
new SecureRandom().nextBytes(salt);
|
||||
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 10000, 256);
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||
byte[] keyBytes = factory.generateSecret(spec).getEncoded();
|
||||
SecretKey aesKey = new SecretKeySpec(keyBytes, "AES");
|
||||
return Base64.getEncoder().encodeToString(aesKey.getEncoded());
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional PBE example using PBEWithSHA256And128BitAES-CBC-BC.
|
||||
*
|
||||
* @param password The input password.
|
||||
* @param plaintext The plaintext to encrypt.
|
||||
* @return The IV concatenated with ciphertext (Base64-encoded).
|
||||
* @throws Exception if key derivation or encryption fails.
|
||||
*/
|
||||
public String additionalPBEExample(String password, String plaintext) throws Exception {
|
||||
byte[] salt = new byte[16];
|
||||
new SecureRandom().nextBytes(salt);
|
||||
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 10000, 128);
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEWithSHA256And128BitAES-CBC-BC");
|
||||
SecretKey pbeKey = factory.generateSecret(spec);
|
||||
SecretKey aesKey = new SecretKeySpec(pbeKey.getEncoded(), "AES");
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
byte[] iv = new byte[16];
|
||||
new SecureRandom().nextBytes(iv);
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, aesKey, ivSpec);
|
||||
byte[] ciphertext = cipher.doFinal(plaintext.getBytes());
|
||||
byte[] output = concatenate(iv, ciphertext);
|
||||
return Base64.getEncoder().encodeToString(output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Additional PBE example using PBEWithSHA1And128BitAES-CBC-BC. This is less
|
||||
* preferred than PBKDF2WithHmacSHA256 but demonstrates another variant.
|
||||
*
|
||||
* @param password The input password.
|
||||
* @param plaintext The plaintext to encrypt.
|
||||
* @return The IV concatenated with ciphertext (Base64-encoded).
|
||||
* @throws Exception if key derivation or encryption fails.
|
||||
*/
|
||||
public String additionalPBEExample2(String password, String plaintext) throws Exception {
|
||||
byte[] salt = new byte[16];
|
||||
new SecureRandom().nextBytes(salt);
|
||||
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 10000, 128);
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEWithSHA1And128BitAES-CBC-BC");
|
||||
SecretKey pbeKey = factory.generateSecret(spec);
|
||||
SecretKey aesKey = new SecretKeySpec(pbeKey.getEncoded(), "AES");
|
||||
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
byte[] iv = new byte[16];
|
||||
new SecureRandom().nextBytes(iv);
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, aesKey, ivSpec);
|
||||
byte[] ciphertext = cipher.doFinal(plaintext.getBytes());
|
||||
byte[] output = concatenate(iv, ciphertext);
|
||||
return Base64.getEncoder().encodeToString(output);
|
||||
}
|
||||
|
||||
// ===========================
|
||||
// 3. Dynamic PBE Encryption
|
||||
// ===========================
|
||||
/**
|
||||
* Dynamically selects a PBE transformation based on a configuration string.
|
||||
*
|
||||
* Acceptable values: - "PBKDF2": Uses PBKDF2WithHmacSHA256. - "SHA256AES":
|
||||
* Uses PBEWithSHA256And128BitAES-CBC-BC. - "SHA1AES": Uses
|
||||
* PBEWithSHA1And128BitAES-CBC-BC. - Otherwise, falls back to insecure
|
||||
* PBEWithMD5AndDES.
|
||||
*
|
||||
* @param config The configuration string.
|
||||
* @param password The input password.
|
||||
* @param plaintext The plaintext to encrypt.
|
||||
* @return The Base64-encoded encrypted output.
|
||||
* @throws Exception if an error occurs.
|
||||
*/
|
||||
public String dynamicPBEEncryption(String config, String password, String plaintext) throws Exception {
|
||||
if ("PBKDF2".equalsIgnoreCase(config)) {
|
||||
return securePBEExample(password);
|
||||
} else if ("SHA256AES".equalsIgnoreCase(config)) {
|
||||
return additionalPBEExample(password, plaintext);
|
||||
} else if ("SHA1AES".equalsIgnoreCase(config)) {
|
||||
return additionalPBEExample2(password, plaintext);
|
||||
} else {
|
||||
// Fallback insecure option.
|
||||
return insecurePBEExample(password);
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================
|
||||
// Helper Methods
|
||||
// ===========================
|
||||
/**
|
||||
* Concatenates two byte arrays.
|
||||
*/
|
||||
private 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
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.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package com.example.crypto.algorithms;
|
||||
|
||||
// import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import java.security.*;
|
||||
import java.util.Arrays;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
|
||||
public class ChainedEncryptionTest {
|
||||
|
||||
// static {
|
||||
// Security.addProvider(new BouncyCastleProvider());
|
||||
// }
|
||||
// Encrypts using AES-GCM. Returns IV concatenated with ciphertext.
|
||||
public static byte[] encryptAESGCM(SecretKey key, byte[] plaintext) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
byte[] iv = new byte[12]; // 12-byte nonce for AES-GCM
|
||||
new SecureRandom().nextBytes(iv);
|
||||
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
|
||||
byte[] ciphertext = cipher.doFinal(plaintext);
|
||||
return concat(iv, ciphertext);
|
||||
}
|
||||
|
||||
// Decrypts AES-GCM ciphertext where IV is prepended.
|
||||
public static byte[] decryptAESGCM(SecretKey key, byte[] ivCiphertext) throws Exception {
|
||||
byte[] iv = Arrays.copyOfRange(ivCiphertext, 0, 12);
|
||||
byte[] ciphertext = Arrays.copyOfRange(ivCiphertext, 12, ivCiphertext.length);
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, spec);
|
||||
return cipher.doFinal(ciphertext);
|
||||
}
|
||||
|
||||
// Encrypts using ChaCha20-Poly1305. Returns nonce concatenated with ciphertext.
|
||||
public static byte[] encryptChaCha20Poly1305(SecretKey key, byte[] plaintext) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance("ChaCha20-Poly1305", "BC");
|
||||
byte[] nonce = new byte[12]; // 12-byte nonce for ChaCha20-Poly1305
|
||||
new SecureRandom().nextBytes(nonce);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(nonce));
|
||||
byte[] ciphertext = cipher.doFinal(plaintext);
|
||||
return concat(nonce, ciphertext);
|
||||
}
|
||||
|
||||
// Decrypts ChaCha20-Poly1305 ciphertext where nonce is prepended.
|
||||
public static byte[] decryptChaCha20Poly1305(SecretKey key, byte[] nonceCiphertext) throws Exception {
|
||||
byte[] nonce = Arrays.copyOfRange(nonceCiphertext, 0, 12);
|
||||
byte[] ciphertext = Arrays.copyOfRange(nonceCiphertext, 12, nonceCiphertext.length);
|
||||
Cipher cipher = Cipher.getInstance("ChaCha20-Poly1305", "BC");
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(nonce));
|
||||
return cipher.doFinal(ciphertext);
|
||||
}
|
||||
|
||||
// Helper method to concatenate two byte arrays.
|
||||
private static byte[] concat(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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs chained encryption and decryption in one function. First,
|
||||
* plaintext is encrypted with AES-GCM (inner layer), then that ciphertext
|
||||
* is encrypted with ChaCha20-Poly1305 (outer layer). The decryption process
|
||||
* reverses these steps.
|
||||
*
|
||||
* @param plaintext The input plaintext.
|
||||
* @return The decrypted plaintext as a String.
|
||||
* @throws Exception if any cryptographic operation fails.
|
||||
*/
|
||||
public static String chainEncryptDecrypt(String plaintext) throws Exception {
|
||||
byte[] plainBytes = plaintext.getBytes("UTF-8");
|
||||
|
||||
// Generate keys for inner and outer encryption.
|
||||
KeyGenerator aesGen = KeyGenerator.getInstance("AES");
|
||||
aesGen.init(256, new SecureRandom());
|
||||
SecretKey innerKey = aesGen.generateKey();
|
||||
|
||||
KeyGenerator chachaGen = KeyGenerator.getInstance("ChaCha20", "BC");
|
||||
chachaGen.init(256, new SecureRandom());
|
||||
SecretKey outerKey = chachaGen.generateKey();
|
||||
|
||||
// Inner Encryption with AES-GCM.
|
||||
byte[] aesIV = new byte[12]; // Random 12-byte IV.
|
||||
new SecureRandom().nextBytes(aesIV);
|
||||
Cipher aesCipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, aesIV);
|
||||
aesCipher.init(Cipher.ENCRYPT_MODE, innerKey, gcmSpec);
|
||||
byte[] innerCiphertext = aesCipher.doFinal(plainBytes);
|
||||
|
||||
// Outer Encryption with ChaCha20-Poly1305.
|
||||
byte[] chachaNonce = new byte[12]; // Random 12-byte nonce.
|
||||
new SecureRandom().nextBytes(chachaNonce);
|
||||
Cipher chachaCipher = Cipher.getInstance("ChaCha20-Poly1305", "BC");
|
||||
chachaCipher.init(Cipher.ENCRYPT_MODE, outerKey, new IvParameterSpec(chachaNonce));
|
||||
byte[] outerCiphertext = chachaCipher.doFinal(innerCiphertext);
|
||||
|
||||
// Outer Decryption.
|
||||
Cipher chachaDec = Cipher.getInstance("ChaCha20-Poly1305", "BC");
|
||||
chachaDec.init(Cipher.DECRYPT_MODE, outerKey, new IvParameterSpec(chachaNonce));
|
||||
byte[] decryptedInnerCiphertext = chachaDec.doFinal(outerCiphertext);
|
||||
|
||||
// Inner Decryption.
|
||||
Cipher aesDec = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
aesDec.init(Cipher.DECRYPT_MODE, innerKey, new GCMParameterSpec(128, aesIV));
|
||||
byte[] decryptedPlaintext = aesDec.doFinal(decryptedInnerCiphertext);
|
||||
|
||||
return new String(decryptedPlaintext, "UTF-8");
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
// Generate a 256-bit AES key for the first (inner) encryption.
|
||||
KeyGenerator aesGen = KeyGenerator.getInstance("AES");
|
||||
aesGen.init(256, new SecureRandom());
|
||||
SecretKey aesKey = aesGen.generateKey();
|
||||
|
||||
// Generate a 256-bit key for ChaCha20-Poly1305 (outer encryption).
|
||||
KeyGenerator chaChaGen = KeyGenerator.getInstance("ChaCha20");
|
||||
chaChaGen.init(256, new SecureRandom());
|
||||
SecretKey chaChaKey = chaChaGen.generateKey();
|
||||
|
||||
String originalText = "This is a secret message.";
|
||||
byte[] plaintext = originalText.getBytes();
|
||||
|
||||
// Step 1: Encrypt plaintext with AES-GCM.
|
||||
byte[] innerCiphertext = encryptAESGCM(aesKey, plaintext);
|
||||
|
||||
// Step 2: Encrypt the AES-GCM ciphertext with ChaCha20-Poly1305.
|
||||
byte[] outerCiphertext = encryptChaCha20Poly1305(chaChaKey, innerCiphertext);
|
||||
|
||||
// Now, decrypt in reverse order.
|
||||
// Step 3: Decrypt the outer layer (ChaCha20-Poly1305).
|
||||
byte[] decryptedInnerCiphertext = decryptChaCha20Poly1305(chaChaKey, outerCiphertext);
|
||||
|
||||
// Step 4: Decrypt the inner layer (AES-GCM).
|
||||
byte[] decryptedPlaintext = decryptAESGCM(aesKey, decryptedInnerCiphertext);
|
||||
|
||||
System.out.println("Original: " + originalText);
|
||||
System.out.println("Decrypted: " + new String(decryptedPlaintext));
|
||||
}
|
||||
|
||||
}
|
||||
256
java/ql/test/experimental/library-tests/quantum/jca/Digest.java
Normal file
256
java/ql/test/experimental/library-tests/quantum/jca/Digest.java
Normal file
@@ -0,0 +1,256 @@
|
||||
package com.example.crypto.artifacts;
|
||||
|
||||
// import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* DigestTestCase demonstrates the further use of cryptographic digests
|
||||
* as inputs to more complex cryptosystems. In real-world applications,
|
||||
* digest outputs are often used as keys, key material for key derivation,
|
||||
* or as identifiers. This file shows several flows:
|
||||
*
|
||||
* 1. Basic digest generation using SHA-256 (secure) and MD5/SHA-1 (insecure).
|
||||
* 2. Unsalted versus salted digest for password input.
|
||||
* 3. PBKDF2 for secure key derivation.
|
||||
* 4. Using a digest as direct key material for AES encryption (processDigest).
|
||||
* 5. Using a digest as an identifier (alternativeDigestFlow).
|
||||
* 6. **Further Use**: Deriving two separate keys (one for encryption and one
|
||||
* for MAC)
|
||||
* from a digest via PBKDF2 and using them in an authenticated encryption flow.
|
||||
*
|
||||
* SAST/CBOM notes:
|
||||
* - Secure algorithms (e.g. SHA-256, HMAC-SHA256, PBKDF2WithHmacSHA256) are
|
||||
* acceptable.
|
||||
* - Insecure functions (e.g. MD5, SHA-1) and unsalted password digests are
|
||||
* flagged.
|
||||
* - Using a raw digest directly as key material is ambiguous unless produced by
|
||||
* a proper KDF.
|
||||
*/
|
||||
public class Digest {
|
||||
|
||||
// static {
|
||||
// Security.addProvider(new BouncyCastleProvider());
|
||||
// }
|
||||
|
||||
// ---------- Digest Generation Flows ----------
|
||||
|
||||
/**
|
||||
* Secure digest generation using SHA-256.
|
||||
* SAST: SHA-256 is secure.
|
||||
*/
|
||||
public void simpleHashing() throws Exception {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] hash = digest.digest("Simple Test Data".getBytes());
|
||||
processDigest(hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insecure digest generation using MD5.
|
||||
* SAST: MD5 is deprecated and insecure.
|
||||
*/
|
||||
public void insecureMD5Hashing() throws Exception {
|
||||
MessageDigest md5Digest = MessageDigest.getInstance("MD5");
|
||||
byte[] hash = md5Digest.digest("Weak Hash Example".getBytes());
|
||||
processDigest(hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insecure unsalted password hashing using SHA-256.
|
||||
* SAST: Unsalted password hashing is vulnerable to rainbow table attacks.
|
||||
*/
|
||||
public void insecureUnsaltedPasswordHashing(String password) throws Exception {
|
||||
MessageDigest sha256Digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] hash = sha256Digest.digest(password.getBytes());
|
||||
processDigest(hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Secure salted hashing using SHA-256.
|
||||
* SAST: Salting the input improves security.
|
||||
*/
|
||||
public void secureSaltedHashing(String password) throws Exception {
|
||||
byte[] salt = generateSalt(16);
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
digest.update(salt);
|
||||
byte[] hash = digest.digest(password.getBytes());
|
||||
processDigest(hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Secure key derivation using PBKDF2 with HMAC-SHA256.
|
||||
* SAST: PBKDF2 with sufficient iterations is recommended.
|
||||
*/
|
||||
public void securePBKDF2Hashing(String password) throws Exception {
|
||||
byte[] salt = generateSalt(16);
|
||||
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 10000, 256);
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||
byte[] hash = factory.generateSecret(spec).getEncoded();
|
||||
processDigest(hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insecure digest generation using SHA-1.
|
||||
* SAST: SHA-1 is deprecated due to collision vulnerabilities.
|
||||
*/
|
||||
public void insecureRawSHA1Hashing(String input) throws Exception {
|
||||
MessageDigest sha1Digest = MessageDigest.getInstance("SHA-1");
|
||||
byte[] hash = sha1Digest.digest(input.getBytes());
|
||||
processDigest(hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Secure MAC computation using HMAC-SHA256.
|
||||
* SAST: HMAC-SHA256 is considered secure.
|
||||
*/
|
||||
public void secureHMACHashing(String input, byte[] key) throws Exception {
|
||||
Mac hmac = Mac.getInstance("HmacSHA256");
|
||||
SecretKey secretKey = new SecretKeySpec(key, "HmacSHA256");
|
||||
hmac.init(secretKey);
|
||||
byte[] hash = hmac.doFinal(input.getBytes());
|
||||
processDigest(hash);
|
||||
}
|
||||
|
||||
// ---------- Further Use of Digest Outputs ----------
|
||||
|
||||
/**
|
||||
* Processes the digest by using it directly as key material for AES encryption.
|
||||
* SAST: Using a raw digest as key material is acceptable only if the digest is
|
||||
* produced
|
||||
* via a secure KDF. This method is ambiguous if the digest is from an insecure
|
||||
* function.
|
||||
*
|
||||
* @param digest The computed digest.
|
||||
* @throws Exception if encryption fails.
|
||||
*/
|
||||
public void processDigest(byte[] digest) throws Exception {
|
||||
// Derive a 128-bit AES key from the digest.
|
||||
SecretKey key = new SecretKeySpec(digest, 0, 16, "AES");
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, new SecureRandom());
|
||||
byte[] encryptedData = cipher.doFinal("Sensitive Data".getBytes());
|
||||
storeEncryptedDigest(encryptedData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative flow: Uses the digest as an identifier (e.g., checksum) and
|
||||
* encrypts it.
|
||||
* SAST: Using a digest as an identifier is common; encryption must use secure
|
||||
* primitives.
|
||||
*
|
||||
* @param digest The computed digest.
|
||||
* @throws Exception if encryption fails.
|
||||
*/
|
||||
public void alternativeDigestFlow(byte[] digest) throws Exception {
|
||||
byte[] identifier = Base64.getEncoder().encode(digest);
|
||||
encryptAndSend(identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Further use: Derives two separate keys from a digest using PBKDF2,
|
||||
* then uses one key for encryption and the other for computing a MAC over the
|
||||
* ciphertext.
|
||||
*
|
||||
* SAST: This approach of key derivation and splitting is acceptable if PBKDF2
|
||||
* is used securely.
|
||||
*
|
||||
* @param digest The input digest (must be generated from a secure source).
|
||||
* @throws Exception if key derivation or encryption fails.
|
||||
*/
|
||||
public void furtherUseDigestForKeyDerivation(byte[] digest) throws Exception {
|
||||
// Treat the digest (in Base64) as a password input to PBKDF2.
|
||||
String digestAsPassword = Base64.getEncoder().encodeToString(digest);
|
||||
byte[] salt = generateSalt(16);
|
||||
// Derive 256 bits (32 bytes) of key material.
|
||||
PBEKeySpec spec = new PBEKeySpec(digestAsPassword.toCharArray(), salt, 10000, 256);
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||
byte[] keyMaterial = factory.generateSecret(spec).getEncoded();
|
||||
// Split into two 128-bit keys.
|
||||
byte[] encryptionKeyBytes = Arrays.copyOfRange(keyMaterial, 0, 16);
|
||||
byte[] macKeyBytes = Arrays.copyOfRange(keyMaterial, 16, 32);
|
||||
SecretKey encryptionKey = new SecretKeySpec(encryptionKeyBytes, "AES");
|
||||
SecretKey macKey = new SecretKeySpec(macKeyBytes, "HmacSHA256");
|
||||
|
||||
// Encrypt sample data using the derived encryption key.
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, new SecureRandom());
|
||||
byte[] ciphertext = cipher.doFinal("Further Use Test Data".getBytes());
|
||||
|
||||
// Compute HMAC over the ciphertext using the derived MAC key.
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(macKey);
|
||||
byte[] computedMac = mac.doFinal(ciphertext);
|
||||
|
||||
// In production, these outputs would be securely stored or transmitted.
|
||||
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);
|
||||
storeEncryptedDigest(output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts data using AES-GCM and simulates secure transmission or storage.
|
||||
* SAST: Uses a securely generated AES key.
|
||||
*
|
||||
* @param data The data to encrypt.
|
||||
* @throws Exception if encryption fails.
|
||||
*/
|
||||
public void encryptAndSend(byte[] data) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
SecretKey key = generateAESKey();
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, new SecureRandom());
|
||||
byte[] encryptedData = cipher.doFinal(data);
|
||||
storeEncryptedDigest(encryptedData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates secure storage or transmission of an encrypted digest.
|
||||
* SAST: In production, this method would implement secure storage/transmission.
|
||||
*
|
||||
* @param encryptedDigest The encrypted digest.
|
||||
*/
|
||||
public void storeEncryptedDigest(byte[] encryptedDigest) {
|
||||
// For static analysis purposes, this method represents a secure output
|
||||
// mechanism.
|
||||
String stored = Base64.getEncoder().encodeToString(encryptedDigest);
|
||||
}
|
||||
|
||||
// ---------- Helper Methods ----------
|
||||
|
||||
/**
|
||||
* Generates a secure 256-bit AES key.
|
||||
* SAST: Key generation uses a strong RNG.
|
||||
*
|
||||
* @return A SecretKey for AES.
|
||||
* @throws NoSuchAlgorithmException if AES is unsupported.
|
||||
*/
|
||||
private SecretKey generateAESKey() throws NoSuchAlgorithmException {
|
||||
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
|
||||
keyGen.init(256);
|
||||
return keyGen.generateKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random salt of the specified length using SecureRandom.
|
||||
* SAST: Salting is essential for secure digest computations.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package com.example.crypto.algorithms;
|
||||
|
||||
// import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* EllipticCurve1 demonstrates generating EC key pairs for various curve
|
||||
* categories.
|
||||
*
|
||||
* Curve categories covered:
|
||||
* - NIST: e.g., secp256r1, secp384r1, secp521r1.
|
||||
* - SEC: e.g., secp256k1 (from the Standards for Efficient Cryptography, SEC2).
|
||||
* - BRAINPOOL: e.g., brainpoolP256r1.
|
||||
* - CURVE25519: for key agreement (X25519) or signatures (Ed25519).
|
||||
* - CURVE448: for key agreement (X448).
|
||||
* - C2: Binary curves; for example, sect163r2 (if available).
|
||||
* - SM2: Chinese SM2 curve, often named sm2p256v1.
|
||||
* - ES: Elliptic curve signature based on EdDSA, here using Ed25519.
|
||||
* - OtherEllipticCurveType: A fallback (using secp256r1).
|
||||
*
|
||||
* Best practices:
|
||||
* - Use ephemeral key generation with a strong RNG.
|
||||
* - Select curves from secure families (e.g., NIST, Brainpool, Curve25519/448,
|
||||
* SM2).
|
||||
* - Use a crypto provider (e.g., BouncyCastle) that supports the desired
|
||||
* curves.
|
||||
*
|
||||
* In a production environment, the curve type may be externally configured.
|
||||
*/
|
||||
public class EllipticCurve1 {
|
||||
|
||||
// static {
|
||||
// // Register the BouncyCastle provider to access a wide range of curves.
|
||||
// Security.addProvider(new BouncyCastleProvider());
|
||||
// }
|
||||
|
||||
/**
|
||||
* Generates a key pair using a NIST curve (e.g., secp256r1).
|
||||
*/
|
||||
public KeyPair generateNISTKeyPair() throws Exception {
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");
|
||||
// secp256r1 is widely used (also known as P-256)
|
||||
kpg.initialize(new java.security.spec.ECGenParameterSpec("secp256r1"));
|
||||
return kpg.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a key pair using a SEC curve (e.g., secp256k1).
|
||||
*/
|
||||
public KeyPair generateSECCurveKeyPair() throws Exception {
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");
|
||||
// secp256k1 is commonly used in Bitcoin and other blockchain applications.
|
||||
kpg.initialize(new java.security.spec.ECGenParameterSpec("secp256k1"));
|
||||
return kpg.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a key pair using a Brainpool curve (e.g., brainpoolP256r1).
|
||||
*/
|
||||
public KeyPair generateBrainpoolKeyPair() throws Exception {
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");
|
||||
// "brainpoolP256r1" is a commonly recommended Brainpool curve.
|
||||
kpg.initialize(new java.security.spec.ECGenParameterSpec("brainpoolP256r1"));
|
||||
return kpg.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an X25519 key pair (for key agreement).
|
||||
*/
|
||||
public KeyPair generateCurve25519KeyPair() throws Exception {
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("X25519", "BC");
|
||||
// No further parameters are needed for X25519.
|
||||
return kpg.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an X448 key pair (for key agreement).
|
||||
*/
|
||||
public KeyPair generateCurve448KeyPair() throws Exception {
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("X448", "BC");
|
||||
return kpg.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a key pair for a binary (C2) curve.
|
||||
* Example: sect163r2 is a binary field curve.
|
||||
*/
|
||||
public KeyPair generateC2CurveKeyPair() throws Exception {
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");
|
||||
// "sect163r2" is one of the binary field curves supported by BouncyCastle.
|
||||
kpg.initialize(new java.security.spec.ECGenParameterSpec("sect163r2"));
|
||||
return kpg.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a key pair for the SM2 curve.
|
||||
* SM2 is a Chinese cryptographic standard.
|
||||
*/
|
||||
public KeyPair generateSM2KeyPair() throws Exception {
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");
|
||||
// "sm2p256v1" is the standard SM2 curve.
|
||||
kpg.initialize(new java.security.spec.ECGenParameterSpec("sm2p256v1"));
|
||||
return kpg.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a key pair for ES (Elliptic curve signature using EdDSA).
|
||||
* This example uses Ed25519.
|
||||
*/
|
||||
public KeyPair generateESKeyPair() throws Exception {
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("Ed25519", "BC");
|
||||
return kpg.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a key pair for an "Other" elliptic curve type.
|
||||
* This serves as a fallback example (using secp256r1).
|
||||
*/
|
||||
public KeyPair generateOtherEllipticCurveKeyPair() throws Exception {
|
||||
return generateNISTKeyPair(); // Fallback to secp256r1
|
||||
}
|
||||
|
||||
/**
|
||||
* Main method demonstrating key pair generation for various curve types.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
EllipticCurve1 examples = new EllipticCurve1();
|
||||
System.out.println("NIST (secp256r1): " +
|
||||
Base64.getEncoder().encodeToString(examples.generateNISTKeyPair().getPublic().getEncoded()));
|
||||
System.out.println("SEC (secp256k1): " +
|
||||
Base64.getEncoder().encodeToString(examples.generateSECCurveKeyPair().getPublic().getEncoded()));
|
||||
System.out.println("Brainpool (brainpoolP256r1): " +
|
||||
Base64.getEncoder().encodeToString(examples.generateBrainpoolKeyPair().getPublic().getEncoded()));
|
||||
System.out.println("Curve25519 (X25519): " +
|
||||
Base64.getEncoder().encodeToString(examples.generateCurve25519KeyPair().getPublic().getEncoded()));
|
||||
System.out.println("Curve448 (X448): " +
|
||||
Base64.getEncoder().encodeToString(examples.generateCurve448KeyPair().getPublic().getEncoded()));
|
||||
System.out.println("C2 (sect163r2): " +
|
||||
Base64.getEncoder().encodeToString(examples.generateC2CurveKeyPair().getPublic().getEncoded()));
|
||||
System.out.println("SM2 (sm2p256v1): " +
|
||||
Base64.getEncoder().encodeToString(examples.generateSM2KeyPair().getPublic().getEncoded()));
|
||||
System.out.println("ES (Ed25519): " +
|
||||
Base64.getEncoder().encodeToString(examples.generateESKeyPair().getPublic().getEncoded()));
|
||||
System.out.println("Other (Fallback, secp256r1): " +
|
||||
Base64.getEncoder()
|
||||
.encodeToString(examples.generateOtherEllipticCurveKeyPair().getPublic().getEncoded()));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
package com.example.crypto.algorithms;
|
||||
|
||||
//import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
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.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* EllipticCurve2 demonstrates real-world uses of elliptic curve algorithms,
|
||||
* including key pair generation, key agreement (ECDH), digital signatures
|
||||
* (ECDSA, EdDSA), and a simple simulation of ECIES (using ECDH + AES-GCM).
|
||||
*
|
||||
* Curve types shown include: - NIST (e.g., secp256r1) - SEC (e.g., secp256k1) -
|
||||
* Brainpool (e.g., brainpoolP256r1) - CURVE25519 (for X25519 key agreement) -
|
||||
* ES (e.g., Ed25519 for signatures) - Other fallback (e.g., secp256r1 for
|
||||
* "OtherEllipticCurveType")
|
||||
*
|
||||
* Best practices: - Use ephemeral keys and a strong RNG. - Use proper key
|
||||
* agreement (with a KDF if needed) and digital signature schemes. - Avoid
|
||||
* static key reuse or using weak curves.
|
||||
*
|
||||
* SAST/CBOM considerations: - Secure implementations use ephemeral keys and
|
||||
* modern curves. - Insecure practices (e.g., static keys or reusing keys) must
|
||||
* be flagged.
|
||||
*/
|
||||
public class EllipticCurve2 {
|
||||
|
||||
// static {
|
||||
// // Register BouncyCastle provider for additional curves and algorithms.
|
||||
// Security.addProvider(new BouncyCastleProvider());
|
||||
// }
|
||||
// ----------------------------
|
||||
// 1. Key Pair Generation Examples
|
||||
// ----------------------------
|
||||
/**
|
||||
* Generates a key pair using a NIST curve (secp256r1).
|
||||
*/
|
||||
public KeyPair generateNISTKeyPair() throws Exception {
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");
|
||||
kpg.initialize(new ECGenParameterSpec("secp256r1"), new SecureRandom());
|
||||
return kpg.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a key pair using a SEC curve (secp256k1).
|
||||
*/
|
||||
public KeyPair generateSECCurveKeyPair() throws Exception {
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");
|
||||
kpg.initialize(new ECGenParameterSpec("secp256k1"), new SecureRandom());
|
||||
return kpg.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a key pair using a Brainpool curve (brainpoolP256r1).
|
||||
*/
|
||||
public KeyPair generateBrainpoolKeyPair() throws Exception {
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");
|
||||
kpg.initialize(new ECGenParameterSpec("brainpoolP256r1"), new SecureRandom());
|
||||
return kpg.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an X25519 key pair.
|
||||
*/
|
||||
public KeyPair generateX25519KeyPair() throws Exception {
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("X25519", "BC");
|
||||
return kpg.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an Ed25519 key pair (used for signatures).
|
||||
*/
|
||||
public KeyPair generateEd25519KeyPair() throws Exception {
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("Ed25519", "BC");
|
||||
return kpg.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a key pair for "OtherEllipticCurveType" as a fallback (using
|
||||
* secp256r1).
|
||||
*/
|
||||
public KeyPair generateOtherEllipticCurveKeyPair() throws Exception {
|
||||
return generateNISTKeyPair();
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// 2. Key Agreement (ECDH) Examples
|
||||
// ----------------------------
|
||||
/**
|
||||
* Performs ECDH key agreement using two ephemeral NIST key pairs. Secure
|
||||
* Example: Uses ephemeral keys and a strong RNG.
|
||||
*
|
||||
* @return The shared secret.
|
||||
*/
|
||||
public byte[] performECDHKeyAgreement() throws Exception {
|
||||
KeyPair aliceKP = generateNISTKeyPair();
|
||||
KeyPair bobKP = generateNISTKeyPair();
|
||||
|
||||
KeyAgreement ka = KeyAgreement.getInstance("ECDH", "BC");
|
||||
ka.init(aliceKP.getPrivate());
|
||||
ka.doPhase(bobKP.getPublic(), true);
|
||||
return ka.generateSecret();
|
||||
}
|
||||
|
||||
/**
|
||||
* Insecure ECDH Example: Uses a static key pair for both parties. SAST:
|
||||
* Reusing the same key pair eliminates forward secrecy and is insecure.
|
||||
*
|
||||
* @return The (insecure) shared secret.
|
||||
*/
|
||||
public byte[] insecureECDHKeyAgreement() throws Exception {
|
||||
KeyPair staticKP = generateNISTKeyPair();
|
||||
KeyAgreement ka = KeyAgreement.getInstance("ECDH", "BC");
|
||||
ka.init(staticKP.getPrivate());
|
||||
ka.doPhase(staticKP.getPublic(), true);
|
||||
return ka.generateSecret();
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// 3. Digital Signature Examples
|
||||
// ----------------------------
|
||||
/**
|
||||
* Generates an ECDSA signature using a NIST key pair. Secure Example.
|
||||
*
|
||||
* @param message The message to sign.
|
||||
* @return The signature.
|
||||
*/
|
||||
public byte[] generateECDSASignature(byte[] message) throws Exception {
|
||||
KeyPair kp = generateNISTKeyPair();
|
||||
Signature signature = Signature.getInstance("SHA256withECDSA", "BC");
|
||||
signature.initSign(kp.getPrivate());
|
||||
signature.update(message);
|
||||
return signature.sign();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies an ECDSA signature using the corresponding NIST key pair.
|
||||
*
|
||||
* @param message The original message.
|
||||
* @param signatureBytes The signature to verify.
|
||||
* @param kp The key pair used for signing.
|
||||
* @return True if the signature is valid.
|
||||
*/
|
||||
public boolean verifyECDSASignature(byte[] message, byte[] signatureBytes, KeyPair kp) throws Exception {
|
||||
Signature signature = Signature.getInstance("SHA256withECDSA", "BC");
|
||||
signature.initVerify(kp.getPublic());
|
||||
signature.update(message);
|
||||
return signature.verify(signatureBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an Ed25519 signature. Secure Example: Ed25519 is a modern,
|
||||
* high-performance signature scheme.
|
||||
*
|
||||
* @param message The message to sign.
|
||||
* @return The signature.
|
||||
*/
|
||||
public byte[] generateEd25519Signature(byte[] message) throws Exception {
|
||||
KeyPair kp = generateEd25519KeyPair();
|
||||
Signature signature = Signature.getInstance("Ed25519", "BC");
|
||||
signature.initSign(kp.getPrivate());
|
||||
signature.update(message);
|
||||
return signature.sign();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies an Ed25519 signature.
|
||||
*
|
||||
* @param message The original message.
|
||||
* @param signatureBytes The signature to verify.
|
||||
* @param kp The key pair used for signing.
|
||||
* @return True if the signature is valid.
|
||||
*/
|
||||
public boolean verifyEd25519Signature(byte[] message, byte[] signatureBytes, KeyPair kp) throws Exception {
|
||||
Signature signature = Signature.getInstance("Ed25519", "BC");
|
||||
signature.initVerify(kp.getPublic());
|
||||
signature.update(message);
|
||||
return signature.verify(signatureBytes);
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// 4. ECIES-like Encryption (ECDH + AES-GCM)
|
||||
// ----------------------------
|
||||
/**
|
||||
* A simple simulation of ECIES using ECDH for key agreement and AES-GCM for
|
||||
* encryption. Secure Example: Uses ephemeral ECDH key pairs, a KDF to
|
||||
* derive a symmetric key, and AES-GCM with a random nonce.
|
||||
*
|
||||
* @param plaintext The plaintext to encrypt.
|
||||
* @return The concatenation of the ephemeral public key, IV, and ciphertext
|
||||
* (Base64-encoded).
|
||||
* @throws Exception if encryption fails.
|
||||
*/
|
||||
public String eciesEncryptionExample(byte[] plaintext) throws Exception {
|
||||
// Generate ephemeral key pairs for two parties.
|
||||
KeyPair senderKP = generateNISTKeyPair();
|
||||
KeyPair receiverKP = generateNISTKeyPair();
|
||||
|
||||
// Perform ECDH key agreement.
|
||||
KeyAgreement ka = KeyAgreement.getInstance("ECDH", "BC");
|
||||
ka.init(senderKP.getPrivate());
|
||||
ka.doPhase(receiverKP.getPublic(), true);
|
||||
byte[] sharedSecret = ka.generateSecret();
|
||||
|
||||
// Derive a symmetric key from the shared secret using SHA-256 (first 16 bytes
|
||||
// for AES-128).
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] derivedKey = digest.digest(sharedSecret);
|
||||
derivedKey = Arrays.copyOf(derivedKey, 16);
|
||||
SecretKey aesKey = new SecretKeySpec(derivedKey, "AES");
|
||||
|
||||
// Encrypt plaintext using AES-GCM with a random nonce.
|
||||
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);
|
||||
|
||||
// For ECIES, include the sender's ephemeral public key with the output.
|
||||
byte[] senderPub = senderKP.getPublic().getEncoded();
|
||||
byte[] output = concatenate(senderPub, concatenate(iv, ciphertext));
|
||||
|
||||
return Base64.getEncoder().encodeToString(output);
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// 5. Main Method for Demonstration
|
||||
// ----------------------------
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
EllipticCurve2 test = new EllipticCurve2();
|
||||
|
||||
// Key Agreement Example:
|
||||
byte[] sharedSecret = test.performECDHKeyAgreement();
|
||||
System.out.println("ECDH Shared Secret (Base64): " + Base64.getEncoder().encodeToString(sharedSecret));
|
||||
|
||||
// ECDSA Signature Example:
|
||||
byte[] message = "Test message for ECDSA".getBytes();
|
||||
KeyPair nistKP = test.generateNISTKeyPair();
|
||||
byte[] ecdsaSig = test.generateECDSASignature(message);
|
||||
boolean validSig = test.verifyECDSASignature(message, ecdsaSig, nistKP);
|
||||
System.out.println("ECDSA Signature valid? " + validSig);
|
||||
|
||||
// Ed25519 Signature Example:
|
||||
byte[] edSig = test.generateEd25519Signature(message);
|
||||
KeyPair edKP = test.generateEd25519KeyPair();
|
||||
boolean validEdSig = test.verifyEd25519Signature(message, edSig, edKP);
|
||||
System.out.println("Ed25519 Signature valid? " + validEdSig);
|
||||
|
||||
// ECIES-like Encryption Example:
|
||||
String eciesOutput = test.eciesEncryptionExample("Secret ECIES Message".getBytes());
|
||||
System.out.println("ECIES-like Encrypted Output (Base64): " + eciesOutput);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
package com.example.crypto.algorithms;
|
||||
|
||||
//import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import java.security.*;
|
||||
import java.util.Base64;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
|
||||
/**
|
||||
* This class demonstrates several encryption schemes along with SAST/CBOM
|
||||
* classification notes.
|
||||
*
|
||||
* Methods include:
|
||||
*
|
||||
* 1. simpleAESEncryption: Uses AES in GCM mode.
|
||||
* - CBOM: AES-GCM is classified as secure (Parent: AEAD).
|
||||
* - SAST: Secure symmetric encryption pattern; safe when used properly.
|
||||
*
|
||||
* 2. insecureAESWithECB: Uses AES in ECB mode.
|
||||
* - CBOM: AES-ECB is classified as insecure (Parent: SymmetricEncryption).
|
||||
* - SAST: Insecure encryption pattern; flagged as vulnerable due to lack of IV
|
||||
* and predictable structure.
|
||||
*
|
||||
* 3. rsaOaepEncryption / rsaOaepDecryption: Use RSA with OAEP padding.
|
||||
* - CBOM: RSA-OAEP is classified as secure for public-key encryption (Parent:
|
||||
* Hybrid Cryptosystem).
|
||||
* - SAST: Secure for small payloads/key encapsulation; must only encrypt small
|
||||
* data blocks.
|
||||
*
|
||||
* 4. rsaKemEncryption: Demonstrates a key encapsulation mechanism (KEM) using
|
||||
* RSA-OAEP.
|
||||
* - CBOM: RSA-KEM is recognized as secure (Parent: RSA-OAEP based KEM).
|
||||
* - SAST: Secure when used to encapsulate symmetric keys in a hybrid system.
|
||||
*
|
||||
* 5. hybridEncryption: Combines RSA-OAEP for key encapsulation with AES-GCM for
|
||||
* data encryption.
|
||||
* - CBOM: Hybrid encryption (Parent: RSA-OAEP + AES-GCM) is classified as
|
||||
* secure.
|
||||
* - SAST: Secure hybrid encryption pattern; recommended for large data
|
||||
* encryption.
|
||||
*/
|
||||
public class Encryption1 {
|
||||
|
||||
// static {
|
||||
// Security.addProvider(new BouncyCastleProvider());
|
||||
// }
|
||||
|
||||
/**
|
||||
* Simple AES-GCM encryption.
|
||||
*
|
||||
* SAST/CBOM Notes:
|
||||
* - Algorithm: AES/GCM/NoPadding with a 256-bit key.
|
||||
* - Parent Classification: AEAD (Authenticated Encryption with Associated
|
||||
* Data).
|
||||
* - SAST: Considered safe when properly implemented (uses IV and tag).
|
||||
*/
|
||||
public void simpleAESEncryption() throws Exception {
|
||||
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
|
||||
keyGen.init(256); // 256-bit AES key.
|
||||
SecretKey key = keyGen.generateKey();
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
byte[] iv = new byte[12]; // 12-byte IV recommended for GCM.
|
||||
new SecureRandom().nextBytes(iv);
|
||||
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv); // 128-bit authentication tag.
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, gcmSpec);
|
||||
byte[] encryptedData = cipher.doFinal("Sensitive Data".getBytes());
|
||||
System.out.println("AES-GCM Encrypted Data: " + Base64.getEncoder().encodeToString(encryptedData));
|
||||
}
|
||||
|
||||
/**
|
||||
* Insecure AES encryption using ECB mode.
|
||||
*
|
||||
* SAST/CBOM Notes:
|
||||
* - Algorithm: AES/ECB/NoPadding with a 256-bit key.
|
||||
* - Parent Classification: SymmetricEncryption (ECB mode is inherently
|
||||
* insecure).
|
||||
* - SAST: Flagged as vulnerable; ECB mode does not use an IV and reveals data
|
||||
* patterns.
|
||||
*/
|
||||
public void insecureAESWithECB() throws Exception {
|
||||
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
|
||||
keyGen.init(256); // 256-bit AES key.
|
||||
SecretKey key = keyGen.generateKey();
|
||||
// AES/ECB mode is insecure due to the deterministic nature of the block cipher
|
||||
// without an IV.
|
||||
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
byte[] encryptedData = cipher.doFinal("Sensitive Data".getBytes());
|
||||
System.out.println("AES-ECB Encrypted Data (Insecure): " + Base64.getEncoder().encodeToString(encryptedData));
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA encryption using OAEP with SHA-256 and MGF1 padding.
|
||||
*
|
||||
* SAST/CBOM Notes:
|
||||
* - Algorithm: RSA/ECB/OAEPWithSHA-256AndMGF1Padding.
|
||||
* - Parent Classification: Hybrid Cryptosystem. RSA-OAEP is commonly used in
|
||||
* hybrid schemes.
|
||||
* - SAST: Secure for encrypting small payloads or for key encapsulation;
|
||||
* caution when encrypting large data.
|
||||
*/
|
||||
public void rsaOaepEncryption(PublicKey publicKey, String data) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||
byte[] encryptedData = cipher.doFinal(data.getBytes());
|
||||
System.out.println("RSA-OAEP Encrypted Data: " + Base64.getEncoder().encodeToString(encryptedData));
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA decryption using OAEP with SHA-256 and MGF1 padding.
|
||||
*
|
||||
* SAST/CBOM Notes:
|
||||
* - Algorithm: RSA/ECB/OAEPWithSHA-256AndMGF1Padding.
|
||||
* - Parent Classification: Hybrid Cryptosystem.
|
||||
* - SAST: Secure when used with the correct corresponding private key.
|
||||
*/
|
||||
public void rsaOaepDecryption(PrivateKey privateKey, byte[] encryptedData) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
||||
byte[] decryptedData = cipher.doFinal(encryptedData);
|
||||
System.out.println("Decrypted RSA-OAEP Data: " + new String(decryptedData));
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA-KEM encryption: encapsulates an AES key using RSA-OAEP.
|
||||
*
|
||||
* SAST/CBOM Notes:
|
||||
* - Algorithm: RSA-OAEP used as a Key Encapsulation Mechanism (KEM) for an AES
|
||||
* key.
|
||||
* - Parent Classification: RSA-OAEP based KEM.
|
||||
* - SAST: Recognized as a secure key encapsulation pattern; used as part of
|
||||
* hybrid encryption schemes.
|
||||
*/
|
||||
public void rsaKemEncryption(PublicKey rsaPublicKey) throws Exception {
|
||||
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
|
||||
keyGen.init(256); // 256-bit AES key.
|
||||
SecretKey aesKey = keyGen.generateKey();
|
||||
|
||||
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
|
||||
rsaCipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey);
|
||||
byte[] encryptedAesKey = rsaCipher.doFinal(aesKey.getEncoded());
|
||||
|
||||
System.out.println("RSA-KEM Encrypted AES Key: " + Base64.getEncoder().encodeToString(encryptedAesKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* Hybrid encryption: combines RSA-OAEP for key encapsulation with AES-GCM for
|
||||
* data encryption.
|
||||
*
|
||||
* SAST/CBOM Notes:
|
||||
* - Algorithms: RSA-OAEP (for encrypting the AES key) and AES-GCM (for
|
||||
* encrypting the data).
|
||||
* - Parent Classification: Hybrid Cryptosystem (RSA-OAEP + AES-GCM).
|
||||
* - SAST: This pattern is considered secure when implemented correctly;
|
||||
* recommended for large data encryption.
|
||||
*/
|
||||
public void hybridEncryption(PublicKey rsaPublicKey, String data) throws Exception {
|
||||
// Generate a 256-bit AES key for symmetric encryption.
|
||||
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
|
||||
keyGen.init(256);
|
||||
SecretKey aesKey = keyGen.generateKey();
|
||||
|
||||
// Encrypt the AES key using RSA-OAEP.
|
||||
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
|
||||
rsaCipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey);
|
||||
byte[] encryptedAesKey = rsaCipher.doFinal(aesKey.getEncoded());
|
||||
|
||||
// Encrypt the actual data using AES-GCM.
|
||||
Cipher aesCipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
byte[] iv = new byte[12]; // 12-byte IV recommended for GCM.
|
||||
new SecureRandom().nextBytes(iv);
|
||||
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv);
|
||||
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey, gcmSpec);
|
||||
byte[] encryptedData = aesCipher.doFinal(data.getBytes());
|
||||
|
||||
System.out.println(
|
||||
"Hybrid Encryption - Encrypted AES Key: " + Base64.getEncoder().encodeToString(encryptedAesKey));
|
||||
System.out.println("Hybrid Encryption - Encrypted Data: " + Base64.getEncoder().encodeToString(encryptedData));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package com.example.crypto.algorithms;
|
||||
|
||||
//import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
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.Mac;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* This class demonstrates encryption schemes using elliptic-curve
|
||||
* Diffie-Hellman (ECDH) and hybrid encryption methods, including a post-quantum
|
||||
* hybrid scheme.
|
||||
*
|
||||
* SAST/CBOM Classification:
|
||||
*
|
||||
* 1. EC Key Generation & ECDH Key Agreement: - Parent Classification:
|
||||
* Asymmetric Key Generation / Key Agreement. - SAST: Secure when using
|
||||
* established curves (secp256r1) and reputable providers (BouncyCastle).
|
||||
*
|
||||
* 2. ECDH Hybrid Encryption: - Parent Classification: Hybrid Cryptosystem (ECDH
|
||||
* + AEAD). - SAST: Uses ECDH for key agreement and AES/GCM for encryption.
|
||||
* However, the derivation of an AES key by applying a single SHA-256 hash to
|
||||
* the shared secret may be flagged as a weak key derivation method. A dedicated
|
||||
* KDF (e.g., HKDF) is recommended.
|
||||
*
|
||||
* 3. Post-Quantum Hybrid Encryption: - Parent Classification: Hybrid
|
||||
* Cryptosystem (Classical ECDH + Post-Quantum Secret + KDF + AEAD). - SAST:
|
||||
* Combining classical and post-quantum components is advanced and secure if
|
||||
* implemented properly. The custom HKDF expand function provided here is
|
||||
* simplistic and may be flagged in a CBOM analysis; a standard HKDF library
|
||||
* should be used in production.
|
||||
*/
|
||||
public class Encryption2 {
|
||||
|
||||
// static {
|
||||
// Security.addProvider(new BouncyCastleProvider());
|
||||
// }
|
||||
/**
|
||||
* Generates an Elliptic Curve (EC) key pair using the secp256r1 curve.
|
||||
*
|
||||
* SAST/CBOM Notes: - Algorithm: EC key pair generation. - Parent
|
||||
* Classification: Asymmetric Key Generation. - SAST: Considered secure when
|
||||
* using strong randomness and a reputable provider.
|
||||
*
|
||||
* @return an EC KeyPair.
|
||||
*/
|
||||
public KeyPair generateECKeyPair() throws Exception {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "BC");
|
||||
keyPairGenerator.initialize(new ECGenParameterSpec("secp256r1"), new SecureRandom());
|
||||
return keyPairGenerator.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a shared secret using Elliptic Curve Diffie-Hellman (ECDH).
|
||||
*
|
||||
* SAST/CBOM Notes: - Algorithm: ECDH key agreement. - Parent
|
||||
* Classification: Asymmetric Key Agreement. - SAST: Secure when both
|
||||
* parties use strong EC keys and proper randomness.
|
||||
*
|
||||
* @param privateKey the private key of one party.
|
||||
* @param publicKey the public key of the other party.
|
||||
* @return the derived shared secret as a byte array.
|
||||
*/
|
||||
public byte[] deriveSharedSecret(PrivateKey privateKey, PublicKey publicKey) throws Exception {
|
||||
KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH", "BC");
|
||||
keyAgreement.init(privateKey);
|
||||
keyAgreement.doPhase(publicKey, true);
|
||||
return keyAgreement.generateSecret();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs hybrid encryption using ECDH to derive a shared secret, then
|
||||
* derives an AES key by hashing the shared secret with SHA-256, and finally
|
||||
* encrypts the data with AES-GCM.
|
||||
*
|
||||
* SAST/CBOM Notes: - Parent Classification: Hybrid Cryptosystem (ECDH +
|
||||
* AES-GCM). - SAST: While ECDH and AES-GCM are secure, the key derivation
|
||||
* method here (a single SHA-256 hash) is not as robust as using a dedicated
|
||||
* KDF. This approach may be flagged and is recommended for improvement.
|
||||
*
|
||||
* @param recipientPublicKey the recipient's public EC key.
|
||||
* @param data the plaintext data to encrypt.
|
||||
*/
|
||||
public void ecdhHybridEncryption(PublicKey recipientPublicKey, String data) throws Exception {
|
||||
// Generate an ephemeral EC key pair for the sender.
|
||||
KeyPair senderKeyPair = generateECKeyPair();
|
||||
// Derive the shared secret using ECDH.
|
||||
byte[] sharedSecret = deriveSharedSecret(senderKeyPair.getPrivate(), recipientPublicKey);
|
||||
|
||||
// Derive an AES key by hashing the shared secret with SHA-256.
|
||||
// SAST Note: Using a direct hash for key derivation is simplistic and may be
|
||||
// flagged.
|
||||
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
||||
byte[] aesKeyBytes = sha256.digest(sharedSecret);
|
||||
// Use the first 16 bytes (128 bits) as the AES key.
|
||||
SecretKey aesKey = new SecretKeySpec(aesKeyBytes, 0, 16, "AES");
|
||||
|
||||
// Encrypt the data using AES-GCM.
|
||||
Cipher aesCipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
byte[] iv = new byte[12]; // 12-byte IV recommended for GCM.
|
||||
new SecureRandom().nextBytes(iv);
|
||||
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv); // 128-bit authentication tag.
|
||||
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey, gcmSpec);
|
||||
byte[] encryptedData = aesCipher.doFinal(data.getBytes());
|
||||
|
||||
System.out.println(
|
||||
"ECDH Hybrid Encryption - Encrypted Data: " + Base64.getEncoder().encodeToString(encryptedData));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs post-quantum hybrid encryption by combining a classical
|
||||
* ECDH-derived secret with a post-quantum shared secret. The two secrets
|
||||
* are combined using a custom HKDF expansion, and the derived key is used
|
||||
* to encrypt data with AES-GCM.
|
||||
*
|
||||
* SAST/CBOM Notes: - Parent Classification: Hybrid Cryptosystem (Classical
|
||||
* ECDH + Post-Quantum Secret + KDF + AES-GCM). - SAST: The combination of
|
||||
* classical and post-quantum secrets is a modern approach. However, the
|
||||
* custom HKDF expand function is simplistic and may be flagged as insecure.
|
||||
* Use a standard HKDF implementation in production.
|
||||
*
|
||||
* @param ecPublicKey the recipient's EC public key.
|
||||
* @param pqSharedSecret the post-quantum shared secret from a separate
|
||||
* algorithm.
|
||||
*/
|
||||
public void postQuantumHybridEncryption(PublicKey ecPublicKey, byte[] pqSharedSecret) throws Exception {
|
||||
// Step 1: Perform classical ECDH key agreement to derive a shared secret.
|
||||
byte[] ecdhSharedSecret = deriveSharedSecret(generateECKeyPair().getPrivate(), ecPublicKey);
|
||||
|
||||
// Step 2: Combine the ECDH secret and the post-quantum secret using a
|
||||
// simplified HKDF expansion.
|
||||
// SAST Note: This custom HKDF implementation is minimal and does not follow the
|
||||
// full HKDF spec.
|
||||
byte[] combinedSecret = hkdfExpand(ecdhSharedSecret, pqSharedSecret, 32);
|
||||
// Use the first 16 bytes as the AES key (128-bit key).
|
||||
SecretKey aesKey = new SecretKeySpec(combinedSecret, 0, 16, "AES");
|
||||
|
||||
// Step 3: Encrypt the data using AES-GCM.
|
||||
Cipher aesCipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
byte[] iv = new byte[12]; // 12-byte IV recommended for GCM.
|
||||
new SecureRandom().nextBytes(iv);
|
||||
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv);
|
||||
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey, gcmSpec);
|
||||
byte[] encryptedData = aesCipher.doFinal("Post-Quantum Hybrid Encryption Data".getBytes());
|
||||
|
||||
System.out.println("Post-Quantum Hybrid Encryption - Encrypted Data: "
|
||||
+ Base64.getEncoder().encodeToString(encryptedData));
|
||||
}
|
||||
|
||||
/**
|
||||
* A simplified HKDF expansion function that uses HMAC-SHA256 to derive a
|
||||
* key of a desired length.
|
||||
*
|
||||
* SAST/CBOM Notes: - Parent Classification: Key Derivation Function (KDF).
|
||||
* - SAST: Custom KDF implementations are risky if not thoroughly vetted.
|
||||
* This simple HKDF expand function lacks the full HKDF mechanism (e.g.,
|
||||
* multiple iterations, info, and context parameters) and may be flagged. It
|
||||
* is recommended to use a standardized HKDF library.
|
||||
*
|
||||
* @param inputKey the input key material.
|
||||
* @param salt a salt value (here, the post-quantum shared secret is used as
|
||||
* the salt).
|
||||
* @param length the desired length of the derived key.
|
||||
* @return a derived key of the specified length.
|
||||
*/
|
||||
private byte[] hkdfExpand(byte[] inputKey, byte[] salt, int length) throws Exception {
|
||||
Mac hmac = Mac.getInstance("HmacSHA256");
|
||||
SecretKey secretKey = new SecretKeySpec(salt, "HmacSHA256");
|
||||
hmac.init(secretKey);
|
||||
byte[] extractedKey = hmac.doFinal(inputKey);
|
||||
return Arrays.copyOf(extractedKey, length);
|
||||
}
|
||||
}
|
||||
313
java/ql/test/experimental/library-tests/quantum/jca/Hash.java
Normal file
313
java/ql/test/experimental/library-tests/quantum/jca/Hash.java
Normal file
@@ -0,0 +1,313 @@
|
||||
package com.example.crypto.algorithms;
|
||||
|
||||
// import org.bouncycastle.crypto.digests.SHA3Digest;
|
||||
// import org.bouncycastle.crypto.digests.Blake2bDigest;
|
||||
// import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.*;
|
||||
import java.util.Base64;
|
||||
import java.util.Properties;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
|
||||
/**
|
||||
* This class demonstrates various hashing, HMAC, and password hashing
|
||||
* techniques.
|
||||
*
|
||||
* SAST/CBOM Classification Notes:
|
||||
*
|
||||
* 1. simpleSHA256Hash: - Parent Classification: Cryptographic Hash Function. -
|
||||
* SAST: Uses SHA-256, which is widely regarded as secure.
|
||||
*
|
||||
* 2. insecureMD5Hash: - Parent Classification: Cryptographic Hash Function. -
|
||||
* SAST: MD5 is cryptographically broken and should be flagged as insecure.
|
||||
*
|
||||
* 3. hashWithBouncyCastleSHA3: - Parent Classification: Cryptographic Hash
|
||||
* Function (SHA3). - SAST: Uses SHA3-256 from BouncyCastle; considered secure.
|
||||
*
|
||||
* 4. hashWithBouncyCastleBlake2b: - Parent Classification: Cryptographic Hash
|
||||
* Function (BLAKE2). - SAST: Uses BLAKE2b-512; considered secure if used
|
||||
* correctly.
|
||||
*
|
||||
* 5. hashAndSign & verifyHashSignature: - Parent Classification: Digital
|
||||
* Signature (RSA-based). - SAST: Uses SHA256withRSA for signing and
|
||||
* verification; secure if key management is proper.
|
||||
*
|
||||
* 6. hashForDataIntegrityCheck: - Parent Classification: Data Integrity Check.
|
||||
* - SAST: Uses SHA-256 to verify integrity; considered secure.
|
||||
*
|
||||
* 7. hashWithVariousAlgorithms: - Parent Classification: Cryptographic Hash
|
||||
* Function. - SAST: Iterates through multiple algorithms; insecure algorithms
|
||||
* (MD5, SHA-1) may be flagged.
|
||||
*
|
||||
* 8. hmacWithVariousAlgorithms: - Parent Classification: Message Authentication
|
||||
* Code (MAC). - SAST: Iterates through various HMAC algorithms; HmacSHA1 is
|
||||
* considered weaker than SHA256 and above.
|
||||
*
|
||||
* 9. hashForPasswordStorage: - Parent Classification: Password-Based Key
|
||||
* Derivation Function (PBKDF). - SAST: Uses PBKDF2WithHmacSHA256 with salt and
|
||||
* iteration count; considered secure, though iteration counts should be
|
||||
* reviewed against current standards.
|
||||
*
|
||||
* 10. hashFromUnknownConfig: - Parent Classification: Dynamic Cryptographic
|
||||
* Hash Function. - SAST: Loading the hash algorithm from an external
|
||||
* configuration introduces risk of misconfiguration.
|
||||
*
|
||||
* 11. insecureHashBasedRNG: - Parent Classification: Pseudo-Random Number
|
||||
* Generator (PRNG) using hash. - SAST: Uses a fixed seed with various hash
|
||||
* algorithms; flagged as insecure due to predictability.
|
||||
*/
|
||||
public class Hash {
|
||||
|
||||
// static {
|
||||
// Security.addProvider(new BouncyCastleProvider());
|
||||
// }
|
||||
/**
|
||||
* Computes a SHA-256 hash of static test data.
|
||||
*
|
||||
* CBOM/SAST Classification: - Uses SHA-256: Classified as secure.
|
||||
*/
|
||||
public void simpleSHA256Hash() throws Exception {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] hash = digest.digest("Simple Test Data".getBytes());
|
||||
System.out.println("SHA-256 Hash: " + Base64.getEncoder().encodeToString(hash));
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes an MD5 hash of static data.
|
||||
*
|
||||
* CBOM/SAST Classification: - Uses MD5: Classified as insecure. - SAST: MD5
|
||||
* is deprecated for cryptographic purposes due to collision
|
||||
* vulnerabilities.
|
||||
*/
|
||||
public void insecureMD5Hash() throws Exception {
|
||||
MessageDigest md5Digest = MessageDigest.getInstance("MD5");
|
||||
byte[] hash = md5Digest.digest("Weak Hash Example".getBytes());
|
||||
System.out.println("MD5 Hash (Insecure): " + Base64.getEncoder().encodeToString(hash));
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Computes a SHA3-256 hash using BouncyCastle's SHA3Digest.
|
||||
// *
|
||||
// * CBOM/SAST Classification:
|
||||
// * - Uses SHA3-256: Classified as secure.
|
||||
// * - SAST: BouncyCastle's implementation is considered reliable.
|
||||
// */
|
||||
// public void hashWithBouncyCastleSHA3(String input) {
|
||||
// SHA3Digest digest = new SHA3Digest(256);
|
||||
// byte[] inputBytes = input.getBytes();
|
||||
// digest.update(inputBytes, 0, inputBytes.length);
|
||||
// byte[] hash = new byte[digest.getDigestSize()];
|
||||
// digest.doFinal(hash, 0);
|
||||
// System.out.println("SHA3-256 (BC) Hash: " + Base64.getEncoder().encodeToString(hash));
|
||||
// }
|
||||
// /**
|
||||
// * Computes a BLAKE2b-512 hash using BouncyCastle's Blake2bDigest.
|
||||
// *
|
||||
// * CBOM/SAST Classification:
|
||||
// * - Uses BLAKE2b-512: Classified as secure.
|
||||
// * - SAST: BLAKE2b is modern and fast, considered secure when used correctly.
|
||||
// */
|
||||
// public void hashWithBouncyCastleBlake2b(String input) {
|
||||
// Blake2bDigest digest = new Blake2bDigest(512);
|
||||
// byte[] inputBytes = input.getBytes();
|
||||
// digest.update(inputBytes, 0, inputBytes.length);
|
||||
// byte[] hash = new byte[digest.getDigestSize()];
|
||||
// digest.doFinal(hash, 0);
|
||||
// System.out.println("BLAKE2b-512 (BC) Hash: " + Base64.getEncoder().encodeToString(hash));
|
||||
// }
|
||||
/**
|
||||
* Signs the hash of the input using SHA256withRSA.
|
||||
*
|
||||
* CBOM/SAST Classification: - Digital Signature (RSA): Classified as secure
|
||||
* if keys are managed correctly. - SAST: The combination of SHA256 and RSA
|
||||
* is a standard and secure pattern.
|
||||
*
|
||||
* @param input The input data to be signed.
|
||||
* @param privateKey The RSA private key used for signing.
|
||||
*/
|
||||
public void hashAndSign(String input, PrivateKey privateKey) throws Exception {
|
||||
Signature signature = Signature.getInstance("SHA256withRSA");
|
||||
signature.initSign(privateKey);
|
||||
signature.update(input.getBytes());
|
||||
byte[] signedData = signature.sign();
|
||||
System.out.println("Signed Hash: " + Base64.getEncoder().encodeToString(signedData));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the signature of the input data.
|
||||
*
|
||||
* CBOM/SAST Classification: - Digital Signature Verification: Classified as
|
||||
* secure when using SHA256withRSA. - SAST: Should correctly verify that the
|
||||
* signed hash matches the input.
|
||||
*
|
||||
* @param input The original input data.
|
||||
* @param signedHash The signed hash to verify.
|
||||
* @param publicKey The RSA public key corresponding to the private key that
|
||||
* signed the data.
|
||||
* @return true if the signature is valid, false otherwise.
|
||||
*/
|
||||
public boolean verifyHashSignature(String input, byte[] signedHash, PublicKey publicKey) throws Exception {
|
||||
Signature signature = Signature.getInstance("SHA256withRSA");
|
||||
signature.initVerify(publicKey);
|
||||
signature.update(input.getBytes());
|
||||
return signature.verify(signedHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a SHA-256 hash for data integrity checking and compares it with
|
||||
* an expected hash.
|
||||
*
|
||||
* CBOM/SAST Classification: - Data Integrity: Uses SHA-256 for integrity
|
||||
* checks, which is secure. - SAST: A correct implementation for verifying
|
||||
* data has not been tampered with.
|
||||
*
|
||||
* @param data The input data.
|
||||
* @param expectedHash The expected Base64-encoded hash.
|
||||
*/
|
||||
public void hashForDataIntegrityCheck(String data, String expectedHash) throws Exception {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] hash = digest.digest(data.getBytes());
|
||||
String computedHash = Base64.getEncoder().encodeToString(hash);
|
||||
System.out.println("Computed Hash: " + computedHash);
|
||||
System.out.println("Validation: " + (computedHash.equals(expectedHash) ? "Pass" : "Fail"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes hashes of the input data using various algorithms.
|
||||
*
|
||||
* CBOM/SAST Classification: - Cryptographic Hash Functions: Iterates
|
||||
* through multiple hash functions. - SAST: While many are secure (e.g.,
|
||||
* SHA-256, SHA-512, SHA3), MD5 and SHA-1 are insecure and should be flagged
|
||||
* if used in security-critical contexts.
|
||||
*
|
||||
* @param input The input data to hash.
|
||||
*/
|
||||
public void hashWithVariousAlgorithms(String input) throws Exception {
|
||||
String[] algorithms = {"SHA-1", "SHA-224", "SHA-256", "SHA-384", "SHA-512", "SHA3-256", "SHA3-512",
|
||||
"BLAKE2B-512", "BLAKE2S-256", "MD5"};
|
||||
for (String algorithm : algorithms) {
|
||||
MessageDigest digest = MessageDigest.getInstance(algorithm);
|
||||
byte[] hash = digest.digest(input.getBytes());
|
||||
System.out.println(algorithm + " Hash: " + Base64.getEncoder().encodeToString(hash));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes HMACs of the input data using various algorithms.
|
||||
*
|
||||
* CBOM/SAST Classification: - Message Authentication Code (MAC): Iterates
|
||||
* through different HMAC algorithms. - SAST: HmacSHA256, HmacSHA384,
|
||||
* HmacSHA512, HmacSHA3-256, and HmacSHA3-512 are secure; HmacSHA1 is
|
||||
* considered less secure and may be flagged.
|
||||
*
|
||||
* @param input The input data.
|
||||
* @param key The secret key used for HMAC computation.
|
||||
*/
|
||||
public void hmacWithVariousAlgorithms(String input, byte[] key) throws Exception {
|
||||
String[] algorithms = {"HmacSHA1", "HmacSHA256", "HmacSHA384", "HmacSHA512", "HmacSHA3-256", "HmacSHA3-512"};
|
||||
for (String algorithm : algorithms) {
|
||||
Mac mac = Mac.getInstance(algorithm);
|
||||
SecretKey secretKey = new SecretKeySpec(key, algorithm);
|
||||
mac.init(secretKey);
|
||||
byte[] hmac = mac.doFinal(input.getBytes());
|
||||
System.out.println(algorithm + " HMAC: " + Base64.getEncoder().encodeToString(hmac));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a PBKDF2 hash for password storage.
|
||||
*
|
||||
* CBOM/SAST Classification: - Password-Based Key Derivation Function
|
||||
* (PBKDF): Uses PBKDF2WithHmacSHA256. - SAST: Considered secure when using
|
||||
* a strong salt and an appropriate iteration count. Note: The iteration
|
||||
* count (10000) should be reviewed against current security standards.
|
||||
*
|
||||
* @param password The password to hash.
|
||||
*/
|
||||
public void hashForPasswordStorage(String password) throws Exception {
|
||||
byte[] salt = generateSecureSalt(16);
|
||||
// 10,000 iterations and a 256-bit derived key.
|
||||
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 10000, 256);
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||
byte[] hash = factory.generateSecret(spec).getEncoded();
|
||||
System.out.println("PBKDF2 Hash: " + Base64.getEncoder().encodeToString(hash));
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically loads a hash algorithm from configuration and computes a
|
||||
* hash.
|
||||
*
|
||||
* CBOM/SAST Classification: - Dynamic Cryptographic Hash Selection:
|
||||
* Algorithm is loaded from a config file. - SAST: May be flagged as risky
|
||||
* because an insecure or unintended algorithm might be chosen.
|
||||
*/
|
||||
public void hashFromUnknownConfig() throws Exception {
|
||||
String algorithm = loadHashAlgorithmFromConfig("config.properties");
|
||||
MessageDigest digest = MessageDigest.getInstance(algorithm);
|
||||
byte[] hash = digest.digest("Config-based Hashing".getBytes());
|
||||
System.out.println("Dynamically Loaded Hash Algorithm (" + algorithm + "): "
|
||||
+ Base64.getEncoder().encodeToString(hash));
|
||||
}
|
||||
|
||||
/**
|
||||
* Demonstrates an insecure method for generating pseudo-random bytes by
|
||||
* using a fixed seed with hash algorithms.
|
||||
*
|
||||
* CBOM/SAST Classification: - Insecure RNG: Uses a fixed seed with various
|
||||
* hash algorithms. - SAST: This approach is insecure because it produces
|
||||
* predictable output and should be flagged.
|
||||
*/
|
||||
public void insecureHashBasedRNG() throws Exception {
|
||||
String[] algorithms = {"SHA-256", "SHA-512", "SHA3-256", "SHA3-512"};
|
||||
for (String algorithm : algorithms) {
|
||||
MessageDigest digest = MessageDigest.getInstance(algorithm);
|
||||
byte[] seed = "fixed-seed".getBytes(); // Fixed seed: insecure and predictable.
|
||||
digest.update(seed);
|
||||
byte[] pseudoRandomBytes = digest.digest();
|
||||
System.out.println("Insecure RNG using " + algorithm + ": "
|
||||
+ Base64.getEncoder().encodeToString(pseudoRandomBytes));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the hash algorithm from an external configuration file.
|
||||
*
|
||||
* CBOM/SAST Classification: - Dynamic Configuration: External config
|
||||
* loading. - SAST: The use of external configuration may introduce risks if
|
||||
* the config file is compromised.
|
||||
*
|
||||
* @param configPath Path to the configuration file.
|
||||
* @return The hash algorithm to be used (default is SHA-256).
|
||||
*/
|
||||
private String loadHashAlgorithmFromConfig(String configPath) {
|
||||
Properties properties = new Properties();
|
||||
try (FileInputStream fis = new FileInputStream(configPath)) {
|
||||
properties.load(fis);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return properties.getProperty("hash.algorithm", "SHA-256");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a secure salt using a cryptographically strong random number
|
||||
* generator.
|
||||
*
|
||||
* CBOM/SAST Classification: - Secure Salt Generation: Uses SecureRandom. -
|
||||
* SAST: This is a best-practice approach for generating salts for password
|
||||
* hashing.
|
||||
*
|
||||
* @param length The desired salt length.
|
||||
* @return A byte array representing the salt.
|
||||
*/
|
||||
private byte[] generateSecureSalt(int length) {
|
||||
byte[] salt = new byte[length];
|
||||
new SecureRandom().nextBytes(salt);
|
||||
return salt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
package com.example.crypto.artifacts;
|
||||
|
||||
// import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import java.security.*;
|
||||
import java.util.Base64;
|
||||
import java.util.random.*;
|
||||
import java.util.Properties;
|
||||
import java.util.Random;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class IVArtifact {
|
||||
|
||||
// static {
|
||||
// Security.addProvider(new BouncyCastleProvider()); // Ensure BouncyCastle is available
|
||||
// }
|
||||
/**
|
||||
* Simple Case: Generates a secure IV and encrypts with
|
||||
* AES/CBC/PKCS5Padding.
|
||||
*/
|
||||
public void simpleIVEncryption() throws Exception {
|
||||
SecretKey key = generateAESKey();
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(secureIV(16));
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
|
||||
byte[] ciphertext = cipher.doFinal("Simple Test Data".getBytes());
|
||||
}
|
||||
|
||||
public void encryptWithIV(byte[] plaintext, SecretKey key, IvParameterSpec ivSpec, String cipherAlgorithm)
|
||||
throws Exception {
|
||||
Cipher cipher = Cipher.getInstance(cipherAlgorithm);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
|
||||
byte[] ciphertext = cipher.doFinal(plaintext);
|
||||
}
|
||||
|
||||
public void complexIVFlow() {
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(useSecureMethod() ? secureIV(16) : insecureIV(16));
|
||||
processIV(ivSpec);
|
||||
|
||||
// Example dynamic cipher selection with IV usage
|
||||
String cipherAlgorithm = loadCipherAlgorithm();
|
||||
try {
|
||||
encryptWithIV("Sensitive Data".getBytes(), generateAESKey(), ivSpec, cipherAlgorithm);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean useSecureMethod() {
|
||||
return System.currentTimeMillis() % 2 == 0;
|
||||
}
|
||||
|
||||
private void processIV(IvParameterSpec ivSpec) {
|
||||
String ivBase64 = Base64.getEncoder().encodeToString(ivSpec.getIV());
|
||||
}
|
||||
|
||||
private String loadCipherAlgorithm() {
|
||||
Properties properties = new Properties();
|
||||
try {
|
||||
properties.load(new FileInputStream("crypto-config.properties"));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return properties.getProperty("cipher.algorithm", "AES/CBC/PKCS5Padding");
|
||||
}
|
||||
|
||||
private SecretKey generateAESKey() throws NoSuchAlgorithmException {
|
||||
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
|
||||
keyGen.init(256);
|
||||
return keyGen.generateKey();
|
||||
}
|
||||
|
||||
private byte[] secureIV(int length) {
|
||||
byte[] iv = new byte[length];
|
||||
new SecureRandom().nextBytes(iv);
|
||||
return iv;
|
||||
}
|
||||
|
||||
private byte[] insecureIV(int length) {
|
||||
byte[] iv = new byte[length];
|
||||
new Random().nextBytes(iv);
|
||||
return iv;
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
// 1. Direct Fixed IV Usage
|
||||
// -------------------------------
|
||||
/**
|
||||
* Encrypts plaintext using AES-GCM with a fixed IV (all zeros). This is an
|
||||
* insecure practice as IV reuse in AES-GCM undermines confidentiality and
|
||||
* integrity.
|
||||
*
|
||||
* @param key The AES key.
|
||||
* @param plaintext The plaintext to encrypt.
|
||||
* @return The ciphertext.
|
||||
* @throws Exception if encryption fails.
|
||||
*/
|
||||
public byte[] encryptWithFixedIV(SecretKey key, byte[] plaintext) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
byte[] fixedIV = new byte[12]; // 12-byte fixed IV (all zeros)
|
||||
GCMParameterSpec spec = new GCMParameterSpec(128, fixedIV);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
|
||||
return cipher.doFinal(plaintext);
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
// 2. Cached IV Usage
|
||||
// -------------------------------
|
||||
// Cache an IV for reuse in multiple encryptions (insecure)
|
||||
private byte[] cachedIV = null;
|
||||
|
||||
/**
|
||||
* Encrypts plaintext using AES-GCM with an IV cached from the first call.
|
||||
* Reusing the same IV across multiple encryptions is insecure.
|
||||
*
|
||||
* @param key The AES key.
|
||||
* @param plaintext The plaintext to encrypt.
|
||||
* @return The ciphertext.
|
||||
* @throws Exception if encryption fails.
|
||||
*/
|
||||
public byte[] encryptWithCachedIV(SecretKey key, byte[] plaintext) throws Exception {
|
||||
if (cachedIV == null) {
|
||||
cachedIV = new byte[12];
|
||||
new SecureRandom().nextBytes(cachedIV);
|
||||
}
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
GCMParameterSpec spec = new GCMParameterSpec(128, cachedIV);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
|
||||
return cipher.doFinal(plaintext);
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
// 3. Indirect IV Reuse via Deterministic Derivation
|
||||
// -------------------------------
|
||||
/**
|
||||
* Encrypts plaintext using AES-GCM with an IV derived deterministically
|
||||
* from a constant. This method computes a SHA-256 hash of a constant string
|
||||
* and uses the first 12 bytes as the IV. Such derived IVs are fixed and
|
||||
* must not be reused.
|
||||
*
|
||||
* @param key The AES key.
|
||||
* @param plaintext The plaintext to encrypt.
|
||||
* @return The ciphertext.
|
||||
* @throws Exception if encryption fails.
|
||||
*/
|
||||
public byte[] encryptWithDerivedIV(SecretKey key, byte[] plaintext) throws Exception {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] constantHash = digest.digest("fixedConstant".getBytes("UTF-8"));
|
||||
byte[] derivedIV = Arrays.copyOf(constantHash, 12); // Deterministically derived IV
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
GCMParameterSpec spec = new GCMParameterSpec(128, derivedIV);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
|
||||
return cipher.doFinal(plaintext);
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
// 4. Reusing a Single IV Across Multiple Messages
|
||||
// -------------------------------
|
||||
/**
|
||||
* Encrypts an array of plaintext messages using AES-GCM with the same IV
|
||||
* for every message. Reusing an IV across messages is insecure in
|
||||
* authenticated encryption schemes.
|
||||
*
|
||||
* @param key The AES key.
|
||||
* @param plaintexts An array of plaintext messages.
|
||||
* @return An array of ciphertexts.
|
||||
* @throws Exception if encryption fails.
|
||||
*/
|
||||
public byte[][] encryptMultipleMessagesWithSameIV(SecretKey key, byte[][] plaintexts) throws Exception {
|
||||
byte[] iv = new byte[12];
|
||||
new SecureRandom().nextBytes(iv); // Generate once and reuse
|
||||
byte[][] ciphertexts = new byte[plaintexts.length][];
|
||||
for (int i = 0; i < plaintexts.length; i++) {
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
|
||||
ciphertexts[i] = cipher.doFinal(plaintexts[i]);
|
||||
}
|
||||
return ciphertexts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts the given plaintext using AES-GCM with the provided key and IV.
|
||||
*
|
||||
* @param key The AES key.
|
||||
* @param ivSpec The IV specification.
|
||||
* @param plaintext The plaintext to encrypt.
|
||||
* @return The ciphertext (IV is not prepended here for clarity).
|
||||
* @throws Exception if encryption fails.
|
||||
*/
|
||||
public byte[] encrypt(SecretKey key, IvParameterSpec ivSpec, byte[] plaintext) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
// Use 128-bit authentication tag length.
|
||||
GCMParameterSpec spec = new GCMParameterSpec(128, ivSpec.getIV());
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
|
||||
return cipher.doFinal(plaintext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Example 1: Reuses the same IvParameterSpec object across two encryption
|
||||
* calls.
|
||||
*
|
||||
* @param key The AES key.
|
||||
* @param plaintext The plaintext to encrypt.
|
||||
* @return An array containing two ciphertexts generated with the same
|
||||
* IvParameterSpec.
|
||||
* @throws Exception if encryption fails.
|
||||
*/
|
||||
public byte[][] encryptUsingSameIvParameterSpec(SecretKey key, byte[] plaintext) throws Exception {
|
||||
// Fixed IV (all zeros for demonstration purposes; insecure in production)
|
||||
byte[] fixedIV = new byte[12];
|
||||
IvParameterSpec fixedIvSpec = new IvParameterSpec(fixedIV);
|
||||
// Encrypt the plaintext twice using the same IvParameterSpec.
|
||||
byte[] ciphertext1 = encrypt(key, fixedIvSpec, plaintext);
|
||||
byte[] ciphertext2 = encrypt(key, fixedIvSpec, plaintext);
|
||||
return new byte[][]{ciphertext1, ciphertext2};
|
||||
}
|
||||
|
||||
/**
|
||||
* Example 2: Creates two different IvParameterSpec objects that share the
|
||||
* same underlying IV array.
|
||||
*
|
||||
* @param key The AES key.
|
||||
* @param plaintext The plaintext to encrypt.
|
||||
* @return An array containing two ciphertexts generated with two
|
||||
* IvParameterSpec objects constructed from the same IV array.
|
||||
* @throws Exception if encryption fails.
|
||||
*/
|
||||
public byte[][] encryptUsingDifferentIvSpecSameIVArray(SecretKey key, byte[] plaintext) throws Exception {
|
||||
// Create a fixed IV array (all zeros for demonstration; insecure in production)
|
||||
byte[] fixedIV = new byte[12];
|
||||
// Create two distinct IvParameterSpec objects from the same IV array reference.
|
||||
IvParameterSpec ivSpec1 = new IvParameterSpec(fixedIV);
|
||||
IvParameterSpec ivSpec2 = new IvParameterSpec(fixedIV);
|
||||
// Encrypt the plaintext twice.
|
||||
byte[] ciphertext1 = encrypt(key, ivSpec1, plaintext);
|
||||
byte[] ciphertext2 = encrypt(key, ivSpec2, plaintext);
|
||||
return new byte[][]{ciphertext1, ciphertext2};
|
||||
}
|
||||
|
||||
// -------------------------------
|
||||
// Main Method for Demonstration
|
||||
// -------------------------------
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
IVArtifact test = new IVArtifact();
|
||||
KeyGenerator kg = KeyGenerator.getInstance("AES");
|
||||
kg.init(256, new SecureRandom());
|
||||
SecretKey key = kg.generateKey();
|
||||
byte[] plaintext = "Sensitive Data".getBytes();
|
||||
|
||||
// Example 1: Fixed IV usage
|
||||
byte[] fixedIVCipher1 = test.encryptWithFixedIV(key, plaintext);
|
||||
byte[] fixedIVCipher2 = test.encryptWithFixedIV(key, plaintext);
|
||||
System.out.println("Fixed IV Encryption 1: " + Base64.getEncoder().encodeToString(fixedIVCipher1));
|
||||
System.out.println("Fixed IV Encryption 2: " + Base64.getEncoder().encodeToString(fixedIVCipher2));
|
||||
|
||||
// Example 2: Cached IV usage
|
||||
byte[] cachedIVCipher1 = test.encryptWithCachedIV(key, plaintext);
|
||||
byte[] cachedIVCipher2 = test.encryptWithCachedIV(key, plaintext);
|
||||
System.out.println("Cached IV Encryption 1: " + Base64.getEncoder().encodeToString(cachedIVCipher1));
|
||||
System.out.println("Cached IV Encryption 2: " + Base64.getEncoder().encodeToString(cachedIVCipher2));
|
||||
|
||||
// Example 3: Indirect IV (derived)
|
||||
byte[] derivedIVCipher = test.encryptWithDerivedIV(key, plaintext);
|
||||
System.out.println("Derived IV Encryption: " + Base64.getEncoder().encodeToString(derivedIVCipher));
|
||||
|
||||
// Example 4: Reusing the same IV across multiple messages
|
||||
byte[][] messages = {"Message One".getBytes(), "Message Two".getBytes(), "Message Three".getBytes()};
|
||||
byte[][] multiCiphers = test.encryptMultipleMessagesWithSameIV(key, messages);
|
||||
for (int i = 0; i < multiCiphers.length; i++) {
|
||||
System.out.println("Multi-message Encryption " + (i + 1) + ": "
|
||||
+ Base64.getEncoder().encodeToString(multiCiphers[i]));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.example.crypto.artifacts;
|
||||
|
||||
// import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import java.io.FileInputStream;
|
||||
import java.security.*;
|
||||
import java.security.spec.*;
|
||||
import java.util.Properties;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
public class KeyArtifact {
|
||||
|
||||
// static {
|
||||
// Security.addProvider(new BouncyCastleProvider());
|
||||
// }
|
||||
public void generateSymmetricKeys() throws NoSuchAlgorithmException {
|
||||
// AES Key Generation (Default Provider)
|
||||
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
|
||||
keyGen.init(256);
|
||||
SecretKey aesKeyJDK = keyGen.generateKey();
|
||||
|
||||
// AES Key Generation (BouncyCastle)
|
||||
keyGen = KeyGenerator.getInstance("AES");
|
||||
keyGen.init(256);
|
||||
SecretKey aesKeyBC = keyGen.generateKey();
|
||||
}
|
||||
|
||||
public void generateAsymmetricKeys() throws NoSuchAlgorithmException {
|
||||
// RSA Key Generation (JDK Default)
|
||||
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
|
||||
keyPairGen.initialize(2048);
|
||||
KeyPair rsaPairJDK = keyPairGen.generateKeyPair();
|
||||
|
||||
// RSA Key Generation (BouncyCastle)
|
||||
keyPairGen = KeyPairGenerator.getInstance("RSA");
|
||||
keyPairGen.initialize(2048);
|
||||
KeyPair rsaPairBC = keyPairGen.generateKeyPair();
|
||||
|
||||
// EC Key Generation
|
||||
keyPairGen = KeyPairGenerator.getInstance("EC");
|
||||
keyPairGen.initialize(256);
|
||||
KeyPair ecPair = keyPairGen.generateKeyPair();
|
||||
}
|
||||
|
||||
public void importExportRSAKeys(KeyPair keyPair) throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
// Export Public Key
|
||||
byte[] pubKeyBytes = keyPair.getPublic().getEncoded();
|
||||
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(pubKeyBytes);
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
PublicKey importedPubKey = keyFactory.generatePublic(pubKeySpec);
|
||||
|
||||
// Export Private Key
|
||||
byte[] privKeyBytes = keyPair.getPrivate().getEncoded();
|
||||
PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(privKeyBytes);
|
||||
PrivateKey importedPrivKey = keyFactory.generatePrivate(privKeySpec);
|
||||
}
|
||||
|
||||
public void dynamicAlgorithmSelection() throws Exception {
|
||||
// Load algorithm from configuration
|
||||
Properties properties = new Properties();
|
||||
properties.load(new FileInputStream("crypto-config.properties"));
|
||||
String algorithm = properties.getProperty("key.algorithm", "AES");
|
||||
|
||||
KeyGenerator keyGen = KeyGenerator.getInstance(algorithm);
|
||||
keyGen.init(256);
|
||||
SecretKey dynamicKey = keyGen.generateKey();
|
||||
}
|
||||
|
||||
public KeyPair generateKeyPair(String algorithm) throws NoSuchAlgorithmException {
|
||||
// Wrapper for Key Generation
|
||||
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(algorithm);
|
||||
keyPairGen.initialize(2048);
|
||||
return keyPairGen.generateKeyPair();
|
||||
}
|
||||
|
||||
public void keySelectionFromArray() throws NoSuchAlgorithmException {
|
||||
// Selecting Algorithm Dynamically from an Array
|
||||
String[] algorithms = {"RSA", "EC", "Ed25519"};
|
||||
KeyPair[] keyPairs = new KeyPair[algorithms.length];
|
||||
|
||||
for (int i = 0; i < algorithms.length; i++) {
|
||||
keyPairs[i] = generateKeyPair(algorithms[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
package com.example.crypto.algorithms;
|
||||
|
||||
// import org.bouncycastle.crypto.generators.Argon2BytesGenerator;
|
||||
// import org.bouncycastle.crypto.params.Argon2Parameters;
|
||||
// import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.Properties;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* This class demonstrates multiple key derivation functions (KDFs) including
|
||||
* PBKDF2, scrypt, Argon2, raw hash derivation, HKDF, and dynamic algorithm
|
||||
* selection.
|
||||
*
|
||||
* SAST/CBOM Classification Notes:
|
||||
*
|
||||
* 1. PBKDF2 Examples: - Parent Classification: Password-Based Key Derivation
|
||||
* Function (PBKDF). - SAST: * pbkdf2DerivationBasic: Uses PBKDF2WithHmacSHA256
|
||||
* with 10,000 iterations - acceptable if parameters meet current standards. *
|
||||
* pbkdf2LowIteration: Uses only 10 iterations, flagged as insecure due to
|
||||
* insufficient iteration count. * pbkdf2HighIteration: Uses 1,000,000
|
||||
* iterations - secure (though performance may be impacted). * pbkdf2HmacSHA1:
|
||||
* Uses PBKDF2WithHmacSHA1 - flagged as weaker compared to SHA-256, though
|
||||
* sometimes seen in legacy systems. * pbkdf2HmacSHA512: Uses
|
||||
* PBKDF2WithHmacSHA512 - classified as secure.
|
||||
*
|
||||
* 2. Scrypt Examples: - Parent Classification: Memory-Hard Key Derivation
|
||||
* Function. - SAST: * scryptWeak: Uses weak parameters (n=1024, r=1, p=1) -
|
||||
* flagged as insecure. * scryptStrong: Uses stronger parameters (n=16384, r=8,
|
||||
* p=1) - considered secure.
|
||||
*
|
||||
* 3. Argon2 Examples: - Parent Classification: Memory-Hard Key Derivation
|
||||
* Function (Argon2id). - SAST: * argon2Derivation: Uses moderate memory and
|
||||
* iterations - considered secure. * argon2HighMemory: Uses high memory (128MB)
|
||||
* and more iterations - secure, though resource intensive.
|
||||
*
|
||||
* 4. Insecure Raw Hash Derivation: - Parent Classification: Raw Hash Usage for
|
||||
* Key Derivation. - SAST: Using a single SHA-256 hash as a key and then using
|
||||
* it with insecure AES/ECB mode is highly discouraged.
|
||||
*
|
||||
* 5. HKDF Examples: - Parent Classification: Key Derivation Function (HKDF). -
|
||||
* SAST: The provided HKDF implementation is simplistic (single-block expansion)
|
||||
* and may be flagged.
|
||||
*
|
||||
* 6. Multi-Step Hybrid Derivation: - Parent Classification: Composite KDF
|
||||
* (PBKDF2 followed by HKDF). - SAST: Combining two KDFs is acceptable if done
|
||||
* carefully; however, custom implementations should be reviewed.
|
||||
*
|
||||
* 7. Dynamic KDF Selection: - Parent Classification: Dynamic/Configurable Key
|
||||
* Derivation. - SAST: Loading KDF parameters from configuration introduces
|
||||
* ambiguity and risk if misconfigured.
|
||||
*/
|
||||
public class KeyDerivation1 {
|
||||
|
||||
// static {
|
||||
// Security.addProvider(new BouncyCastleProvider());
|
||||
// }
|
||||
//////////////////////////////////////
|
||||
// 1. PBKDF2 EXAMPLES
|
||||
//////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Basic PBKDF2 derivation using PBKDF2WithHmacSHA256.
|
||||
*
|
||||
* SAST/CBOM:
|
||||
* - Parent: PBKDF2.
|
||||
* - Uses 10,000 iterations with a 256-bit key; generally acceptable.
|
||||
*/
|
||||
public void pbkdf2DerivationBasic(String password) throws Exception {
|
||||
byte[] salt = generateSalt(16);
|
||||
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 10000, 256);
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||
byte[] key = factory.generateSecret(spec).getEncoded();
|
||||
System.out.println("PBKDF2 (Basic) Key: " + Base64.getEncoder().encodeToString(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* PBKDF2 derivation with a very low iteration count.
|
||||
*
|
||||
* SAST/CBOM: - Parent: PBKDF2. - Iteration count is only 10, which is far
|
||||
* below acceptable security standards. - Flagged as insecure.
|
||||
*/
|
||||
public void pbkdf2LowIteration(String password) throws Exception {
|
||||
byte[] salt = generateSalt(16);
|
||||
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 10, 256); // Very low iteration count.
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||
byte[] key = factory.generateSecret(spec).getEncoded();
|
||||
System.out.println("PBKDF2 (Low Iteration) Key (Insecure): " + Base64.getEncoder().encodeToString(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* PBKDF2 derivation with a high iteration count.
|
||||
*
|
||||
* SAST/CBOM: - Parent: PBKDF2. - Uses 1,000,000 iterations; this is secure
|
||||
* but may impact performance.
|
||||
*/
|
||||
public void pbkdf2HighIteration(String password) throws Exception {
|
||||
byte[] salt = generateSalt(16);
|
||||
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 1_000_000, 256);
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||
byte[] key = factory.generateSecret(spec).getEncoded();
|
||||
System.out.println("PBKDF2 (High Iteration) Key: " + Base64.getEncoder().encodeToString(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* PBKDF2 derivation using HmacSHA1.
|
||||
*
|
||||
* SAST/CBOM: - Parent: PBKDF2. - Uses HMAC-SHA1, which is considered weaker
|
||||
* than SHA-256; may be acceptable only for legacy systems.
|
||||
*/
|
||||
public void pbkdf2HmacSHA1(String password) throws Exception {
|
||||
byte[] salt = generateSalt(16);
|
||||
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 80000, 256);
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
|
||||
byte[] key = factory.generateSecret(spec).getEncoded();
|
||||
System.out.println("PBKDF2 (HmacSHA1) Key: " + Base64.getEncoder().encodeToString(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* PBKDF2 derivation using HmacSHA512.
|
||||
*
|
||||
* SAST/CBOM: - Parent: PBKDF2. - Uses HMAC-SHA512 with 160,000 iterations;
|
||||
* classified as secure.
|
||||
*/
|
||||
public void pbkdf2HmacSHA512(String password) throws Exception {
|
||||
byte[] salt = generateSalt(16);
|
||||
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 160000, 256);
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
|
||||
byte[] key = factory.generateSecret(spec).getEncoded();
|
||||
System.out.println("PBKDF2 (HmacSHA512) Key: " + Base64.getEncoder().encodeToString(key));
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
// 2. SCRYPT EXAMPLES
|
||||
//////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Scrypt derivation with weak parameters.
|
||||
*
|
||||
* SAST/CBOM:
|
||||
* - Parent: Scrypt.
|
||||
* - Parameters (n=1024, r=1, p=1) are too weak and should be flagged as
|
||||
* insecure.
|
||||
*/
|
||||
public void scryptWeak(String password) throws Exception {
|
||||
byte[] salt = generateSalt(16);
|
||||
// Weak parameters: low work factor.
|
||||
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 1024, 128);
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("SCRYPT");
|
||||
byte[] key = factory.generateSecret(spec).getEncoded();
|
||||
System.out.println("scrypt (Weak) Key: " + Base64.getEncoder().encodeToString(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrypt derivation with stronger parameters.
|
||||
*
|
||||
* SAST/CBOM: - Parent: Scrypt. - Parameters (n=16384, r=8, p=1) provide a
|
||||
* secure work factor.
|
||||
*/
|
||||
public void scryptStrong(String password) throws Exception {
|
||||
byte[] salt = generateSalt(16);
|
||||
// Strong parameters for scrypt.
|
||||
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 16384, 256);
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("SCRYPT");
|
||||
byte[] key = factory.generateSecret(spec).getEncoded();
|
||||
System.out.println("scrypt (Strong) Key: " + Base64.getEncoder().encodeToString(key));
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
// 3. ARGON2 EXAMPLES
|
||||
//////////////////////////////////////
|
||||
|
||||
// /**
|
||||
// * Argon2 derivation using Argon2id with moderate memory and iterations.
|
||||
// *
|
||||
// * SAST/CBOM:
|
||||
// * - Parent: Argon2 (Memory-Hard KDF).
|
||||
// * - Parameters: memory=65536 KB, iterations=2, parallelism=2; considered
|
||||
// * secure.
|
||||
// */
|
||||
// public void argon2Derivation(String password) throws Exception {
|
||||
// byte[] salt = generateSalt(16);
|
||||
// Argon2Parameters.Builder builder = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id)
|
||||
// .withSalt(salt)
|
||||
// .withParallelism(2)
|
||||
// .withMemoryAsKB(65536)
|
||||
// .withIterations(2);
|
||||
|
||||
// Argon2BytesGenerator gen = new Argon2BytesGenerator();
|
||||
// gen.init(builder.build());
|
||||
// byte[] hash = new byte[32];
|
||||
// gen.generateBytes(password.getBytes(), hash, 0, hash.length);
|
||||
// System.out.println("Argon2 Key: " + Base64.getEncoder().encodeToString(hash));
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Argon2 derivation with high memory and more iterations.
|
||||
// *
|
||||
// * SAST/CBOM:
|
||||
// * - Parent: Argon2.
|
||||
// * - Uses high memory (131072 KB = 128MB) and 5 iterations; secure but resource
|
||||
// * intensive.
|
||||
// */
|
||||
// public void argon2HighMemory(String password) throws Exception {
|
||||
// byte[] salt = generateSalt(16);
|
||||
// Argon2Parameters.Builder builder = new Argon2Parameters.Builder(Argon2Parameters.ARGON2_id)
|
||||
// .withSalt(salt)
|
||||
// .withParallelism(4)
|
||||
// .withMemoryAsKB(131072) // 128MB of memory.
|
||||
// .withIterations(5);
|
||||
|
||||
// Argon2BytesGenerator gen = new Argon2BytesGenerator();
|
||||
// gen.init(builder.build());
|
||||
// byte[] hash = new byte[64];
|
||||
// gen.generateBytes(password.getBytes(), hash, 0, hash.length);
|
||||
// System.out.println("Argon2 (High Memory) Key: " + Base64.getEncoder().encodeToString(hash));
|
||||
// }
|
||||
|
||||
//////////////////////////////////////
|
||||
// 4. INSECURE RAW HASH EXAMPLES
|
||||
//////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Derives a key by directly hashing input with SHA-256 and uses it for
|
||||
* encryption.
|
||||
*
|
||||
* SAST/CBOM:
|
||||
* - Parent: Raw Hash-Based Key Derivation.
|
||||
* - This approach is insecure since it uses a raw hash as a key and then uses
|
||||
* AES in ECB mode,
|
||||
* which is vulnerable to pattern analysis.
|
||||
*/
|
||||
public void insecureRawSHA256Derivation(String input) throws Exception {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] derivedKey = digest.digest(input.getBytes());
|
||||
System.out.println("Insecure Raw SHA-256 Key: " + Base64.getEncoder().encodeToString(derivedKey));
|
||||
|
||||
// Insecure usage: AES/ECB mode is used with a key derived from a raw hash.
|
||||
SecretKey key = new SecretKeySpec(derivedKey, 0, 16, "AES");
|
||||
javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES/ECB/NoPadding");
|
||||
cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, key);
|
||||
byte[] ciphertext = cipher.doFinal("SampleData16Bytes".getBytes());
|
||||
System.out.println("Insecurely Encrypted Data with Raw-SHA256 Key: "
|
||||
+ Base64.getEncoder().encodeToString(ciphertext));
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
// 5. HKDF EXAMPLES
|
||||
//////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Derives a key using a simple HKDF expansion based on HMAC-SHA256.
|
||||
*
|
||||
* SAST/CBOM:
|
||||
* - Parent: HKDF.
|
||||
* - The implementation uses a single-block (simplistic) expansion and may be
|
||||
* flagged.
|
||||
* A full, standard HKDF implementation is recommended.
|
||||
*/
|
||||
public void hkdfDerivation(byte[] ikm) throws Exception {
|
||||
byte[] salt = generateSalt(32);
|
||||
byte[] derivedKey = hkdfExpand(ikm, salt, 32);
|
||||
System.out.println("HKDF Derived Key: " + Base64.getEncoder().encodeToString(derivedKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* Multi-step hybrid derivation: first using PBKDF2, then applying HKDF
|
||||
* expansion.
|
||||
*
|
||||
* SAST/CBOM: - Parent: Composite KDF. - Combining PBKDF2 and HKDF is a
|
||||
* non-standard approach and may be flagged; ensure that each step meets
|
||||
* security requirements.
|
||||
*/
|
||||
public void multiStepHybridDerivation(String password, byte[] sharedSecret) throws Exception {
|
||||
byte[] pbkdf2Key = derivePBKDF2Key(password);
|
||||
byte[] finalKey = hkdfExpand(sharedSecret, pbkdf2Key, 32);
|
||||
System.out.println("Multi-Step Hybrid Key: " + Base64.getEncoder().encodeToString(finalKey));
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
// 6. DYNAMIC ALGORITHM SELECTION (AMBIGUOUS CASE)
|
||||
//////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Dynamically selects a KDF algorithm based on external configuration.
|
||||
*
|
||||
* SAST/CBOM:
|
||||
* - Parent: Dynamic/Configurable Key Derivation.
|
||||
* - Loading the algorithm and parameters from a config file introduces risk if
|
||||
* the configuration is compromised
|
||||
* or misconfigured.
|
||||
*/
|
||||
public void dynamicKDFSelection(String password, String configPath) throws Exception {
|
||||
Properties props = new Properties();
|
||||
try (FileInputStream fis = new FileInputStream(configPath)) {
|
||||
props.load(fis);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
String kdfAlg = props.getProperty("kdf.alg", "PBKDF2WithHmacSHA256");
|
||||
int iterations = Integer.parseInt(props.getProperty("kdf.iterations", "10000"));
|
||||
int keySize = Integer.parseInt(props.getProperty("kdf.keySize", "256"));
|
||||
|
||||
byte[] salt = generateSalt(16);
|
||||
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterations, keySize);
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance(kdfAlg);
|
||||
byte[] derived = factory.generateSecret(spec).getEncoded();
|
||||
System.out.println("Dynamically Selected KDF (" + kdfAlg + ") Key: "
|
||||
+ Base64.getEncoder().encodeToString(derived));
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
// HELPER METHODS
|
||||
//////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Helper method to derive a PBKDF2 key with PBKDF2WithHmacSHA256.
|
||||
*
|
||||
* SAST/CBOM:
|
||||
* - Parent: PBKDF2 helper.
|
||||
*/
|
||||
private byte[] derivePBKDF2Key(String password) throws Exception {
|
||||
byte[] salt = generateSalt(16);
|
||||
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 10000, 256);
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||
return factory.generateSecret(spec).getEncoded();
|
||||
}
|
||||
|
||||
/**
|
||||
* A simplistic HKDF expansion function using HMAC-SHA256.
|
||||
*
|
||||
* SAST/CBOM: - Parent: HKDF. - Uses a single-block expansion
|
||||
* ("hkdf-expansion") which is non-standard and may be flagged.
|
||||
*/
|
||||
private byte[] hkdfExpand(byte[] ikm, byte[] salt, int length) throws Exception {
|
||||
Mac hmac = Mac.getInstance("HmacSHA256");
|
||||
SecretKey secretKey = new SecretKeySpec(salt, "HmacSHA256");
|
||||
hmac.init(secretKey);
|
||||
byte[] prk = hmac.doFinal(ikm); // Extraction step.
|
||||
|
||||
// Single-block expansion (non-standard; for full HKDF, multiple iterations may
|
||||
// be necessary)
|
||||
hmac.init(new SecretKeySpec(prk, "HmacSHA256"));
|
||||
byte[] okm = hmac.doFinal("hkdf-expansion".getBytes());
|
||||
return Arrays.copyOf(okm, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a secure random salt of the specified length.
|
||||
*
|
||||
* SAST/CBOM: - Parent: Secure Random Salt Generation. - Uses SecureRandom;
|
||||
* considered best practice.
|
||||
*/
|
||||
private byte[] generateSalt(int length) {
|
||||
byte[] salt = new byte[length];
|
||||
new SecureRandom().nextBytes(salt);
|
||||
return salt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
package com.example.crypto.algorithms;
|
||||
|
||||
// import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
// import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
|
||||
// import org.bouncycastle.pqc.jcajce.spec.KyberParameterSpec;
|
||||
// import org.bouncycastle.util.Strings;
|
||||
import java.security.*;
|
||||
import java.security.spec.ECGenParameterSpec;
|
||||
import java.util.Base64;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyAgreement;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* Demonstrates various Key Encapsulation Mechanisms (KEMs), including:
|
||||
*
|
||||
* 1) RSA-KEM (emulated using RSA-OAEP for ephemeral key wrapping) - CBOM/SAST:
|
||||
* Classified as a Hybrid Cryptosystem (public-key based key encapsulation).
|
||||
* While RSA-OAEP is secure, using it to emulate KEM (without a standard scheme)
|
||||
* may be flagged.
|
||||
*
|
||||
* 2) ECIES (Elliptic Curve Integrated Encryption Scheme) - CBOM/SAST:
|
||||
* Classified as a Hybrid Cryptosystem (KEM+DEM) based on ECDH and AES. Note:
|
||||
* Directly using the raw ECDH shared secret as key material is insecure in
|
||||
* production.
|
||||
*
|
||||
* 3) Kyber (Post-Quantum KEM using BouncyCastle PQC) - CBOM/SAST: Classified as
|
||||
* a Post-Quantum Key Encapsulation mechanism. This is modern and secure when
|
||||
* using standardized parameters.
|
||||
*
|
||||
* 4) Basic ephemeral flows that mimic KEM logic using ephemeral ECDH. -
|
||||
* CBOM/SAST: Classified as a simple KEM mimic based on ephemeral ECDH.
|
||||
*/
|
||||
public class KeyEncapsulation {
|
||||
|
||||
// static {
|
||||
// // Adding both classical and PQC providers.
|
||||
// Security.addProvider(new BouncyCastleProvider());
|
||||
// Security.addProvider(new BouncyCastlePQCProvider());
|
||||
// }
|
||||
//////////////////////////////////////
|
||||
// 1. RSA-KEM-Like Flow
|
||||
//////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Emulates RSA-KEM by using RSA-OAEP to wrap a random AES key.
|
||||
*
|
||||
* SAST/CBOM Classification:
|
||||
* - Parent: Hybrid Cryptosystem (RSA-OAEP based key encapsulation).
|
||||
* - Note: Although RSA-OAEP is secure, using it to "wrap" an ephemeral key is a
|
||||
* non-standard KEM pattern.
|
||||
*
|
||||
* @param rsaPub The RSA public key of the recipient.
|
||||
*/
|
||||
public void rsaKEMEncapsulation(PublicKey rsaPub) throws Exception {
|
||||
// 1) Generate an ephemeral AES key (symmetric key for data encryption)
|
||||
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
|
||||
keyGen.init(256); // 256-bit AES key.
|
||||
SecretKey aesKey = keyGen.generateKey();
|
||||
System.out.println("Ephemeral AES Key: " + Base64.getEncoder().encodeToString(aesKey.getEncoded()));
|
||||
|
||||
// 2) Encrypt (wrap) the ephemeral AES key with RSA-OAEP.
|
||||
// SAST Note: This RSA-OAEP wrapping is used to encapsulate the AES key.
|
||||
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
|
||||
rsaCipher.init(Cipher.ENCRYPT_MODE, rsaPub);
|
||||
byte[] wrappedKey = rsaCipher.doFinal(aesKey.getEncoded());
|
||||
System.out.println("RSA-KEM Encapsulated AES Key: " + Base64.getEncoder().encodeToString(wrappedKey));
|
||||
|
||||
// 3) Example usage: Encrypt data with the ephemeral AES key using AES-GCM.
|
||||
Cipher aesCipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
byte[] iv = new byte[12]; // Standard IV length for GCM.
|
||||
new SecureRandom().nextBytes(iv);
|
||||
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv);
|
||||
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey, gcmSpec);
|
||||
byte[] ciphertext = aesCipher.doFinal("KEM-based encryption".getBytes());
|
||||
System.out.println("AES-GCM ciphertext: " + Base64.getEncoder().encodeToString(ciphertext));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs RSA decapsulation by decrypting the wrapped AES key.
|
||||
*
|
||||
* SAST/CBOM Classification: - Parent: Hybrid Cryptosystem (RSA-OAEP based
|
||||
* key decapsulation). - Note: Secure when used with matching RSA key pairs.
|
||||
*
|
||||
* @param rsaPriv The RSA private key corresponding to the public key used.
|
||||
* @param wrappedKey The RSA-wrapped ephemeral AES key.
|
||||
*/
|
||||
public void rsaKEMDecapsulation(PrivateKey rsaPriv, byte[] wrappedKey) throws Exception {
|
||||
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
|
||||
rsaCipher.init(Cipher.DECRYPT_MODE, rsaPriv);
|
||||
byte[] aesKeyBytes = rsaCipher.doFinal(wrappedKey);
|
||||
SecretKey aesKey = new SecretKeySpec(aesKeyBytes, "AES");
|
||||
System.out.println("RSA-KEM Decapsulated AES Key: " + Base64.getEncoder().encodeToString(aesKey.getEncoded()));
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
// 2. ECIES Example
|
||||
//////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Implements a simplified ECIES flow using ephemeral ECDH and AES-GCM.
|
||||
*
|
||||
* SAST/CBOM Classification:
|
||||
* - Parent: Hybrid Cryptosystem (ECIES: ECDH-based key encapsulation + DEM).
|
||||
* - Note: Directly using the raw ECDH shared secret as key material is
|
||||
* insecure.
|
||||
* In practice, a proper KDF must be applied.
|
||||
*
|
||||
* @param ecPub The recipient's EC public key.
|
||||
*/
|
||||
public void eciesEncapsulation(PublicKey ecPub) throws Exception {
|
||||
// Generate an ephemeral EC key pair.
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
|
||||
kpg.initialize(new ECGenParameterSpec("secp256r1"), new SecureRandom());
|
||||
KeyPair ephemeralEC = kpg.generateKeyPair();
|
||||
|
||||
// Perform ECDH key agreement to derive the shared secret.
|
||||
KeyAgreement ka = KeyAgreement.getInstance("ECDH");
|
||||
ka.init(ephemeralEC.getPrivate());
|
||||
ka.doPhase(ecPub, true);
|
||||
byte[] sharedSecret = ka.generateSecret();
|
||||
System.out.println("ECIES ephemeral ECDH Secret: " + Base64.getEncoder().encodeToString(sharedSecret));
|
||||
|
||||
// For demonstration only: directly use part of the shared secret as an AES key.
|
||||
// SAST Note: This is insecure; a proper key derivation function (KDF) must be
|
||||
// used.
|
||||
SecretKey aesKey = new SecretKeySpec(sharedSecret, 0, 16, "AES");
|
||||
|
||||
// Encrypt the message using AES-GCM.
|
||||
Cipher aesCipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
byte[] iv = new byte[12];
|
||||
new SecureRandom().nextBytes(iv);
|
||||
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey, new GCMParameterSpec(128, iv));
|
||||
byte[] ciphertext = aesCipher.doFinal("ECIES message".getBytes());
|
||||
|
||||
// The ephemeral public key (ephemeralEC.getPublic()) is transmitted as part of
|
||||
// the output.
|
||||
System.out.println(
|
||||
"ECIES ephemeral public: " + Base64.getEncoder().encodeToString(ephemeralEC.getPublic().getEncoded()));
|
||||
System.out.println("ECIES ciphertext: " + Base64.getEncoder().encodeToString(ciphertext));
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
// 3. Kyber Example (Post-Quantum KEM)
|
||||
//////////////////////////////////////
|
||||
|
||||
// /**
|
||||
// * Demonstrates a Kyber-based encapsulation using BouncyCastle's PQC provider.
|
||||
// *
|
||||
// * SAST/CBOM Classification:
|
||||
// * - Parent: Post-Quantum KEM.
|
||||
// * - Note: Kyber is a modern, post-quantum secure KEM. This example uses
|
||||
// * Kyber-512.
|
||||
// *
|
||||
// * @param kyberRecipientKP The recipient's Kyber key pair.
|
||||
// */
|
||||
// public void kyberEncapsulate(KeyPair kyberRecipientKP) throws Exception {
|
||||
// // Use an ephemeral label for demonstration.
|
||||
// byte[] ephemeralLabel = Strings.toByteArray("Kyber-KEM-Label");
|
||||
// Cipher kemCipher = Cipher.getInstance("Kyber", "BCPQC");
|
||||
// kemCipher.init(Cipher.ENCRYPT_MODE, kyberRecipientKP.getPublic(), new SecureRandom());
|
||||
// byte[] ciphertext = kemCipher.doFinal(ephemeralLabel);
|
||||
// System.out.println("Kyber ciphertext: " + Base64.getEncoder().encodeToString(ciphertext));
|
||||
// }
|
||||
|
||||
//////////////////////////////////////
|
||||
// 4. Basic Ephemeral Flows That Mimic KEM
|
||||
//////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Uses ephemeral ECDH to derive a shared secret that mimics a KEM.
|
||||
*
|
||||
* SAST/CBOM Classification:
|
||||
* - Parent: Ephemeral Key Agreement (mimicking KEM).
|
||||
* - Note: This simple approach demonstrates the concept of using ephemeral keys
|
||||
* to derive a secret.
|
||||
* In a full scheme, the ephemeral public key would also be transmitted.
|
||||
*
|
||||
* @param recipientPubKey The recipient's public key.
|
||||
*/
|
||||
public void ephemeralECDHMimicKEM(PublicKey recipientPubKey) throws Exception {
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
|
||||
kpg.initialize(new ECGenParameterSpec("secp256r1"));
|
||||
KeyPair ephemeralKP = kpg.generateKeyPair();
|
||||
KeyAgreement ka = KeyAgreement.getInstance("ECDH");
|
||||
ka.init(ephemeralKP.getPrivate());
|
||||
ka.doPhase(recipientPubKey, true);
|
||||
byte[] sharedSecret = ka.generateSecret();
|
||||
System.out.println(
|
||||
"Ephemeral ECDH shared secret (mimics KEM): " + Base64.getEncoder().encodeToString(sharedSecret));
|
||||
// In a full implementation, the ephemeral public key and the shared secret are
|
||||
// used together.
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
// Test / Demo Method
|
||||
//////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Demonstrates each of the key encapsulation flows.
|
||||
*/
|
||||
public void runKeyEncapsulationDemos() throws Exception {
|
||||
// 1) RSA-KEM-like Flow:
|
||||
KeyPairGenerator rsaKpg = KeyPairGenerator.getInstance("RSA");
|
||||
rsaKpg.initialize(2048);
|
||||
KeyPair rsaKP = rsaKpg.generateKeyPair();
|
||||
rsaKEMEncapsulation(rsaKP.getPublic());
|
||||
|
||||
// 2) ECIES Example:
|
||||
KeyPairGenerator ecKpg = KeyPairGenerator.getInstance("EC");
|
||||
ecKpg.initialize(new ECGenParameterSpec("secp256r1"));
|
||||
KeyPair ecKP = ecKpg.generateKeyPair();
|
||||
eciesEncapsulation(ecKP.getPublic());
|
||||
|
||||
// // 3) Kyber Example (Post-Quantum KEM):
|
||||
// KeyPairGenerator kyberKpg = KeyPairGenerator.getInstance("Kyber", "BCPQC");
|
||||
// kyberKpg.initialize(KyberParameterSpec.kyber512);
|
||||
// KeyPair kyberKP = kyberKpg.generateKeyPair();
|
||||
// kyberEncapsulate(kyberKP);
|
||||
// 4) Ephemeral ECDH Mimic KEM:
|
||||
// For demonstration, we use an EC key pair and mimic KEM by deriving a shared
|
||||
// secret.
|
||||
KeyPair ephemeralEC = ecKpg.generateKeyPair();
|
||||
ephemeralECDHMimicKEM(ephemeralEC.getPublic());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
package com.example.crypto.algorithms;
|
||||
|
||||
// import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import java.security.*;
|
||||
import java.security.spec.ECGenParameterSpec;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import javax.crypto.KeyAgreement;
|
||||
|
||||
/**
|
||||
* Demonstrates various Key Exchange mechanisms using standard Java and
|
||||
* BouncyCastle:
|
||||
*
|
||||
* 1) Classic DH (Diffie-Hellman) with multiple key sizes: - 512-bit:
|
||||
* Insecure/deprecated (flagged as unsafe by SAST). - 2048-bit: Standard secure
|
||||
* level. - 4096-bit: High-security (but can be slow).
|
||||
*
|
||||
* 2) ECDH (using secp256r1): - Classified as a secure elliptic-curve key
|
||||
* exchange.
|
||||
*
|
||||
* 3) X25519: - A modern and efficient elliptic-curve key exchange protocol.
|
||||
*
|
||||
* 4) X448: - Provides a higher security level for key exchange.
|
||||
*
|
||||
* In addition, the class now includes a nuanced insecure example that
|
||||
* demonstrates: - Reusing static key pairs instead of generating fresh
|
||||
* ephemeral keys. - Using weak parameters (512-bit DH) in a key exchange.
|
||||
*
|
||||
* The runAllExchanges() method demonstrates generating keys for each algorithm,
|
||||
* deriving shared secrets, and comparing safe vs. insecure practices.
|
||||
*/
|
||||
public class KeyExchange {
|
||||
|
||||
// static {
|
||||
// // Add the BouncyCastle provider to support additional algorithms.
|
||||
// Security.addProvider(new BouncyCastleProvider());
|
||||
// }
|
||||
//////////////////////////////////////////
|
||||
// 1. Classic DH (Diffie-Hellman)
|
||||
//////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Generates a standard Diffie-Hellman key pair using a 2048-bit modulus.
|
||||
*
|
||||
* CBOM/SAST Classification:
|
||||
* - Parent: Classic Diffie-Hellman Key Exchange.
|
||||
* - 2048-bit is considered secure and is widely accepted.
|
||||
*
|
||||
* @return A 2048-bit DH KeyPair.
|
||||
*/
|
||||
public KeyPair generateDHKeyPair() throws Exception {
|
||||
KeyPairGenerator dhKpg = KeyPairGenerator.getInstance("DH");
|
||||
dhKpg.initialize(2048);
|
||||
return dhKpg.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a deprecated/unsafe Diffie-Hellman key pair using a 512-bit
|
||||
* modulus.
|
||||
*
|
||||
* CBOM/SAST Classification: - Parent: Classic Diffie-Hellman Key Exchange.
|
||||
* - 512-bit DH is considered insecure and should be flagged by SAST tools.
|
||||
*
|
||||
* @return A 512-bit (insecure) DH KeyPair.
|
||||
*/
|
||||
public KeyPair generateDHDeprecated() throws Exception {
|
||||
KeyPairGenerator dhKpg = KeyPairGenerator.getInstance("DH");
|
||||
// 512 bits is considered insecure/deprecated.
|
||||
dhKpg.initialize(512);
|
||||
return dhKpg.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a high-security Diffie-Hellman key pair using a 4096-bit
|
||||
* modulus.
|
||||
*
|
||||
* CBOM/SAST Classification: - Parent: Classic Diffie-Hellman Key Exchange.
|
||||
* - 4096-bit DH offers high security, though it may be slower in practice.
|
||||
*
|
||||
* @return A 4096-bit DH KeyPair.
|
||||
*/
|
||||
public KeyPair generateDHHighSecurity() throws Exception {
|
||||
KeyPairGenerator dhKpg = KeyPairGenerator.getInstance("DH");
|
||||
dhKpg.initialize(4096);
|
||||
return dhKpg.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a shared secret from a DH key pair.
|
||||
*
|
||||
* CBOM/SAST Classification: - Parent: Classic Diffie-Hellman Key Exchange.
|
||||
* - Properly deriving the shared secret is secure if using a safe key size.
|
||||
*
|
||||
* @param privateKey The private key of one party.
|
||||
* @param publicKey The public key of the other party.
|
||||
* @return The derived shared secret as a byte array.
|
||||
*/
|
||||
public byte[] deriveDHSecret(PrivateKey privateKey, PublicKey publicKey) throws Exception {
|
||||
KeyAgreement ka = KeyAgreement.getInstance("DH");
|
||||
ka.init(privateKey);
|
||||
ka.doPhase(publicKey, true);
|
||||
return ka.generateSecret();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
// 2. ECDH (secp256r1)
|
||||
//////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Generates an Elliptic Curve Diffie-Hellman key pair using the secp256r1
|
||||
* curve.
|
||||
*
|
||||
* CBOM/SAST Classification:
|
||||
* - Parent: Elliptic Curve Diffie-Hellman (ECDH).
|
||||
* - secp256r1 is widely regarded as secure and efficient.
|
||||
*
|
||||
* @return An ECDH KeyPair on secp256r1.
|
||||
*/
|
||||
public KeyPair generateECDHKeyPair() throws Exception {
|
||||
KeyPairGenerator ecKpg = KeyPairGenerator.getInstance("EC", "BC");
|
||||
ecKpg.initialize(new ECGenParameterSpec("secp256r1"), new SecureRandom());
|
||||
return ecKpg.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a shared secret using ECDH.
|
||||
*
|
||||
* CBOM/SAST Classification: - Parent: Elliptic Curve Diffie-Hellman (ECDH).
|
||||
* - Secure when using appropriate curves and proper randomness.
|
||||
*
|
||||
* @param privateKey The ECDH private key.
|
||||
* @param publicKey The corresponding public key.
|
||||
* @return The derived ECDH shared secret.
|
||||
*/
|
||||
public byte[] deriveECDHSecret(PrivateKey privateKey, PublicKey publicKey) throws Exception {
|
||||
KeyAgreement ka = KeyAgreement.getInstance("ECDH", "BC");
|
||||
ka.init(privateKey);
|
||||
ka.doPhase(publicKey, true);
|
||||
return ka.generateSecret();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
// 3. X25519
|
||||
//////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Generates an ephemeral X25519 key pair.
|
||||
*
|
||||
* CBOM/SAST Classification:
|
||||
* - Parent: Modern Elliptic-Curve Key Exchange.
|
||||
* - X25519 is considered secure and efficient.
|
||||
*
|
||||
* @return An X25519 KeyPair.
|
||||
*/
|
||||
public KeyPair generateX25519KeyPair() throws Exception {
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("X25519", "BC");
|
||||
// X25519 key size is fixed; the parameter (255) is a reference value.
|
||||
kpg.initialize(255, new SecureRandom());
|
||||
return kpg.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a shared secret using the X25519 key agreement.
|
||||
*
|
||||
* CBOM/SAST Classification: - Parent: Modern Elliptic-Curve Key Exchange. -
|
||||
* X25519 is highly recommended for its security and efficiency.
|
||||
*
|
||||
* @param privateKey The X25519 private key.
|
||||
* @param publicKey The corresponding public key.
|
||||
* @return The derived X25519 shared secret.
|
||||
*/
|
||||
public byte[] deriveX25519Secret(PrivateKey privateKey, PublicKey publicKey) throws Exception {
|
||||
KeyAgreement ka = KeyAgreement.getInstance("X25519", "BC");
|
||||
ka.init(privateKey);
|
||||
ka.doPhase(publicKey, true);
|
||||
return ka.generateSecret();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
// 4. X448
|
||||
//////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Generates an ephemeral X448 key pair.
|
||||
*
|
||||
* CBOM/SAST Classification:
|
||||
* - Parent: Modern Elliptic-Curve Key Exchange.
|
||||
* - X448 provides a higher security margin than X25519.
|
||||
*
|
||||
* @return An X448 KeyPair.
|
||||
*/
|
||||
public KeyPair generateX448KeyPair() throws Exception {
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("X448", "BC");
|
||||
// X448 key size is fixed; the parameter (448) is the curve parameter.
|
||||
kpg.initialize(448, new SecureRandom());
|
||||
return kpg.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a shared secret using the X448 key agreement.
|
||||
*
|
||||
* CBOM/SAST Classification: - Parent: Modern Elliptic-Curve Key Exchange. -
|
||||
* X448 is considered secure and suitable for high-security applications.
|
||||
*
|
||||
* @param privateKey The X448 private key.
|
||||
* @param publicKey The corresponding public key.
|
||||
* @return The derived X448 shared secret.
|
||||
*/
|
||||
public byte[] deriveX448Secret(PrivateKey privateKey, PublicKey publicKey) throws Exception {
|
||||
KeyAgreement ka = KeyAgreement.getInstance("X448", "BC");
|
||||
ka.init(privateKey);
|
||||
ka.doPhase(publicKey, true);
|
||||
return ka.generateSecret();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
// 5. Nuanced Insecure Key Exchange Example
|
||||
//////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Demonstrates a nuanced example of insecure key exchange by:
|
||||
* - Using deprecated DH parameters (512-bit).
|
||||
* - Reusing static (non-ephemeral) keys.
|
||||
*
|
||||
* SAST/CBOM Classification:
|
||||
* - Parent: Insecure Key Exchange Patterns.
|
||||
* - Issues:
|
||||
* * 512-bit DH is weak and vulnerable to attacks.
|
||||
* * Reusing a static key pair across sessions eliminates forward secrecy.
|
||||
* * Reusing an ECDH key pair for both sides results in predictable shared
|
||||
* secrets.
|
||||
*/
|
||||
public void insecureKeyExchangeExample() throws Exception {
|
||||
System.out.println("\n--- Insecure Key Exchange Example ---");
|
||||
|
||||
// Example 1: Using weak 512-bit DH with static key reuse.
|
||||
KeyPair staticDHKeyPair = generateDHDeprecated();
|
||||
// Reusing the same static DH key pair for both parties.
|
||||
byte[] staticDHSecret = deriveDHSecret(staticDHKeyPair.getPrivate(), staticDHKeyPair.getPublic());
|
||||
System.out.println("Static DH (512-bit) shared secret (reused): "
|
||||
+ Base64.getEncoder().encodeToString(staticDHSecret));
|
||||
// SAST Note: 512-bit DH is considered insecure and static key reuse prevents
|
||||
// forward secrecy.
|
||||
|
||||
// Example 2: Reusing an ECDH key pair instead of generating fresh ephemeral
|
||||
// keys.
|
||||
KeyPair reusedECDHKeyPair = generateECDHKeyPair();
|
||||
// Using the same key pair for both sides leads to a shared secret that is
|
||||
// easily derived.
|
||||
byte[] reusedECDHSecret = deriveECDHSecret(reusedECDHKeyPair.getPrivate(), reusedECDHKeyPair.getPublic());
|
||||
System.out.println("Reused ECDH shared secret: "
|
||||
+ Base64.getEncoder().encodeToString(reusedECDHSecret));
|
||||
// SAST Note: Proper key exchange requires fresh ephemeral keys for each session
|
||||
// to ensure forward secrecy.
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
// 6. runAllExchanges() Demo Method
|
||||
//////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Demonstrates key exchange flows for various algorithms, including both secure
|
||||
* and insecure examples.
|
||||
*
|
||||
* CBOM/SAST Classification:
|
||||
* - Exercises both safe configurations (e.g., DH with 2048/4096-bit, ECDH,
|
||||
* X25519, X448)
|
||||
* and insecure configurations (e.g., DH with 512-bit, static key reuse).
|
||||
*/
|
||||
public void runAllExchanges() throws Exception {
|
||||
System.out.println("--- Running Secure Key Exchanges ---");
|
||||
|
||||
// ============ DEPRECATED / UNSAFE DH (512 bits) ============
|
||||
KeyPair dhDep1 = generateDHDeprecated();
|
||||
KeyPair dhDep2 = generateDHDeprecated();
|
||||
byte[] dhDepSecret1 = deriveDHSecret(dhDep1.getPrivate(), dhDep2.getPublic());
|
||||
byte[] dhDepSecret2 = deriveDHSecret(dhDep2.getPrivate(), dhDep1.getPublic());
|
||||
System.out.println("DH(512) K1->K2: " + Base64.getEncoder().encodeToString(dhDepSecret1));
|
||||
System.out.println("DH(512) K2->K1: " + Base64.getEncoder().encodeToString(dhDepSecret2));
|
||||
System.out.println("DH(512) match? " + Arrays.equals(dhDepSecret1, dhDepSecret2));
|
||||
|
||||
// ============ DH (2048 bits) Standard ============
|
||||
KeyPair dhKP1 = generateDHKeyPair();
|
||||
KeyPair dhKP2 = generateDHKeyPair();
|
||||
byte[] dhSecret1 = deriveDHSecret(dhKP1.getPrivate(), dhKP2.getPublic());
|
||||
byte[] dhSecret2 = deriveDHSecret(dhKP2.getPrivate(), dhKP1.getPublic());
|
||||
System.out.println("DH(2048) K1->K2: " + Base64.getEncoder().encodeToString(dhSecret1));
|
||||
System.out.println("DH(2048) K2->K1: " + Base64.getEncoder().encodeToString(dhSecret2));
|
||||
System.out.println("DH(2048) match? " + Arrays.equals(dhSecret1, dhSecret2));
|
||||
|
||||
// ============ DH (4096 bits) High-Security ============
|
||||
KeyPair dhHigh1 = generateDHHighSecurity();
|
||||
KeyPair dhHigh2 = generateDHHighSecurity();
|
||||
byte[] dhHighSecret1 = deriveDHSecret(dhHigh1.getPrivate(), dhHigh2.getPublic());
|
||||
byte[] dhHighSecret2 = deriveDHSecret(dhHigh2.getPrivate(), dhHigh1.getPublic());
|
||||
System.out.println("DH(4096) K1->K2: " + Base64.getEncoder().encodeToString(dhHighSecret1));
|
||||
System.out.println("DH(4096) K2->K1: " + Base64.getEncoder().encodeToString(dhHighSecret2));
|
||||
System.out.println("DH(4096) match? " + Arrays.equals(dhHighSecret1, dhHighSecret2));
|
||||
|
||||
// ============ ECDH (secp256r1) ============
|
||||
KeyPair ecKP1 = generateECDHKeyPair();
|
||||
KeyPair ecKP2 = generateECDHKeyPair();
|
||||
byte[] ecdhSecret1 = deriveECDHSecret(ecKP1.getPrivate(), ecKP2.getPublic());
|
||||
byte[] ecdhSecret2 = deriveECDHSecret(ecKP2.getPrivate(), ecKP1.getPublic());
|
||||
System.out.println("ECDH K1->K2: " + Base64.getEncoder().encodeToString(ecdhSecret1));
|
||||
System.out.println("ECDH K2->K1: " + Base64.getEncoder().encodeToString(ecdhSecret2));
|
||||
System.out.println("ECDH match? " + Arrays.equals(ecdhSecret1, ecdhSecret2));
|
||||
|
||||
// ============ X25519 ============
|
||||
KeyPair x25519KP1 = generateX25519KeyPair();
|
||||
KeyPair x25519KP2 = generateX25519KeyPair();
|
||||
byte[] x25519Secret1 = deriveX25519Secret(x25519KP1.getPrivate(), x25519KP2.getPublic());
|
||||
byte[] x25519Secret2 = deriveX25519Secret(x25519KP2.getPrivate(), x25519KP1.getPublic());
|
||||
System.out.println("X25519 K1->K2: " + Base64.getEncoder().encodeToString(x25519Secret1));
|
||||
System.out.println("X25519 K2->K1: " + Base64.getEncoder().encodeToString(x25519Secret2));
|
||||
System.out.println("X25519 match? " + Arrays.equals(x25519Secret1, x25519Secret2));
|
||||
|
||||
// ============ X448 ============
|
||||
KeyPair x448KP1 = generateX448KeyPair();
|
||||
KeyPair x448KP2 = generateX448KeyPair();
|
||||
byte[] x448Secret1 = deriveX448Secret(x448KP1.getPrivate(), x448KP2.getPublic());
|
||||
byte[] x448Secret2 = deriveX448Secret(x448KP2.getPrivate(), x448KP1.getPublic());
|
||||
System.out.println("X448 K1->K2: " + Base64.getEncoder().encodeToString(x448Secret1));
|
||||
System.out.println("X448 K2->K1: " + Base64.getEncoder().encodeToString(x448Secret2));
|
||||
System.out.println("X448 match? " + Arrays.equals(x448Secret1, x448Secret2));
|
||||
|
||||
// ============ Insecure Key Exchange Example ============
|
||||
insecureKeyExchangeExample();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
package com.example.crypto.algorithms;
|
||||
|
||||
// import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import java.security.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* MACOperation demonstrates various Message Authentication Code (MAC)
|
||||
* operations and further use of MAC outputs as inputs into higher-level
|
||||
* cryptosystems.
|
||||
*
|
||||
* Flows include:
|
||||
*
|
||||
* 1. Secure HMAC-SHA2 (HMAC-SHA256) - a widely accepted MAC. 2. Secure
|
||||
* HMAC-SHA3 (HMAC-SHA3-256) - an alternative using the SHA-3 family. 3. Secure
|
||||
* Poly1305 MAC - using BouncyCastle's implementation. 4. Secure GMAC - using
|
||||
* AES-GCM's authentication tag in a dedicated MAC mode. 5. Secure KMAC - using
|
||||
* KMAC128 (from the SHA-3 family).
|
||||
*
|
||||
* Insecure examples include:
|
||||
*
|
||||
* 6. Insecure HMAC-SHA1 - which is deprecated.
|
||||
*
|
||||
* Further flows:
|
||||
*
|
||||
* A. processMACOutput: Uses the MAC output directly as key material for AES
|
||||
* encryption. (Note: This is acceptable only if the MAC is produced by a secure
|
||||
* function.)
|
||||
*
|
||||
* B. alternativeMACFlow: Uses the MAC output as an identifier that is then
|
||||
* encrypted.
|
||||
*
|
||||
* C. furtherUseMACForKeyDerivation: Uses PBKDF2 to split a MAC output into two
|
||||
* keys, one for encryption and one for MACing ciphertext.
|
||||
*
|
||||
* SAST/CBOM Notes: - Secure MAC algorithms (HMAC-SHA256, HMAC-SHA3-256,
|
||||
* Poly1305, GMAC, KMAC128) are acceptable if used correctly. - HMAC-SHA1 is
|
||||
* flagged as insecure. - Using a raw MAC output directly as key material is
|
||||
* ambiguous unless the MAC is produced by a secure KDF.
|
||||
*/
|
||||
public class MACOperation {
|
||||
|
||||
// static {
|
||||
// Security.addProvider(new BouncyCastleProvider());
|
||||
// }
|
||||
// ---------- MAC Operations ----------
|
||||
/**
|
||||
* Secure MAC using HMAC-SHA256. SAST: HMAC-SHA256 is widely considered
|
||||
* 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());
|
||||
}
|
||||
|
||||
/**
|
||||
* Secure MAC using HMAC-SHA3-256. SAST: HMAC-SHA3 is a modern alternative
|
||||
* from the SHA-3 family.
|
||||
*/
|
||||
public byte[] secureHMACSHA3(String message, byte[] key) throws Exception {
|
||||
Mac mac = Mac.getInstance("HmacSHA3-256", "BC");
|
||||
SecretKey secretKey = new SecretKeySpec(key, "HmacSHA3-256");
|
||||
mac.init(secretKey);
|
||||
return mac.doFinal(message.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Secure MAC using Poly1305. SAST: Poly1305 is secure when used with a
|
||||
* one-time key from a cipher (e.g. ChaCha20).
|
||||
*/
|
||||
public byte[] securePoly1305(String message, byte[] key) throws Exception {
|
||||
Mac mac = Mac.getInstance("Poly1305", "BC");
|
||||
SecretKey secretKey = new SecretKeySpec(key, "Poly1305");
|
||||
mac.init(secretKey);
|
||||
return mac.doFinal(message.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Secure MAC using GMAC. SAST: GMAC (the MAC part of AES-GCM) is secure
|
||||
* when used correctly.
|
||||
*/
|
||||
public byte[] secureGMAC(String message, byte[] key) throws Exception {
|
||||
// For GMAC, we use the GMac algorithm as provided by BC.
|
||||
Mac mac = Mac.getInstance("GMac", "BC");
|
||||
// Initialize the key for GMAC; key should be appropriate for the underlying
|
||||
// block cipher.
|
||||
SecretKey secretKey = new SecretKeySpec(key, "AES");
|
||||
mac.init(secretKey);
|
||||
return mac.doFinal(message.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Secure MAC using KMAC128. SAST: KMAC128 is part of the SHA-3 family and
|
||||
* is secure when used properly.
|
||||
*/
|
||||
public byte[] secureKMAC(String message, byte[] key) throws Exception {
|
||||
Mac mac = Mac.getInstance("KMAC128", "BC");
|
||||
SecretKey secretKey = new SecretKeySpec(key, "KMAC128");
|
||||
mac.init(secretKey);
|
||||
return mac.doFinal(message.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Insecure MAC using HMAC-SHA1. SAST: HMAC-SHA1 is considered deprecated
|
||||
* and weak.
|
||||
*/
|
||||
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());
|
||||
}
|
||||
|
||||
// ---------- Further Use of MAC Outputs ----------
|
||||
/**
|
||||
* Processes the MAC output by using it as key material for AES encryption.
|
||||
* SAST: Using a raw MAC output as key material is acceptable only if the
|
||||
* MAC was produced by a secure function; otherwise, this is ambiguous.
|
||||
*
|
||||
* @param macOutput The computed MAC output.
|
||||
* @throws Exception if encryption fails.
|
||||
*/
|
||||
public void processMACOutput(byte[] macOutput) throws Exception {
|
||||
// Derive a 128-bit AES key from the MAC output.
|
||||
SecretKey key = new SecretKeySpec(macOutput, 0, 16, "AES");
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, new SecureRandom());
|
||||
byte[] encryptedData = cipher.doFinal("Sensitive Data".getBytes());
|
||||
storeEncryptedMAC(encryptedData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative flow: Uses the MAC output as an identifier and then encrypts
|
||||
* it. SAST: Using a MAC as an identifier is common; subsequent encryption
|
||||
* must be secure.
|
||||
*
|
||||
* @param macOutput The computed MAC output.
|
||||
* @throws Exception if encryption fails.
|
||||
*/
|
||||
public void alternativeMACFlow(byte[] macOutput) throws Exception {
|
||||
byte[] identifier = Base64.getEncoder().encode(macOutput);
|
||||
encryptAndSend(identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Further use: Derives two separate keys from the MAC output using PBKDF2,
|
||||
* then uses one key for encryption and one for computing an additional MAC
|
||||
* over the ciphertext.
|
||||
*
|
||||
* SAST: This key-splitting technique is acceptable if PBKDF2 is used
|
||||
* securely.
|
||||
*
|
||||
* @param macOutput The MAC output to derive keys from.
|
||||
* @throws Exception if key derivation or encryption fails.
|
||||
*/
|
||||
public void furtherUseMACForKeyDerivation(byte[] macOutput) throws Exception {
|
||||
// Use the Base64 representation of the MAC as the password input to PBKDF2.
|
||||
String macAsPassword = Base64.getEncoder().encodeToString(macOutput);
|
||||
byte[] salt = generateSalt(16);
|
||||
PBEKeySpec spec = new PBEKeySpec(macAsPassword.toCharArray(), salt, 10000, 256);
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||
byte[] keyMaterial = factory.generateSecret(spec).getEncoded();
|
||||
// Split into two 128-bit keys.
|
||||
byte[] encryptionKeyBytes = Arrays.copyOfRange(keyMaterial, 0, 16);
|
||||
byte[] macKeyBytes = Arrays.copyOfRange(keyMaterial, 16, 32);
|
||||
SecretKey encryptionKey = new SecretKeySpec(encryptionKeyBytes, "AES");
|
||||
SecretKey derivedMacKey = new SecretKeySpec(macKeyBytes, "HmacSHA256");
|
||||
|
||||
// Encrypt some sample data using the derived encryption key.
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, new SecureRandom());
|
||||
byte[] ciphertext = cipher.doFinal("Further Use Test Data".getBytes());
|
||||
|
||||
// Compute HMAC over the ciphertext using the derived MAC key.
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(derivedMacKey);
|
||||
byte[] computedMac = mac.doFinal(ciphertext);
|
||||
|
||||
// Concatenate the ciphertext and MAC for further use.
|
||||
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);
|
||||
storeEncryptedMAC(output);
|
||||
}
|
||||
|
||||
// ---------- Output/Storage Methods ----------
|
||||
/**
|
||||
* Simulates secure storage or transmission of an encrypted MAC output.
|
||||
* SAST: In production, storage and transmission must be protected.
|
||||
*
|
||||
* @param encryptedMAC The encrypted MAC output.
|
||||
*/
|
||||
public void storeEncryptedMAC(byte[] encryptedMAC) {
|
||||
String stored = Base64.getEncoder().encodeToString(encryptedMAC);
|
||||
// In production, this string would be securely stored or transmitted.
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts data using AES-GCM and simulates secure transmission. SAST: Uses
|
||||
* a securely generated AES key.
|
||||
*
|
||||
* @param data The data to encrypt.
|
||||
* @throws Exception if encryption fails.
|
||||
*/
|
||||
public void encryptAndSend(byte[] data) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
SecretKey key = generateAESKey();
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, new SecureRandom());
|
||||
byte[] encryptedData = cipher.doFinal(data);
|
||||
storeEncryptedMAC(encryptedData);
|
||||
}
|
||||
|
||||
// ---------- Helper Methods ----------
|
||||
/**
|
||||
* Generates a secure 256-bit AES key. SAST: Uses a strong RNG for key
|
||||
* generation.
|
||||
*
|
||||
* @return A SecretKey for AES.
|
||||
* @throws NoSuchAlgorithmException if AES is unsupported.
|
||||
*/
|
||||
private SecretKey generateAESKey() throws NoSuchAlgorithmException {
|
||||
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
|
||||
keyGen.init(256);
|
||||
return keyGen.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;
|
||||
}
|
||||
}
|
||||
114
java/ql/test/experimental/library-tests/quantum/jca/Nonce.java
Normal file
114
java/ql/test/experimental/library-tests/quantum/jca/Nonce.java
Normal file
@@ -0,0 +1,114 @@
|
||||
package com.example.crypto.artifacts;
|
||||
|
||||
// import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.Cipher;
|
||||
import java.security.*;
|
||||
import java.util.Base64;
|
||||
import java.util.Random;
|
||||
|
||||
public class Nonce {
|
||||
|
||||
// static {
|
||||
// Security.addProvider(new BouncyCastleProvider()); // Ensure BouncyCastle is available
|
||||
// }
|
||||
/**
|
||||
* Simple Case: Generates a secure nonce and uses it in HMAC.
|
||||
*/
|
||||
public void simpleNonceUsage() throws Exception {
|
||||
byte[] nonce = secureNonce(16);
|
||||
SecretKey key = generateHmacKey();
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(key);
|
||||
mac.update(nonce);
|
||||
byte[] macResult = mac.doFinal("Simple Test Data".getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Incorrect: Hardcoded, reused nonce in encryption, leading to predictable
|
||||
* output.
|
||||
*/
|
||||
public void hardcodedNonceReuse() throws Exception {
|
||||
byte[] nonce = "BADNONCEBADNONCE".getBytes(); // HARDCODED NONCE REUSED!
|
||||
SecretKey key = generateHmacKey();
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(key);
|
||||
mac.update(nonce);
|
||||
byte[] macResult = mac.doFinal("Sensitive Data".getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Incorrect: Reusing a nonce with AES-GCM, which can lead to catastrophic
|
||||
* security failures.
|
||||
*/
|
||||
public void insecureNonceReuseGCM(SecretKey key, byte[] plaintext) throws Exception {
|
||||
byte[] nonce = getReusedNonce(12); // SAME NONCE REUSED!
|
||||
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, nonce);
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, gcmSpec);
|
||||
byte[] ciphertext = cipher.doFinal(plaintext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Secure Case: Proper unique nonce usage in AES-GCM.
|
||||
*/
|
||||
public void secureNonceUsageGCM(SecretKey key, byte[] plaintext) throws Exception {
|
||||
byte[] nonce = secureNonce(12);
|
||||
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, nonce);
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, gcmSpec);
|
||||
byte[] ciphertext = cipher.doFinal(plaintext);
|
||||
}
|
||||
|
||||
public void complexNonceFlow() {
|
||||
byte[] nonce = useSecureMethod() ? secureNonce(16) : insecureNonce(16);
|
||||
processNonce(nonce);
|
||||
try {
|
||||
useNonceInMac(nonce, generateHmacKey(), "HmacSHA256");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void useNonceInMac(byte[] nonce, SecretKey key, String macAlgorithm) throws Exception {
|
||||
Mac mac = Mac.getInstance(macAlgorithm);
|
||||
mac.init(key);
|
||||
mac.update(nonce);
|
||||
byte[] macResult = mac.doFinal("Sensitive Data".getBytes());
|
||||
}
|
||||
|
||||
private boolean useSecureMethod() {
|
||||
return System.currentTimeMillis() % 2 == 0;
|
||||
}
|
||||
|
||||
private void processNonce(byte[] nonce) {
|
||||
String nonceBase64 = Base64.getEncoder().encodeToString(nonce);
|
||||
}
|
||||
|
||||
private SecretKey generateHmacKey() throws NoSuchAlgorithmException {
|
||||
KeyGenerator keyGen = KeyGenerator.getInstance("HmacSHA256");
|
||||
return keyGen.generateKey();
|
||||
}
|
||||
|
||||
private byte[] secureNonce(int length) {
|
||||
byte[] nonce = new byte[length];
|
||||
new SecureRandom().nextBytes(nonce);
|
||||
return nonce;
|
||||
}
|
||||
|
||||
private byte[] insecureNonce(int length) {
|
||||
byte[] nonce = new byte[length];
|
||||
new Random().nextBytes(nonce);
|
||||
return nonce;
|
||||
}
|
||||
|
||||
/**
|
||||
* Incorrect: A nonce that is stored and reused across multiple encryptions.
|
||||
*/
|
||||
private byte[] getReusedNonce(int length) {
|
||||
return "BADNONCEBADNONCE".getBytes(); // Fixed nonce reuse across calls
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package com.example.crypto.algorithms;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Random;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.KeyGenerator;
|
||||
|
||||
/**
|
||||
* PrngTest demonstrates various approaches for generating random data using
|
||||
* PRNG/RNG APIs.
|
||||
*
|
||||
* It covers: 1) Secure random generation using SecureRandom (default and
|
||||
* getInstanceStrong). 2) Insecure random generation using java.util.Random. 3)
|
||||
* Flawed PRNG usage by setting a fixed seed. 4) Dynamic PRNG selection based on
|
||||
* configuration. 5) Usage of random data as nonces/IVs in symmetric encryption.
|
||||
*
|
||||
* SAST/CBOM Notes: - SecureRandom (and SecureRandom.getInstanceStrong) are
|
||||
* recommended. - java.util.Random is not suitable for cryptographic purposes. -
|
||||
* Re-seeding or using a fixed seed with SecureRandom makes it predictable. -
|
||||
* IVs and nonces must be unique for each operation; reusing fixed values is
|
||||
* insecure.
|
||||
*/
|
||||
public class PrngTest {
|
||||
|
||||
// ---------- Secure Random Generation ----------
|
||||
/**
|
||||
* Generates random bytes using the default SecureRandom. SAST: SecureRandom
|
||||
* is recommended for cryptographically secure random data.
|
||||
*
|
||||
* @param numBytes Number of bytes to generate.
|
||||
* @return A byte array of random data.
|
||||
*/
|
||||
public byte[] generateSecureRandomBytes(int numBytes) {
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
byte[] bytes = new byte[numBytes];
|
||||
secureRandom.nextBytes(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates random bytes using SecureRandom.getInstanceStrong(). SAST:
|
||||
* getInstanceStrong() returns a strong RNG (may block in some
|
||||
* environments).
|
||||
*
|
||||
* @param numBytes Number of bytes to generate.
|
||||
* @return A byte array of random data.
|
||||
* @throws NoSuchAlgorithmException if a strong RNG is not available.
|
||||
*/
|
||||
public byte[] generateSecureRandomBytesStrong(int numBytes) throws NoSuchAlgorithmException {
|
||||
SecureRandom secureRandom = SecureRandom.getInstanceStrong();
|
||||
byte[] bytes = new byte[numBytes];
|
||||
secureRandom.nextBytes(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// ---------- Insecure Random Generation ----------
|
||||
/**
|
||||
* Generates random bytes using java.util.Random. SAST: java.util.Random is
|
||||
* predictable and insecure for cryptographic purposes.
|
||||
*
|
||||
* @param numBytes Number of bytes to generate.
|
||||
* @return A byte array of random data.
|
||||
*/
|
||||
public byte[] generateInsecureRandomBytes(int numBytes) {
|
||||
Random random = new Random();
|
||||
byte[] bytes = new byte[numBytes];
|
||||
random.nextBytes(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates random bytes using SecureRandom with a fixed seed. SAST: Fixed
|
||||
* seeding makes SecureRandom predictable and insecure.
|
||||
*
|
||||
* @param numBytes Number of bytes to generate.
|
||||
* @return A byte array of predictable random data.
|
||||
*/
|
||||
public byte[] generatePredictableRandomBytes(int numBytes) {
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
// Fixed seed (predictable and insecure)
|
||||
secureRandom.setSeed(0xDEADBEEF);
|
||||
byte[] bytes = new byte[numBytes];
|
||||
secureRandom.nextBytes(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// ---------- Dynamic PRNG Selection ----------
|
||||
/**
|
||||
* Dynamically selects a PRNG algorithm based on a configuration property.
|
||||
* If the algorithm is unknown, falls back to java.util.Random (insecure).
|
||||
* SAST: Dynamic selection may introduce risk if an insecure RNG is chosen.
|
||||
*
|
||||
* @param algorithmName The PRNG algorithm name (e.g. "SHA1PRNG",
|
||||
* "NativePRNGNonBlocking", "getInstanceStrong").
|
||||
* @param numBytes Number of bytes to generate.
|
||||
* @return A byte array of random data.
|
||||
* @throws NoSuchAlgorithmException if the algorithm is not available.
|
||||
*/
|
||||
public byte[] dynamicRandomGeneration(String algorithmName, int numBytes) throws NoSuchAlgorithmException {
|
||||
SecureRandom secureRandom;
|
||||
if ("SHA1PRNG".equalsIgnoreCase(algorithmName)) {
|
||||
// SHA1PRNG is older and less preferred.
|
||||
secureRandom = SecureRandom.getInstance("SHA1PRNG");
|
||||
} else if ("NativePRNGNonBlocking".equalsIgnoreCase(algorithmName)) {
|
||||
secureRandom = SecureRandom.getInstance("NativePRNGNonBlocking");
|
||||
} else if ("getInstanceStrong".equalsIgnoreCase(algorithmName)) {
|
||||
secureRandom = SecureRandom.getInstanceStrong();
|
||||
} else {
|
||||
// Fallback to insecure java.util.Random.
|
||||
Random random = new Random();
|
||||
byte[] bytes = new byte[numBytes];
|
||||
random.nextBytes(bytes);
|
||||
return bytes;
|
||||
}
|
||||
byte[] bytes = new byte[numBytes];
|
||||
secureRandom.nextBytes(bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// ---------- Usage Examples: Nonce/IV Generation for Symmetric Encryption
|
||||
// ----------
|
||||
/**
|
||||
* Demonstrates secure generation of an IV for AES-GCM encryption. SAST: A
|
||||
* unique, random IV is required for each encryption operation.
|
||||
*
|
||||
* @return A 12-byte IV.
|
||||
*/
|
||||
public byte[] generateRandomIVForGCM() {
|
||||
return generateSecureRandomBytes(12);
|
||||
}
|
||||
|
||||
/**
|
||||
* Demonstrates insecure use of a fixed IV for AES-GCM encryption. SAST:
|
||||
* Reusing a fixed IV in AES-GCM compromises security.
|
||||
*
|
||||
* @return A fixed 12-byte IV (all zeros).
|
||||
*/
|
||||
public byte[] generateFixedIVForGCM() {
|
||||
return new byte[12]; // 12 bytes of zeros.
|
||||
}
|
||||
|
||||
// ---------- Example: Using PRNG for Key Generation ----------
|
||||
/**
|
||||
* Generates a secure 256-bit AES key using SecureRandom. SAST: Strong key
|
||||
* generation is critical for symmetric cryptography.
|
||||
*
|
||||
* @return A new AES SecretKey.
|
||||
* @throws Exception if key generation fails.
|
||||
*/
|
||||
public SecretKey generateAESKey() throws Exception {
|
||||
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
|
||||
keyGen.init(256, new SecureRandom());
|
||||
return keyGen.generateKey();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
package com.example.crypto.algorithms;
|
||||
|
||||
// import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import java.security.*;
|
||||
import java.security.spec.ECGenParameterSpec;
|
||||
import java.util.Arrays;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
|
||||
/**
|
||||
* This class demonstrates cryptographic flows combining signing, encryption,
|
||||
* and MAC.
|
||||
*
|
||||
* It intentionally includes both safe and unsafe patterns so that a SAST tool
|
||||
* can detect:
|
||||
*
|
||||
* 1. **Sign then Encrypt (Unsafe)** - Signs the plaintext and encrypts only the
|
||||
* signature, leaving the plaintext in cleartext. - *Issue:* The message is
|
||||
* exposed, which could lead to replay or modification attacks.
|
||||
*
|
||||
* 2. **Encrypt then Sign (Safe with caveats)** - Encrypts the plaintext and
|
||||
* then signs the ciphertext. - *Caveat:* The signature is in the clear;
|
||||
* metadata (e.g. ciphertext length) may be exposed.
|
||||
*
|
||||
* 3. **MAC then Encrypt (Unsafe)** - Computes a MAC on the plaintext and
|
||||
* appends it before encryption. - *Issue:* Operating on plaintext for MAC
|
||||
* generation can leak information and is discouraged.
|
||||
*
|
||||
* 4. **Encrypt then MAC (Safe)** - Encrypts the plaintext and computes a MAC
|
||||
* over the ciphertext. - *Benefit:* Provides a robust authenticated encryption
|
||||
* construction when not using an AEAD cipher.
|
||||
*
|
||||
* Note: AES/GCM already provides authentication, so adding an external MAC is
|
||||
* redundant.
|
||||
*/
|
||||
public class SignEncryptCombinations {
|
||||
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
|
||||
// static {
|
||||
// Security.addProvider(new BouncyCastleProvider());
|
||||
// }
|
||||
///////////////////////////////////////////////
|
||||
// Key Generation for ECDSA on secp256r1
|
||||
///////////////////////////////////////////////
|
||||
|
||||
public KeyPair generateECDSAKeyPair() throws Exception {
|
||||
KeyPairGenerator ecKpg = KeyPairGenerator.getInstance("EC", "BC");
|
||||
ecKpg.initialize(new ECGenParameterSpec("secp256r1"), RANDOM);
|
||||
return ecKpg.generateKeyPair();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// Signing with ECDSA (SHA256withECDSA)
|
||||
///////////////////////////////////////////////
|
||||
|
||||
public byte[] signECDSA(PrivateKey privKey, byte[] data) throws Exception {
|
||||
Signature signature = Signature.getInstance("SHA256withECDSA", "BC");
|
||||
signature.initSign(privKey, RANDOM);
|
||||
signature.update(data);
|
||||
return signature.sign();
|
||||
}
|
||||
|
||||
public boolean verifyECDSA(PublicKey pubKey, byte[] data, byte[] signatureBytes) throws Exception {
|
||||
Signature signature = Signature.getInstance("SHA256withECDSA", "BC");
|
||||
signature.initVerify(pubKey);
|
||||
signature.update(data);
|
||||
return signature.verify(signatureBytes);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// Symmetric Encryption with AES-GCM
|
||||
///////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Generates a 256-bit AES key.
|
||||
*/
|
||||
public SecretKey generateAESKey() throws Exception {
|
||||
KeyGenerator kg = KeyGenerator.getInstance("AES");
|
||||
kg.init(256);
|
||||
return kg.generateKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts data using AES-GCM with a 12-byte IV and a 128-bit tag. Returns
|
||||
* the concatenation of IV and ciphertext.
|
||||
*/
|
||||
public byte[] encryptAESGCM(SecretKey key, byte[] plaintext) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
byte[] iv = new byte[12]; // 12-byte IV recommended for GCM
|
||||
RANDOM.nextBytes(iv);
|
||||
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
|
||||
byte[] ciphertext = cipher.doFinal(plaintext);
|
||||
|
||||
byte[] result = new byte[iv.length + ciphertext.length];
|
||||
System.arraycopy(iv, 0, result, 0, iv.length);
|
||||
System.arraycopy(ciphertext, 0, result, iv.length, ciphertext.length);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts data that was encrypted using encryptAESGCM.
|
||||
*/
|
||||
public byte[] decryptAESGCM(SecretKey key, byte[] ivCiphertext) throws Exception {
|
||||
byte[] iv = Arrays.copyOfRange(ivCiphertext, 0, 12);
|
||||
byte[] ciphertext = Arrays.copyOfRange(ivCiphertext, 12, ivCiphertext.length);
|
||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
|
||||
cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
|
||||
return cipher.doFinal(ciphertext);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// HMAC Usage with HMAC-SHA256
|
||||
///////////////////////////////////////////////
|
||||
|
||||
public byte[] computeHmacSHA256(SecretKey key, byte[] data) throws Exception {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
mac.init(key);
|
||||
return mac.doFinal(data);
|
||||
}
|
||||
|
||||
public boolean verifyHmacSHA256(SecretKey key, byte[] data, byte[] givenMac) throws Exception {
|
||||
byte[] computed = computeHmacSHA256(key, data);
|
||||
return Arrays.equals(computed, givenMac);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// 1) SIGN THEN ENCRYPT vs. ENCRYPT THEN SIGN
|
||||
///////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* UNSAFE FLOW: Signs the plaintext and encrypts only the signature.
|
||||
*
|
||||
* <p>
|
||||
* **Issue:** The plaintext message is not encrypted, only the signature is.
|
||||
* This exposes the original message to eavesdroppers and negates the purpose of
|
||||
* encryption.
|
||||
* </p>
|
||||
*
|
||||
* @param signingKey ECDSA private key for signing.
|
||||
* @param encryptionKey AES key for encryption.
|
||||
* @param data The plaintext message.
|
||||
* @return The encrypted signature only.
|
||||
*/
|
||||
public byte[] signThenEncrypt(PrivateKey signingKey, SecretKey encryptionKey, byte[] data) throws Exception {
|
||||
// Sign the plaintext message.
|
||||
byte[] signature = signECDSA(signingKey, data);
|
||||
// **** UNSAFE: Only the signature is encrypted. The plaintext remains in the
|
||||
// clear. ****
|
||||
return encryptAESGCM(encryptionKey, signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts the signature and verifies it against the original plaintext.
|
||||
*/
|
||||
public boolean decryptThenVerify(SecretKey encryptionKey, PublicKey verifyingKey, byte[] encryptedSig,
|
||||
byte[] originalData) throws Exception {
|
||||
byte[] decryptedSig = decryptAESGCM(encryptionKey, encryptedSig);
|
||||
return verifyECDSA(verifyingKey, originalData, decryptedSig);
|
||||
}
|
||||
|
||||
/**
|
||||
* SAFE FLOW (with caveats): Encrypts the plaintext and then signs the
|
||||
* ciphertext.
|
||||
*
|
||||
* <p>
|
||||
* **Benefit:** The plaintext is fully encrypted and remains confidential.
|
||||
* **Caveat:** The signature is transmitted in the clear. Although this does
|
||||
* not compromise the message, it might reveal metadata (like ciphertext
|
||||
* length).
|
||||
* </p>
|
||||
*
|
||||
* @param encryptionKey AES key for encryption.
|
||||
* @param signingKey ECDSA private key for signing.
|
||||
* @param data The plaintext message.
|
||||
* @return The concatenation of the ciphertext and its signature.
|
||||
*/
|
||||
public byte[] encryptThenSign(SecretKey encryptionKey, PrivateKey signingKey, byte[] data) throws Exception {
|
||||
// Encrypt the plaintext.
|
||||
byte[] ivCiphertext = encryptAESGCM(encryptionKey, data);
|
||||
// Sign the ciphertext.
|
||||
byte[] signature = signECDSA(signingKey, ivCiphertext);
|
||||
|
||||
// Combine ciphertext and signature.
|
||||
byte[] combined = new byte[ivCiphertext.length + signature.length];
|
||||
System.arraycopy(ivCiphertext, 0, combined, 0, ivCiphertext.length);
|
||||
System.arraycopy(signature, 0, combined, ivCiphertext.length, signature.length);
|
||||
return combined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts and verifies the signature from the combined
|
||||
* ciphertext-signature bundle, then decrypts the ciphertext.
|
||||
*
|
||||
* <p>
|
||||
* **Issue:** Here we assume a fixed signature length (70 bytes). In
|
||||
* production, the signature length should be explicitly stored. Hard-coding
|
||||
* a length is an unsafe pattern and may trigger SAST warnings.
|
||||
* </p>
|
||||
*
|
||||
* @param verifyingKey ECDSA public key for signature verification.
|
||||
* @param encryptionKey AES key for decryption.
|
||||
* @param combined The combined ciphertext and signature.
|
||||
* @return The decrypted plaintext message.
|
||||
*/
|
||||
public byte[] verifyThenDecrypt(PublicKey verifyingKey, SecretKey encryptionKey, byte[] combined) throws Exception {
|
||||
int assumedSignatureLength = 70; // UNSAFE: Hard-coded signature length.
|
||||
if (combined.length < assumedSignatureLength) {
|
||||
throw new IllegalArgumentException("Combined data too short.");
|
||||
}
|
||||
int ctLen = combined.length - assumedSignatureLength;
|
||||
byte[] ivCiphertext = Arrays.copyOfRange(combined, 0, ctLen);
|
||||
byte[] signature = Arrays.copyOfRange(combined, ctLen, combined.length);
|
||||
|
||||
if (!verifyECDSA(verifyingKey, ivCiphertext, signature)) {
|
||||
throw new SecurityException("Signature verification failed.");
|
||||
}
|
||||
return decryptAESGCM(encryptionKey, ivCiphertext);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// 2) MAC THEN ENCRYPT vs. ENCRYPT THEN MAC
|
||||
///////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* UNSAFE FLOW: Computes a MAC on the plaintext, appends it to the plaintext,
|
||||
* and then encrypts the combined data.
|
||||
*
|
||||
* <p>
|
||||
* **Issue:** Operating on unencrypted plaintext to compute the MAC can leak
|
||||
* structural
|
||||
* information. Additionally, if the encryption scheme does not provide
|
||||
* integrity,
|
||||
* this construction is vulnerable.
|
||||
* </p>
|
||||
*
|
||||
* @param macKey AES key used as the HMAC key (should be separate from the
|
||||
* encryption key).
|
||||
* @param encKey AES key for encryption.
|
||||
* @param data The plaintext message.
|
||||
* @return The encrypted (plaintext + MAC) bundle.
|
||||
*/
|
||||
public byte[] macThenEncrypt(SecretKey macKey, SecretKey encKey, byte[] data) throws Exception {
|
||||
// Compute MAC over the plaintext.
|
||||
byte[] mac = computeHmacSHA256(macKey, data);
|
||||
// Combine plaintext and MAC.
|
||||
byte[] combined = new byte[data.length + mac.length];
|
||||
System.arraycopy(data, 0, combined, 0, data.length);
|
||||
System.arraycopy(mac, 0, combined, data.length, mac.length);
|
||||
// **** UNSAFE: The MAC is computed on plaintext, which can leak information.
|
||||
// ****
|
||||
return encryptAESGCM(encKey, combined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts the combined data and verifies the MAC.
|
||||
*
|
||||
* @param macKey AES key used as the HMAC key.
|
||||
* @param encKey AES key for decryption.
|
||||
* @param ciphertext The encrypted bundle containing plaintext and MAC.
|
||||
* @return true if the MAC is valid; false otherwise.
|
||||
*/
|
||||
public boolean decryptThenVerifyMac(SecretKey macKey, SecretKey encKey, byte[] ciphertext) throws Exception {
|
||||
byte[] combined = decryptAESGCM(encKey, ciphertext);
|
||||
if (combined.length < 32) { // HMAC-SHA256 produces a 32-byte MAC.
|
||||
throw new IllegalArgumentException("Combined data too short for MAC verification.");
|
||||
}
|
||||
int dataLen = combined.length - 32;
|
||||
byte[] originalData = Arrays.copyOfRange(combined, 0, dataLen);
|
||||
byte[] extractedMac = Arrays.copyOfRange(combined, dataLen, combined.length);
|
||||
return verifyHmacSHA256(macKey, originalData, extractedMac);
|
||||
}
|
||||
|
||||
/**
|
||||
* SAFE FLOW: Encrypts the plaintext and then computes a MAC over the
|
||||
* ciphertext.
|
||||
*
|
||||
* <p>
|
||||
* **Benefit:** This "encrypt-then-MAC" construction ensures that the
|
||||
* ciphertext is both confidential and tamper-evident.
|
||||
* </p>
|
||||
*
|
||||
* @param encKey AES key for encryption.
|
||||
* @param macKey AES key used as the HMAC key.
|
||||
* @param data The plaintext message.
|
||||
* @return The concatenation of ciphertext and MAC.
|
||||
*/
|
||||
public byte[] encryptThenMac(SecretKey encKey, SecretKey macKey, byte[] data) throws Exception {
|
||||
// Encrypt the plaintext.
|
||||
byte[] ivCiphertext = encryptAESGCM(encKey, data);
|
||||
// Compute MAC over the ciphertext.
|
||||
byte[] mac = computeHmacSHA256(macKey, ivCiphertext);
|
||||
// Combine ciphertext and MAC.
|
||||
byte[] combined = new byte[ivCiphertext.length + mac.length];
|
||||
System.arraycopy(ivCiphertext, 0, combined, 0, ivCiphertext.length);
|
||||
System.arraycopy(mac, 0, combined, ivCiphertext.length, mac.length);
|
||||
return combined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the MAC and then decrypts the ciphertext.
|
||||
*
|
||||
* @param encKey AES key for decryption.
|
||||
* @param macKey AES key used as the HMAC key.
|
||||
* @param combined The combined ciphertext and MAC.
|
||||
* @return The decrypted plaintext message.
|
||||
*/
|
||||
public byte[] verifyMacThenDecrypt(SecretKey encKey, SecretKey macKey, byte[] combined) throws Exception {
|
||||
if (combined.length < 32) {
|
||||
throw new IllegalArgumentException("Combined data too short for MAC verification.");
|
||||
}
|
||||
int macOffset = combined.length - 32;
|
||||
byte[] ivCiphertext = Arrays.copyOfRange(combined, 0, macOffset);
|
||||
byte[] extractedMac = Arrays.copyOfRange(combined, macOffset, combined.length);
|
||||
if (!verifyHmacSHA256(macKey, ivCiphertext, extractedMac)) {
|
||||
throw new SecurityException("MAC verification failed.");
|
||||
}
|
||||
return decryptAESGCM(encKey, ivCiphertext);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// Demo: runAllCombinations
|
||||
///////////////////////////////////////////////
|
||||
|
||||
public void runAllCombinations() throws Exception {
|
||||
// Generate keys for signing and for encryption/MAC.
|
||||
KeyPair signingKeys = generateECDSAKeyPair();
|
||||
SecretKey encKey = generateAESKey();
|
||||
SecretKey macKey = generateAESKey(); // Ensure a separate key for MAC operations.
|
||||
|
||||
byte[] message = "Hello, combinations!".getBytes();
|
||||
|
||||
// 1) Sign then Encrypt (Unsafe) vs. Encrypt then Sign (Safe with caveats)
|
||||
System.out.println("--Sign Then Encrypt (UNSAFE)");
|
||||
byte[] encryptedSig = signThenEncrypt(signingKeys.getPrivate(), encKey, message);
|
||||
boolean steVerified = decryptThenVerify(encKey, signingKeys.getPublic(), encryptedSig, message);
|
||||
System.out.println("signThenEncrypt -> signature verified? " + steVerified);
|
||||
|
||||
System.out.println("--Encrypt Then Sign (SAFE with caveats)");
|
||||
byte[] combinedETS = encryptThenSign(encKey, signingKeys.getPrivate(), message);
|
||||
byte[] finalPlain = verifyThenDecrypt(signingKeys.getPublic(), encKey, combinedETS);
|
||||
System.out.println("encryptThenSign -> decrypted message: " + new String(finalPlain));
|
||||
|
||||
// 2) MAC then Encrypt (Unsafe) vs. Encrypt then MAC (Safe)
|
||||
System.out.println("--MAC Then Encrypt (UNSAFE)");
|
||||
byte[] mteCipher = macThenEncrypt(macKey, encKey, message);
|
||||
boolean mteVerified = decryptThenVerifyMac(macKey, encKey, mteCipher);
|
||||
System.out.println("macThenEncrypt -> MAC verified? " + mteVerified);
|
||||
|
||||
System.out.println("--Encrypt Then MAC (SAFE)");
|
||||
byte[] etmCombined = encryptThenMac(encKey, macKey, message);
|
||||
byte[] etmPlain = verifyMacThenDecrypt(encKey, macKey, etmCombined);
|
||||
System.out.println("encryptThenMac -> decrypted message: " + new String(etmPlain));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
package com.example.crypto.algorithms;
|
||||
|
||||
// import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import java.security.*;
|
||||
import java.security.spec.ECGenParameterSpec;
|
||||
import java.util.Base64;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* Demonstrates various digital signature operations:
|
||||
*
|
||||
* 1) RSA-PSS (modern, safer) - CBOM/SAST: Classified as a Modern Digital
|
||||
* Signature scheme using RSA-PSS. RSA-PSS with SHA-256 is recommended.
|
||||
*
|
||||
* 2) ECDSA with secp256r1 - CBOM/SAST: Classified as an Elliptic Curve Digital
|
||||
* Signature Algorithm. Secure when used with a strong curve and proper
|
||||
* randomness.
|
||||
*
|
||||
* 3) Ed25519 (RFC 8032) - CBOM/SAST: Classified as a modern, high-performance
|
||||
* signature scheme.
|
||||
*
|
||||
* 4) SHA1withRSA (deprecated/unsafe example) - CBOM/SAST: Classified as a
|
||||
* legacy digital signature scheme. SHA-1 and 1024-bit RSA are deprecated.
|
||||
*
|
||||
* Additional nuanced examples:
|
||||
*
|
||||
* - Signing and verifying an empty message. - Signing data with non-ASCII
|
||||
* characters. - Demonstrating signature tampering and its detection. - A
|
||||
* dynamic (runtime-selected) signature algorithm scenario ("known unknown").
|
||||
*
|
||||
* Requirements: - BouncyCastle for ECDSA, Ed25519, and RSA-PSS (if needed). -
|
||||
* Java 11+ for native Ed25519 support or using BC for older versions.
|
||||
*/
|
||||
public class SignatureOperation {
|
||||
|
||||
// static {
|
||||
// // Register the BouncyCastle provider.
|
||||
// Security.addProvider(new BouncyCastleProvider());
|
||||
// }
|
||||
///////////////////////////////////////
|
||||
// 1. RSA-PSS (Recommended)
|
||||
///////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Generate an RSA key pair for RSA-PSS.
|
||||
* Uses a 2048-bit key.
|
||||
*
|
||||
* CBOM/SAST Notes:
|
||||
* - Parent: Modern Digital Signature (RSA-PSS).
|
||||
*/
|
||||
public KeyPair generateRSAPSSKeyPair() throws Exception {
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
|
||||
kpg.initialize(2048);
|
||||
return kpg.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign data using RSA-PSS with SHA-256.
|
||||
*
|
||||
* CBOM/SAST Notes: - Parent: Modern Digital Signature (RSA-PSS).
|
||||
*/
|
||||
public byte[] signRSAPSS(PrivateKey privateKey, byte[] data) throws Exception {
|
||||
Signature signature = Signature.getInstance("SHA256withRSAandMGF1");
|
||||
signature.initSign(privateKey);
|
||||
signature.update(data);
|
||||
return signature.sign();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify data using RSA-PSS with SHA-256.
|
||||
*
|
||||
* CBOM/SAST Notes: - Parent: Modern Digital Signature (RSA-PSS).
|
||||
*/
|
||||
public boolean verifyRSAPSS(PublicKey publicKey, byte[] data, byte[] sigBytes) throws Exception {
|
||||
Signature signature = Signature.getInstance("SHA256withRSAandMGF1");
|
||||
signature.initVerify(publicKey);
|
||||
signature.update(data);
|
||||
return signature.verify(sigBytes);
|
||||
}
|
||||
|
||||
///////////////////////////////////////
|
||||
// 2. ECDSA (secp256r1)
|
||||
///////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Generate an ECDSA key pair on secp256r1.
|
||||
*
|
||||
* CBOM/SAST Notes:
|
||||
* - Parent: Elliptic Curve Digital Signature.
|
||||
*/
|
||||
public KeyPair generateECDSAKeyPair() throws Exception {
|
||||
KeyPairGenerator ecKpg = KeyPairGenerator.getInstance("EC", "BC");
|
||||
ecKpg.initialize(new ECGenParameterSpec("secp256r1"), new SecureRandom());
|
||||
return ecKpg.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign data using ECDSA with SHA-256.
|
||||
*
|
||||
* CBOM/SAST Notes: - Parent: Elliptic Curve Digital Signature.
|
||||
*/
|
||||
public byte[] signECDSA(PrivateKey privateKey, byte[] data) throws Exception {
|
||||
Signature signature = Signature.getInstance("SHA256withECDSA", "BC");
|
||||
signature.initSign(privateKey);
|
||||
signature.update(data);
|
||||
return signature.sign();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify data using ECDSA with SHA-256.
|
||||
*
|
||||
* CBOM/SAST Notes: - Parent: Elliptic Curve Digital Signature.
|
||||
*/
|
||||
public boolean verifyECDSA(PublicKey publicKey, byte[] data, byte[] sigBytes) throws Exception {
|
||||
Signature signature = Signature.getInstance("SHA256withECDSA", "BC");
|
||||
signature.initVerify(publicKey);
|
||||
signature.update(data);
|
||||
return signature.verify(sigBytes);
|
||||
}
|
||||
|
||||
///////////////////////////////////////
|
||||
// 3. Ed25519 (RFC 8032)
|
||||
///////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Generate an Ed25519 key pair.
|
||||
*
|
||||
* CBOM/SAST Notes:
|
||||
* - Parent: Modern Digital Signature (EdDSA).
|
||||
*/
|
||||
public KeyPair generateEd25519KeyPair() throws Exception {
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("Ed25519", "BC");
|
||||
return kpg.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign data using Ed25519.
|
||||
*
|
||||
* CBOM/SAST Notes: - Parent: Modern Digital Signature (EdDSA).
|
||||
*/
|
||||
public byte[] signEd25519(PrivateKey privateKey, byte[] data) throws Exception {
|
||||
Signature signature = Signature.getInstance("Ed25519", "BC");
|
||||
signature.initSign(privateKey);
|
||||
signature.update(data);
|
||||
return signature.sign();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify data using Ed25519.
|
||||
*
|
||||
* CBOM/SAST Notes: - Parent: Modern Digital Signature (EdDSA).
|
||||
*/
|
||||
public boolean verifyEd25519(PublicKey publicKey, byte[] data, byte[] sigBytes) throws Exception {
|
||||
Signature signature = Signature.getInstance("Ed25519", "BC");
|
||||
signature.initVerify(publicKey);
|
||||
signature.update(data);
|
||||
return signature.verify(sigBytes);
|
||||
}
|
||||
|
||||
///////////////////////////////////////
|
||||
// 4. SHA1withRSA (Deprecated/Unsafe)
|
||||
///////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Generate an RSA key pair for the deprecated/unsafe example.
|
||||
* Uses a 1024-bit key.
|
||||
*
|
||||
* CBOM/SAST Notes:
|
||||
* - Parent: Legacy Digital Signature.
|
||||
* - RSA with SHA-1 and 1024-bit keys is deprecated and should be avoided.
|
||||
*/
|
||||
public KeyPair generateRSAUnsafeKeyPair() throws Exception {
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
|
||||
kpg.initialize(1024);
|
||||
return kpg.generateKeyPair();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign data using SHA1withRSA.
|
||||
*
|
||||
* CBOM/SAST Notes: - Parent: Legacy Digital Signature. - SHA-1 is
|
||||
* deprecated and RSA with 1024 bits is considered weak.
|
||||
*/
|
||||
public byte[] signSHA1withRSA(PrivateKey privateKey, byte[] data) throws Exception {
|
||||
Signature signature = Signature.getInstance("SHA1withRSA");
|
||||
signature.initSign(privateKey);
|
||||
signature.update(data);
|
||||
return signature.sign();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify data using SHA1withRSA.
|
||||
*
|
||||
* CBOM/SAST Notes: - Parent: Legacy Digital Signature. - Verification of
|
||||
* SHA1withRSA is insecure.
|
||||
*/
|
||||
public boolean verifySHA1withRSA(PublicKey publicKey, byte[] data, byte[] sigBytes) throws Exception {
|
||||
Signature signature = Signature.getInstance("SHA1withRSA");
|
||||
signature.initVerify(publicKey);
|
||||
signature.update(data);
|
||||
return signature.verify(sigBytes);
|
||||
}
|
||||
|
||||
///////////////////////////////////////
|
||||
// Nuanced Edge-Case Examples
|
||||
///////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Demonstrates signing and verifying an empty message.
|
||||
*
|
||||
* CBOM/SAST Notes:
|
||||
* - Edge Case: Signing empty input should be handled correctly but might be
|
||||
* unexpected.
|
||||
*/
|
||||
public void signAndVerifyEmptyMessage() throws Exception {
|
||||
byte[] emptyMessage = new byte[0];
|
||||
KeyPair kp = generateRSAPSSKeyPair();
|
||||
byte[] sig = signRSAPSS(kp.getPrivate(), emptyMessage);
|
||||
boolean verified = verifyRSAPSS(kp.getPublic(), emptyMessage, sig);
|
||||
System.out.println("Empty message signature verified? " + verified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Demonstrates that even a slight tampering with the signature will cause
|
||||
* verification to fail.
|
||||
*
|
||||
* CBOM/SAST Notes: - Edge Case: Signature integrity is critical. Any
|
||||
* change-even a single byte-should invalidate the signature.
|
||||
*/
|
||||
public void tamperSignatureEdgeCase() throws Exception {
|
||||
byte[] message = "Important Message".getBytes();
|
||||
KeyPair kp = generateECDSAKeyPair();
|
||||
byte[] originalSig = signECDSA(kp.getPrivate(), message);
|
||||
// Tamper with the signature by flipping one bit.
|
||||
byte[] tamperedSig = originalSig.clone();
|
||||
tamperedSig[0] ^= 0x01;
|
||||
boolean verifiedOriginal = verifyECDSA(kp.getPublic(), message, originalSig);
|
||||
boolean verifiedTampered = verifyECDSA(kp.getPublic(), message, tamperedSig);
|
||||
System.out.println("Original ECDSA signature verified? " + verifiedOriginal);
|
||||
System.out.println("Tampered ECDSA signature verified? " + verifiedTampered);
|
||||
}
|
||||
|
||||
/**
|
||||
* Demonstrates dynamic signature algorithm selection. This is a "known
|
||||
* unknown" scenario where the algorithm is chosen at runtime based on
|
||||
* configuration. If the configuration is compromised or misconfigured, an
|
||||
* insecure algorithm might be selected.
|
||||
*
|
||||
* CBOM/SAST Notes: - Known Unknown: Dynamic configuration introduces
|
||||
* ambiguity and risk. - Ensure that fallback defaults are secure.
|
||||
*/
|
||||
public void dynamicSignatureSelectionDemo() throws Exception {
|
||||
// Simulate loading a configuration.
|
||||
Properties config = new Properties();
|
||||
// For demonstration, let's assume the config might specify an algorithm.
|
||||
// Possible values: "SHA256withRSAandMGF1", "SHA256withECDSA", "Ed25519",
|
||||
// "SHA1withRSA"
|
||||
// Here we simulate an unknown or insecure algorithm being selected.
|
||||
config.setProperty("signature.algorithm", "SHA1withRSA"); // Insecure choice!
|
||||
String algorithm = config.getProperty("signature.algorithm", "SHA256withRSAandMGF1");
|
||||
|
||||
KeyPair kp;
|
||||
Signature signature;
|
||||
if ("SHA256withRSAandMGF1".equalsIgnoreCase(algorithm)) {
|
||||
kp = generateRSAPSSKeyPair();
|
||||
signature = Signature.getInstance("SHA256withRSAandMGF1");
|
||||
} else if ("SHA256withECDSA".equalsIgnoreCase(algorithm)) {
|
||||
kp = generateECDSAKeyPair();
|
||||
signature = Signature.getInstance("SHA256withECDSA", "BC");
|
||||
} else if ("Ed25519".equalsIgnoreCase(algorithm)) {
|
||||
kp = generateEd25519KeyPair();
|
||||
signature = Signature.getInstance("Ed25519", "BC");
|
||||
} else if ("SHA1withRSA".equalsIgnoreCase(algorithm)) {
|
||||
kp = generateRSAUnsafeKeyPair();
|
||||
signature = Signature.getInstance("SHA1withRSA");
|
||||
} else {
|
||||
// Fallback to a secure default.
|
||||
kp = generateRSAPSSKeyPair();
|
||||
signature = Signature.getInstance("SHA256withRSAandMGF1");
|
||||
}
|
||||
|
||||
byte[] message = "Dynamic Signature Demo".getBytes();
|
||||
signature.initSign(kp.getPrivate());
|
||||
signature.update(message);
|
||||
byte[] sigBytes = signature.sign();
|
||||
// Verify using the same algorithm.
|
||||
signature.initVerify(kp.getPublic());
|
||||
signature.update(message);
|
||||
boolean verified = signature.verify(sigBytes);
|
||||
System.out.println("Dynamic algorithm (" + algorithm + ") signature verified? " + verified);
|
||||
}
|
||||
|
||||
///////////////////////////////////////
|
||||
// Demo Method: runSignatureDemos
|
||||
///////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Demonstrates digital signature operations for various algorithms.
|
||||
* It generates key pairs, signs a message, and verifies the signature for:
|
||||
* - RSA-PSS
|
||||
* - ECDSA (secp256r1)
|
||||
* - Ed25519
|
||||
* - SHA1withRSA (deprecated/unsafe)
|
||||
* Additionally, it runs several edge-case demos.
|
||||
*
|
||||
* CBOM/SAST Classification:
|
||||
* - Shows both modern, secure signature schemes and a deprecated example.
|
||||
* - Also demonstrates handling of edge cases and dynamic selection risks.
|
||||
*/
|
||||
public void runSignatureDemos() throws Exception {
|
||||
byte[] message = "Hello Signature World!".getBytes();
|
||||
|
||||
// ============ RSA-PSS ============
|
||||
KeyPair rsaPssKP = generateRSAPSSKeyPair();
|
||||
byte[] rsaPssSig = signRSAPSS(rsaPssKP.getPrivate(), message);
|
||||
System.out.println("RSA-PSS Signature: " + Base64.getEncoder().encodeToString(rsaPssSig));
|
||||
boolean rsaPssVerified = verifyRSAPSS(rsaPssKP.getPublic(), message, rsaPssSig);
|
||||
System.out.println("RSA-PSS Verified? " + rsaPssVerified);
|
||||
|
||||
// ============ ECDSA (secp256r1) ============
|
||||
KeyPair ecdsaKP = generateECDSAKeyPair();
|
||||
byte[] ecdsaSig = signECDSA(ecdsaKP.getPrivate(), message);
|
||||
System.out.println("ECDSA Signature: " + Base64.getEncoder().encodeToString(ecdsaSig));
|
||||
boolean ecdsaVerified = verifyECDSA(ecdsaKP.getPublic(), message, ecdsaSig);
|
||||
System.out.println("ECDSA Verified? " + ecdsaVerified);
|
||||
|
||||
// ============ Ed25519 ============
|
||||
KeyPair ed25519KP = generateEd25519KeyPair();
|
||||
byte[] ed25519Sig = signEd25519(ed25519KP.getPrivate(), message);
|
||||
System.out.println("Ed25519 Signature: " + Base64.getEncoder().encodeToString(ed25519Sig));
|
||||
boolean ed25519Verified = verifyEd25519(ed25519KP.getPublic(), message, ed25519Sig);
|
||||
System.out.println("Ed25519 Verified? " + ed25519Verified);
|
||||
|
||||
// ============ SHA1withRSA (Deprecated/Unsafe) ============
|
||||
KeyPair rsaUnsafeKP = generateRSAUnsafeKeyPair();
|
||||
byte[] rsaUnsafeSig = signSHA1withRSA(rsaUnsafeKP.getPrivate(), message);
|
||||
System.out.println("SHA1withRSA Signature (Insecure): " + Base64.getEncoder().encodeToString(rsaUnsafeSig));
|
||||
boolean rsaUnsafeVerified = verifySHA1withRSA(rsaUnsafeKP.getPublic(), message, rsaUnsafeSig);
|
||||
System.out.println("SHA1withRSA Verified? " + rsaUnsafeVerified);
|
||||
|
||||
// ============ Edge Cases ============
|
||||
signAndVerifyEmptyMessage();
|
||||
tamperSignatureEdgeCase();
|
||||
dynamicSignatureSelectionDemo();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package com.example.crypto.algorithms;
|
||||
|
||||
//import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
|
||||
/**
|
||||
* SymmetricModesTest demonstrates the use of advanced cipher modes for
|
||||
* symmetric encryption:
|
||||
*
|
||||
* 1. AES/KWP/NoPadding: Uses AES Key Wrap with Padding (KWP) to securely wrap
|
||||
* (encrypt) a key. - Secure usage: Uses a randomly generated wrapping key.
|
||||
*
|
||||
* 2. AES/OFB8/NoPadding: Uses AES in Output Feedback mode with an 8-bit
|
||||
* feedback size. - Secure usage: Uses a random IV for each encryption. -
|
||||
* Insecure usage: Using a fixed IV (or nonce) in OFB mode compromises
|
||||
* confidentiality.
|
||||
*
|
||||
* In production, algorithm parameters (such as mode, padding, and IV
|
||||
* generation) should be externalized via configuration files to support crypto
|
||||
* agility.
|
||||
*/
|
||||
public class SymmetricModesTest {
|
||||
|
||||
// static {
|
||||
// // Register BouncyCastle provider for additional cipher modes.
|
||||
// Security.addProvider(new BouncyCastleProvider());
|
||||
// }
|
||||
// ---------------------------
|
||||
// AES/KWP/NoPadding Example
|
||||
// ---------------------------
|
||||
/**
|
||||
* Securely wraps a target AES key using AES/KWP/NoPadding.
|
||||
*
|
||||
* Best Practice: - The wrapping key must be generated randomly. - AES/KWP
|
||||
* provides key wrapping with padding, suitable for keys whose lengths are
|
||||
* not multiples of the block size.
|
||||
*
|
||||
* @return The Base64-encoded wrapped key.
|
||||
* @throws Exception if an error occurs during key wrapping.
|
||||
*/
|
||||
public String secureAESKWPWrap() throws Exception {
|
||||
// Generate a random wrapping key (256-bit) for key wrapping.
|
||||
KeyGenerator kg = KeyGenerator.getInstance("AES");
|
||||
kg.init(256, new SecureRandom());
|
||||
SecretKey wrappingKey = kg.generateKey();
|
||||
|
||||
// Generate a target AES key to be wrapped (128-bit).
|
||||
kg.init(128, new SecureRandom());
|
||||
SecretKey targetKey = kg.generateKey();
|
||||
|
||||
// Use AES/KWP (Key Wrap with Padding) to wrap the target key.
|
||||
Cipher cipher = Cipher.getInstance("AES/KWP/NoPadding", "BC");
|
||||
cipher.init(Cipher.WRAP_MODE, wrappingKey);
|
||||
byte[] wrappedKey = cipher.wrap(targetKey);
|
||||
|
||||
return Base64.getEncoder().encodeToString(wrappedKey);
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// AES/OFB8/NoPadding Examples
|
||||
// ---------------------------
|
||||
/**
|
||||
* Securely encrypts plaintext using AES in OFB mode with an 8-bit feedback
|
||||
* size (AES/OFB8/NoPadding).
|
||||
*
|
||||
* Best Practice: - Use a fresh, random IV for each encryption operation.
|
||||
*
|
||||
* @param key The AES key.
|
||||
* @param plaintext The plaintext to encrypt.
|
||||
* @return The ciphertext (Base64-encoded) with the IV prepended.
|
||||
* @throws Exception if encryption fails.
|
||||
*/
|
||||
public String secureAesOfb8Encryption(SecretKey key, byte[] plaintext) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance("AES/OFB8/NoPadding", "BC");
|
||||
byte[] iv = new byte[16]; // IV size for AES block cipher (128-bit) even if feedback is 8-bit.
|
||||
new SecureRandom().nextBytes(iv);
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
|
||||
byte[] ciphertext = cipher.doFinal(plaintext);
|
||||
// Prepend IV to ciphertext (as is common practice)
|
||||
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 Base64.getEncoder().encodeToString(output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insecurely encrypts plaintext using AES in OFB mode with an 8-bit
|
||||
* feedback size (AES/OFB8/NoPadding) by using a fixed IV.
|
||||
*
|
||||
* Best Practice Violation: - Using a fixed IV (or nonce) with any
|
||||
* encryption mode (including OFB) compromises the cipher's security.
|
||||
*
|
||||
* @param key The AES key.
|
||||
* @param plaintext The plaintext to encrypt.
|
||||
* @return The ciphertext (Base64-encoded) with the fixed IV prepended.
|
||||
* @throws Exception if encryption fails.
|
||||
*/
|
||||
public String insecureAesOfb8Encryption(SecretKey key, byte[] plaintext) throws Exception {
|
||||
Cipher cipher = Cipher.getInstance("AES/OFB8/NoPadding", "BC");
|
||||
// Fixed IV: Insecure because it causes nonce/IV reuse.
|
||||
byte[] fixedIV = new byte[16]; // All zeros.
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(fixedIV);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
|
||||
byte[] ciphertext = cipher.doFinal(plaintext);
|
||||
byte[] output = new byte[fixedIV.length + ciphertext.length];
|
||||
System.arraycopy(fixedIV, 0, output, 0, fixedIV.length);
|
||||
System.arraycopy(ciphertext, 0, output, fixedIV.length, ciphertext.length);
|
||||
return Base64.getEncoder().encodeToString(output);
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// Helper Methods
|
||||
// ---------------------------
|
||||
/**
|
||||
* Generates a secure 256-bit AES key.
|
||||
*
|
||||
* @return A new AES SecretKey.
|
||||
* @throws Exception if key generation fails.
|
||||
*/
|
||||
public SecretKey generateAESKey() throws Exception {
|
||||
KeyGenerator kg = KeyGenerator.getInstance("AES");
|
||||
kg.init(256, new SecureRandom());
|
||||
return kg.generateKey();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.example.crypto.algorithms;
|
||||
|
||||
// import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import java.security.*;
|
||||
import java.util.Base64;
|
||||
import java.util.Random;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.Files;
|
||||
import java.io.IOException;
|
||||
|
||||
public class UniversalFlowTest {
|
||||
|
||||
public void simpleAESEncryption() throws Exception {
|
||||
String algorithm = "AES";
|
||||
String otherAlgorithm = loadAlgorithmFromDisk();
|
||||
|
||||
// Randomly select between the known algorithm and the one loaded from disk
|
||||
String selectedAlgorithm = (new Random().nextInt(2) == 0) ? algorithm : otherAlgorithm;
|
||||
|
||||
KeyGenerator keyGen = KeyGenerator.getInstance(selectedAlgorithm);
|
||||
keyGen.init(256); // 256-bit AES key.
|
||||
SecretKey key = keyGen.generateKey();
|
||||
String algorithm2 = "AES/GCM/NoPadding";
|
||||
Cipher cipher = Cipher.getInstance(algorithm2);
|
||||
byte[] iv = new byte[12]; // 12-byte IV recommended for GCM.
|
||||
new SecureRandom().nextBytes(iv);
|
||||
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv); // 128-bit authentication tag.
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key, gcmSpec);
|
||||
byte[] encryptedData = cipher.doFinal("Sensitive Data".getBytes());
|
||||
}
|
||||
|
||||
// Method to load algorithm from disk
|
||||
private String loadAlgorithmFromDisk() {
|
||||
try {
|
||||
// Implementation to load algorithm name from a file
|
||||
Path path = Paths.get("algorithm.txt");
|
||||
return Files.readString(path).trim();
|
||||
} catch (IOException e) {
|
||||
// Fallback to default algorithm if loading fails
|
||||
System.err.println("Failed to load algorithm from disk: " + e.getMessage());
|
||||
return "AES";
|
||||
}
|
||||
}
|
||||
}
|
||||
1725
java/ql/test/experimental/library-tests/quantum/node_edges.expected
Normal file
1725
java/ql/test/experimental/library-tests/quantum/node_edges.expected
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
import java
|
||||
import experimental.quantum.Language
|
||||
|
||||
from Crypto::NodeBase n, string key
|
||||
select n, key, n.getChild(key)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
||||
import java
|
||||
import experimental.quantum.Language
|
||||
|
||||
from Crypto::NodeBase n, string key, string value, Location location
|
||||
where n.properties(key, value, location)
|
||||
select n, key, value, location
|
||||
1515
java/ql/test/experimental/library-tests/quantum/nodes.expected
Normal file
1515
java/ql/test/experimental/library-tests/quantum/nodes.expected
Normal file
File diff suppressed because it is too large
Load Diff
5
java/ql/test/experimental/library-tests/quantum/nodes.ql
Normal file
5
java/ql/test/experimental/library-tests/quantum/nodes.ql
Normal file
@@ -0,0 +1,5 @@
|
||||
import java
|
||||
import experimental.quantum.Language
|
||||
|
||||
from Crypto::NodeBase n
|
||||
select n
|
||||
Reference in New Issue
Block a user