refactor!: consolidate crypto architecture and security model
* make ZeroEchoSession the sole policy, audit, and runtime boundary * replace combined key builders with operation-specific SPI and typed metadata * remove obsolete pre-release compatibility APIs and global crypto operations * finalize JCA agreement contexts and replace inheritance with composition * harden secret lifecycle, key destruction, hybrid KEX, PBKDF2, and audit handling * standardize PairSeq I/O and introduce immutable validated value types * migrate app, ext, samples, and required pki integration points * expand correctness, security, concurrency, and malformed-input coverage BREAKING CHANGE: removes deprecated pre-release global configuration, legacy context factories, combined key-builder contracts, String-based password APIs, unchecked PairSeq writing, BlockGeometry public fields, and other compatibility facades.
This commit is contained in:
@@ -50,7 +50,6 @@ import conflux.Ctx;
|
||||
import conflux.CtxInterface;
|
||||
import zeroecho.core.ConfluxKeys;
|
||||
import zeroecho.core.CryptoAlgorithm;
|
||||
import zeroecho.core.CryptoAlgorithms;
|
||||
import zeroecho.core.KeyUsage;
|
||||
import zeroecho.core.alg.aes.AesKeyGenSpec;
|
||||
import zeroecho.core.alg.aes.AesSpec;
|
||||
@@ -61,23 +60,16 @@ import zeroecho.sdk.builders.alg.AesDataContentBuilder;
|
||||
import zeroecho.sdk.builders.core.DataContentChainBuilder;
|
||||
import zeroecho.sdk.builders.core.PlainBytesBuilder;
|
||||
import zeroecho.sdk.content.api.DataContent;
|
||||
import zeroecho.sdk.ZeroEchoSession;
|
||||
|
||||
class AesTest {
|
||||
private static final Logger LOG = Logger.getLogger(AesTest.class.getName());
|
||||
private final ZeroEchoSession zeroEchoSession = new ZeroEchoSession();
|
||||
|
||||
SecretKey generateAesKey() throws GeneralSecurityException {
|
||||
// Locate the AES algorithm in the catalog
|
||||
CryptoAlgorithm aes = CryptoAlgorithms.require("AES");
|
||||
SecretKey key = aes
|
||||
// Retrieve the builder that works with AesKeyGenSpec - the specification for
|
||||
// AES key generation
|
||||
.symmetricKeyBuilder(AesKeyGenSpec.class)
|
||||
// Generate a secret key according to the AES256 specification
|
||||
.generateSecret(AesKeyGenSpec.aes256());
|
||||
// Log the generated key (truncated to short hex for readability)
|
||||
LOG.log(Level.INFO, "AES256 key generated: {0}", Strings.toShortHexString(key.getEncoded()));
|
||||
|
||||
// or just: CryptoAlgorithms.generateSecret("AES", AesKeyGenSpec.aes256())
|
||||
SecretKey key = zeroEchoSession.keyBuilders().symmetric()
|
||||
.generate("AES", AesKeyGenSpec.aes256());
|
||||
LOG.log(Level.INFO, "AES256 key generated");
|
||||
|
||||
return key;
|
||||
}
|
||||
@@ -101,7 +93,7 @@ class AesTest {
|
||||
SecretKey key = generateAesKey();
|
||||
byte[] encrypted;
|
||||
// Request an encryption context using the key and AES-GCM-128 specification
|
||||
try (EncryptionContext enc = CryptoAlgorithms.create("AES", KeyUsage.ENCRYPT, key, spec)) {
|
||||
try (EncryptionContext enc = zeroEchoSession.createContext("AES", KeyUsage.ENCRYPT, key, spec)) {
|
||||
// This context implements ContextAware, allowing us to associate our session
|
||||
((ContextAware) enc).setContext(session);
|
||||
// Get an encrypted stream that processes the plaintext on-the-fly
|
||||
@@ -132,7 +124,7 @@ class AesTest {
|
||||
|
||||
// Separate context again to hold IV and AAD values so we can inspect them later
|
||||
CtxInterface session = Ctx.INSTANCE.getContext("aes-ctx-" + System.nanoTime());
|
||||
AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder()
|
||||
AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
|
||||
// Use the generated AES key
|
||||
.importKeyRaw(key.getEncoded())
|
||||
// Specify AES-GCM-128 mode
|
||||
@@ -171,7 +163,7 @@ class AesTest {
|
||||
// Sample message to encrypt
|
||||
byte[] msg = randomBytes(100);
|
||||
|
||||
AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder()
|
||||
AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
|
||||
// Automatically generate a 256-bit AES key
|
||||
.generateKey(256)
|
||||
// Store ad-hoc generated parameters (IV, AAD) in the stream header
|
||||
@@ -210,7 +202,7 @@ class AesTest {
|
||||
// Sample message to encrypt
|
||||
byte[] msg = randomBytes(100);
|
||||
|
||||
AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder().generateKey(256).modeGcm(128).withHeader();
|
||||
AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).generateKey(256).modeGcm(128).withHeader();
|
||||
|
||||
// The builder stores generated IV and AAD inside the stream header
|
||||
DataContent dccb = DataContentChainBuilder.encrypt().add(PlainBytesBuilder.builder().bytes(msg)).add(aesBuilder)
|
||||
@@ -227,7 +219,7 @@ class AesTest {
|
||||
|
||||
dccb = DataContentChainBuilder.decrypt().add(PlainBytesBuilder.builder().bytes(encrypted))
|
||||
// Use the same AES key for decryption; IV and AAD are restored from the header
|
||||
.add(AesDataContentBuilder.builder().importKeyRaw(key.getEncoded()).modeGcm(128).withHeader())
|
||||
.add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).importKeyRaw(key.getEncoded()).modeGcm(128).withHeader())
|
||||
// Build the pipeline
|
||||
.build();
|
||||
byte[] decrypted;
|
||||
|
||||
@@ -52,6 +52,7 @@ import zeroecho.core.alg.xdh.XdhSpec;
|
||||
import zeroecho.core.context.AgreementContext;
|
||||
import zeroecho.core.context.MessageAgreementContext;
|
||||
import zeroecho.core.spec.VoidSpec;
|
||||
import zeroecho.sdk.ZeroEchoSession;
|
||||
import zeroecho.core.util.Strings;
|
||||
import zeroecho.sdk.util.BouncyCastleActivator;
|
||||
|
||||
@@ -107,6 +108,7 @@ import zeroecho.sdk.util.BouncyCastleActivator;
|
||||
* </p>
|
||||
*/
|
||||
class AgreementVariantsTest {
|
||||
private final ZeroEchoSession session = new ZeroEchoSession();
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(AgreementVariantsTest.class.getName());
|
||||
|
||||
@@ -138,16 +140,17 @@ class AgreementVariantsTest {
|
||||
void kemAdapter_mlKem_roundTrip() throws Exception {
|
||||
LOG.info("kemAdapter_mlKem_roundTrip - KEM_ADAPTER (ML-KEM)");
|
||||
|
||||
KeyPair recipient = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber1024());
|
||||
KeyPair recipient = session.keyBuilders().asymmetric()
|
||||
.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber1024());
|
||||
|
||||
MessageAgreementContext initiator = null;
|
||||
MessageAgreementContext responder = null;
|
||||
try {
|
||||
// Initiator: constructed with recipient's public key (encapsulation side).
|
||||
initiator = CryptoAlgorithms.create("ML-KEM", KeyUsage.AGREEMENT, recipient.getPublic(), VoidSpec.INSTANCE);
|
||||
initiator = session.createContext("ML-KEM", KeyUsage.AGREEMENT, recipient.getPublic(), VoidSpec.INSTANCE);
|
||||
|
||||
// Responder: constructed with recipient's private key (decapsulation side).
|
||||
responder = CryptoAlgorithms.create("ML-KEM", KeyUsage.AGREEMENT, recipient.getPrivate(),
|
||||
responder = session.createContext("ML-KEM", KeyUsage.AGREEMENT, recipient.getPrivate(),
|
||||
VoidSpec.INSTANCE);
|
||||
|
||||
// One-shot outbound message: KEM ciphertext / encapsulation payload.
|
||||
@@ -193,17 +196,16 @@ class AgreementVariantsTest {
|
||||
void classicAgreement_x25519_roundTrip() throws Exception {
|
||||
LOG.info("classicAgreement_x25519_roundTrip - CLASSIC_AGREEMENT (X25519)");
|
||||
|
||||
CryptoAlgorithm xdh = CryptoAlgorithms.require("Xdh");
|
||||
KeyPair alice = xdh.generateKeyPair();
|
||||
KeyPair bob = xdh.generateKeyPair();
|
||||
KeyPair alice = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
KeyPair bob = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
|
||||
AgreementContext aCtx = null;
|
||||
AgreementContext bCtx = null;
|
||||
|
||||
try {
|
||||
// Both contexts are built from local private keys.
|
||||
aCtx = CryptoAlgorithms.create("Xdh", KeyUsage.AGREEMENT, alice.getPrivate(), XdhSpec.X25519);
|
||||
bCtx = CryptoAlgorithms.create("Xdh", KeyUsage.AGREEMENT, bob.getPrivate(), XdhSpec.X25519);
|
||||
aCtx = session.createContext("Xdh", KeyUsage.AGREEMENT, alice.getPrivate(), XdhSpec.X25519);
|
||||
bCtx = session.createContext("Xdh", KeyUsage.AGREEMENT, bob.getPrivate(), XdhSpec.X25519);
|
||||
|
||||
// The protocol layer provides peer public keys (here we use in-memory
|
||||
// exchange).
|
||||
@@ -254,9 +256,8 @@ class AgreementVariantsTest {
|
||||
void pairMessage_x25519_roundTrip() throws Exception {
|
||||
LOG.info("pairMessage_x25519_roundTrip - PAIR_MESSAGE (X25519)");
|
||||
|
||||
CryptoAlgorithm xdh = CryptoAlgorithms.require("Xdh");
|
||||
KeyPair alice = xdh.generateKeyPair();
|
||||
KeyPair bob = xdh.generateKeyPair();
|
||||
KeyPair alice = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
KeyPair bob = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
|
||||
// Wrapper is required because ZeroEcho capability dispatch uses Key (KeyPair is
|
||||
// not a Key).
|
||||
@@ -267,8 +268,8 @@ class AgreementVariantsTest {
|
||||
MessageAgreementContext bCtx = null;
|
||||
|
||||
try {
|
||||
aCtx = CryptoAlgorithms.create("Xdh", KeyUsage.AGREEMENT, aliceKey, XdhSpec.X25519);
|
||||
bCtx = CryptoAlgorithms.create("Xdh", KeyUsage.AGREEMENT, bobKey, XdhSpec.X25519);
|
||||
aCtx = session.createContext("Xdh", KeyUsage.AGREEMENT, aliceKey, XdhSpec.X25519);
|
||||
bCtx = session.createContext("Xdh", KeyUsage.AGREEMENT, bobKey, XdhSpec.X25519);
|
||||
|
||||
// Outbound messages: SPKI encodings of local public keys.
|
||||
byte[] aMsg = aCtx.getPeerMessage();
|
||||
|
||||
@@ -46,7 +46,6 @@ import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInstance;
|
||||
|
||||
import zeroecho.core.CryptoAlgorithms;
|
||||
import zeroecho.core.KeyUsage;
|
||||
import zeroecho.core.alg.elgamal.ElgamalParamSpec;
|
||||
import zeroecho.core.alg.kyber.KyberKeyGenSpec;
|
||||
@@ -58,11 +57,15 @@ import zeroecho.sdk.builders.core.PlainBytesBuilder;
|
||||
import zeroecho.sdk.content.api.DataContent;
|
||||
import zeroecho.sdk.guard.MultiRecipientDataSourceBuilder;
|
||||
import zeroecho.sdk.guard.UnlockMaterial;
|
||||
import zeroecho.sdk.Pbkdf2Limits;
|
||||
import zeroecho.sdk.ZeroEchoSession;
|
||||
import zeroecho.sdk.util.BouncyCastleActivator;
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class CombinedDeliveryTest {
|
||||
private static final Logger LOG = Logger.getLogger(CombinedDeliveryTest.class.getName());
|
||||
private final ZeroEchoSession session = new ZeroEchoSession()
|
||||
.withPbkdf2Limits(new Pbkdf2Limits(1_000_000, 1_000_000));
|
||||
|
||||
@BeforeAll
|
||||
void setupProviders() {
|
||||
@@ -70,31 +73,25 @@ class CombinedDeliveryTest {
|
||||
}
|
||||
|
||||
KeyPair generateKyberKeys() throws GeneralSecurityException {
|
||||
KeyPair kp = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber1024());
|
||||
|
||||
LOG.log(Level.INFO, "ML-KEM key public={0} private={1}",
|
||||
new Object[] { Strings.toShortHexString(kp.getPublic().getEncoded()),
|
||||
Strings.toShortHexString(kp.getPrivate().getEncoded()) });
|
||||
KeyPair kp = session.keyBuilders().asymmetric()
|
||||
.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber1024());
|
||||
LOG.log(Level.INFO, "ML-KEM key pair generated");
|
||||
|
||||
return kp;
|
||||
}
|
||||
|
||||
KeyPair generateRsaKeys() throws GeneralSecurityException {
|
||||
KeyPair kp = CryptoAlgorithms.generateKeyPair("RSA", RsaKeyGenSpec.rsa4096());
|
||||
|
||||
LOG.log(Level.INFO, "RSA key public={0} private={1}",
|
||||
new Object[] { Strings.toShortHexString(kp.getPublic().getEncoded()),
|
||||
Strings.toShortHexString(kp.getPrivate().getEncoded()) });
|
||||
KeyPair kp = session.keyBuilders().asymmetric()
|
||||
.generateKeyPair("RSA", RsaKeyGenSpec.rsa4096());
|
||||
LOG.log(Level.INFO, "RSA key pair generated");
|
||||
|
||||
return kp;
|
||||
}
|
||||
|
||||
KeyPair generateElGamalKeys() throws GeneralSecurityException {
|
||||
KeyPair kp = CryptoAlgorithms.generateKeyPair("ElGamal", ElgamalParamSpec.ffdhe2048());
|
||||
|
||||
LOG.log(Level.INFO, "ElGamal key public={0} private={1}",
|
||||
new Object[] { Strings.toShortHexString(kp.getPublic().getEncoded()),
|
||||
Strings.toShortHexString(kp.getPrivate().getEncoded()) });
|
||||
KeyPair kp = session.keyBuilders().asymmetric()
|
||||
.generateKeyPair("ElGamal", ElgamalParamSpec.ffdhe2048());
|
||||
LOG.log(Level.INFO, "ElGamal key pair generated");
|
||||
|
||||
return kp;
|
||||
}
|
||||
@@ -112,16 +109,16 @@ class CombinedDeliveryTest {
|
||||
KeyPair decoy1rsa = generateRsaKeys();
|
||||
KeyPair decoy2rsa = generateRsaKeys();
|
||||
|
||||
AesDataContentBuilder payload = AesDataContentBuilder.builder().modeCbcPkcs5().withHeader();
|
||||
AesDataContentBuilder payload = AesDataContentBuilder.builder(session).modeCbcPkcs5().withHeader();
|
||||
|
||||
MultiRecipientDataSourceBuilder multi = MultiRecipientDataSourceBuilder.builder()
|
||||
MultiRecipientDataSourceBuilder multi = MultiRecipientDataSourceBuilder.builder(session)
|
||||
// AES-256 - 32 bytes of key material
|
||||
.payloadKeyBytes(32).withAes(payload)
|
||||
// add recipients - the context initialized with the public key
|
||||
.addRecipient(CryptoAlgorithms.create("ElGamal", KeyUsage.ENCRYPT, elg.getPublic()))
|
||||
.addRecipient(CryptoAlgorithms.create("RSA", KeyUsage.ENCRYPT, rsa.getPublic()))
|
||||
.addRecipient(session.createContext("ElGamal", KeyUsage.ENCRYPT, elg.getPublic()))
|
||||
.addRecipient(session.createContext("RSA", KeyUsage.ENCRYPT, rsa.getPublic()))
|
||||
// ML-KEM PostQuantum uses AES for the inner payload
|
||||
.addRecipient(CryptoAlgorithms.create("ML-KEM", KeyUsage.ENCAPSULATE, kem.getPublic()),
|
||||
.addRecipient(session.createContext("ML-KEM", KeyUsage.ENCAPSULATE, kem.getPublic()),
|
||||
/* AES256 key size */
|
||||
32,
|
||||
/* salt size in hkdf */
|
||||
@@ -129,8 +126,8 @@ class CombinedDeliveryTest {
|
||||
// Password (via KDF)
|
||||
.addPasswordRecipient(password, /* iterations */ 200_000, /* saltLen */ 16, /* kekBytes */ 32)
|
||||
// and some decoys
|
||||
.addRecipientDecoy(CryptoAlgorithms.create("RSA", KeyUsage.ENCRYPT, decoy1rsa.getPublic()))
|
||||
.addRecipientDecoy(CryptoAlgorithms.create("RSA", KeyUsage.ENCRYPT, decoy2rsa.getPublic()));
|
||||
.addRecipientDecoy(session.createContext("RSA", KeyUsage.ENCRYPT, decoy1rsa.getPublic()))
|
||||
.addRecipientDecoy(session.createContext("RSA", KeyUsage.ENCRYPT, decoy2rsa.getPublic()));
|
||||
|
||||
// shuffle all the recipients
|
||||
multi.shuffle();
|
||||
@@ -153,8 +150,8 @@ class CombinedDeliveryTest {
|
||||
|
||||
private void recipientProcessing(String method, byte[] msg, byte[] encrypted, UnlockMaterial unlock)
|
||||
throws IOException {
|
||||
AesDataContentBuilder payload = AesDataContentBuilder.builder().modeCbcPkcs5().withHeader();
|
||||
MultiRecipientDataSourceBuilder multi = MultiRecipientDataSourceBuilder.builder()
|
||||
AesDataContentBuilder payload = AesDataContentBuilder.builder(session).modeCbcPkcs5().withHeader();
|
||||
MultiRecipientDataSourceBuilder multi = MultiRecipientDataSourceBuilder.builder(session)
|
||||
// define our payload
|
||||
.payloadKeyBytes(32).withAes(payload)
|
||||
// one recipient
|
||||
|
||||
@@ -44,7 +44,6 @@ import java.util.logging.Logger;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import zeroecho.core.CryptoAlgorithms;
|
||||
import zeroecho.core.alg.kyber.KyberKeyGenSpec;
|
||||
import zeroecho.core.alg.xdh.XdhSpec;
|
||||
import zeroecho.sdk.builders.HybridKexBuilder;
|
||||
@@ -56,6 +55,7 @@ import zeroecho.sdk.hybrid.derived.HybridDerived;
|
||||
import zeroecho.sdk.hybrid.kex.HybridKexContext;
|
||||
import zeroecho.sdk.hybrid.kex.HybridKexExporter;
|
||||
import zeroecho.sdk.hybrid.kex.HybridKexProfile;
|
||||
import zeroecho.sdk.ZeroEchoSession;
|
||||
import zeroecho.sdk.hybrid.kex.HybridKexTranscript;
|
||||
import zeroecho.sdk.util.BouncyCastleActivator;
|
||||
|
||||
@@ -78,6 +78,7 @@ import zeroecho.sdk.util.BouncyCastleActivator;
|
||||
* </p>
|
||||
*/
|
||||
class HybridDerivedAesDemoTest {
|
||||
private final ZeroEchoSession session = new ZeroEchoSession();
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(HybridDerivedAesDemoTest.class.getName());
|
||||
|
||||
@@ -108,16 +109,17 @@ class HybridDerivedAesDemoTest {
|
||||
"hybrid-derived-aes-gcm-condensed");
|
||||
|
||||
// ...Generate classic key pairs for X25519 (Xdh + XdhSpec.X25519).
|
||||
KeyPair aliceClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
KeyPair bobClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
KeyPair aliceClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
KeyPair bobClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
|
||||
// ...Generate PQC key pair for ML-KEM-768 (recipient; used by Bob side to
|
||||
// decapsulate).
|
||||
KeyPair bobPqc = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
|
||||
KeyPair bobPqc = session.keyBuilders().asymmetric()
|
||||
.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
|
||||
|
||||
// ...Build Alice initiator: classic agreement (out-of-band peer pub) + PQC
|
||||
// encapsulation.
|
||||
HybridKexContext alice = HybridKexBuilder.builder()
|
||||
HybridKexContext alice = HybridKexBuilder.builder(session)
|
||||
// ...Set mandatory profile.
|
||||
.profile(profile)
|
||||
// ...Bind builder HKDF info to transcript.
|
||||
@@ -142,7 +144,7 @@ class HybridDerivedAesDemoTest {
|
||||
.buildInitiator();
|
||||
|
||||
// ...Build Bob responder: classic agreement + PQC decapsulation.
|
||||
HybridKexContext bob = HybridKexBuilder.builder()
|
||||
HybridKexContext bob = HybridKexBuilder.builder(session)
|
||||
// ...Set mandatory profile.
|
||||
.profile(profile)
|
||||
// ...Bind builder HKDF info to transcript.
|
||||
@@ -203,7 +205,7 @@ class HybridDerivedAesDemoTest {
|
||||
// ...Inject explicit AAD.
|
||||
.aad(aad)
|
||||
// ...Apply derived key(256b) and IV(12B) to AES-GCM with header.
|
||||
.applyToAesGcm(AesDataContentBuilder.builder()
|
||||
.applyToAesGcm(AesDataContentBuilder.builder(session)
|
||||
// ...Store IV in header for decrypt side.
|
||||
.withHeader()
|
||||
// ...Use AES-GCM with 128-bit authentication tag.
|
||||
@@ -230,7 +232,7 @@ class HybridDerivedAesDemoTest {
|
||||
// ...Same explicit AAD as encryption.
|
||||
.aad(aad)
|
||||
// ...Apply derived key and IV to AES-GCM with header.
|
||||
.applyToAesGcm(AesDataContentBuilder.builder()
|
||||
.applyToAesGcm(AesDataContentBuilder.builder(session)
|
||||
// ...Parse IV from header.
|
||||
.withHeader()
|
||||
// ...Use AES-GCM with 128-bit authentication tag.
|
||||
@@ -271,14 +273,15 @@ class HybridDerivedAesDemoTest {
|
||||
"hybrid-derived-aes-gcm-expanded");
|
||||
|
||||
// ...Generate classic key pairs for X25519.
|
||||
KeyPair aliceClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
KeyPair bobClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
KeyPair aliceClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
KeyPair bobClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
|
||||
// ...Generate PQC key pair for ML-KEM-768.
|
||||
KeyPair bobPqc = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
|
||||
KeyPair bobPqc = session.keyBuilders().asymmetric()
|
||||
.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
|
||||
|
||||
// ...Build Alice initiator in a step-by-step manner.
|
||||
HybridKexBuilder aliceBuilder = HybridKexBuilder.builder();
|
||||
HybridKexBuilder aliceBuilder = HybridKexBuilder.builder(session);
|
||||
// ...Set mandatory profile.
|
||||
aliceBuilder.profile(profile);
|
||||
// ...Bind builder HKDF info to transcript.
|
||||
@@ -306,7 +309,7 @@ class HybridDerivedAesDemoTest {
|
||||
HybridKexContext alice = alicePqcCfg.buildInitiator();
|
||||
|
||||
// ...Build Bob responder in a step-by-step manner.
|
||||
HybridKexBuilder bobBuilder = HybridKexBuilder.builder();
|
||||
HybridKexBuilder bobBuilder = HybridKexBuilder.builder(session);
|
||||
// ...Set mandatory profile.
|
||||
bobBuilder.profile(profile);
|
||||
// ...Bind builder HKDF info to transcript.
|
||||
@@ -356,7 +359,7 @@ class HybridDerivedAesDemoTest {
|
||||
byte[] aad = "aad:demo:expanded".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
// ...Prepare AES builder for encryption.
|
||||
AesDataContentBuilder aesEnc = AesDataContentBuilder.builder();
|
||||
AesDataContentBuilder aesEnc = AesDataContentBuilder.builder(session);
|
||||
// ...Store IV in header.
|
||||
aesEnc.withHeader();
|
||||
// ...Use AES-GCM with 128-bit authentication tag.
|
||||
@@ -389,7 +392,7 @@ class HybridDerivedAesDemoTest {
|
||||
System.out.println("...ciphertext " + lens(ciphertext) + " " + shortHex(ciphertext, 48));
|
||||
|
||||
// ...Prepare AES builder for decryption.
|
||||
AesDataContentBuilder aesDec = AesDataContentBuilder.builder();
|
||||
AesDataContentBuilder aesDec = AesDataContentBuilder.builder(session);
|
||||
// ...Parse IV from header.
|
||||
aesDec.withHeader();
|
||||
// ...Use AES-GCM with 128-bit authentication tag.
|
||||
@@ -453,13 +456,14 @@ class HybridDerivedAesDemoTest {
|
||||
byte[] aad = "aad:local-self".getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
// ...Generate classic identity keys (X25519).
|
||||
KeyPair selfClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
KeyPair selfClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
|
||||
// ...Generate PQC identity keys (ML-KEM-768).
|
||||
KeyPair selfPqc = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
|
||||
KeyPair selfPqc = session.keyBuilders().asymmetric()
|
||||
.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
|
||||
|
||||
// ...Build local initiator (encapsulation) against our own public keys.
|
||||
HybridKexContext encKex = HybridKexBuilder.builder()
|
||||
HybridKexContext encKex = HybridKexBuilder.builder(session)
|
||||
// ...Set mandatory profile.
|
||||
.profile(profile)
|
||||
// ...Bind derivation to transcript.
|
||||
@@ -509,7 +513,7 @@ class HybridDerivedAesDemoTest {
|
||||
// ...Inject explicit AAD.
|
||||
.aad(aad)
|
||||
// ...Apply derived key(256b) and IV(12B) to AES-GCM with header.
|
||||
.applyToAesGcm(AesDataContentBuilder.builder()
|
||||
.applyToAesGcm(AesDataContentBuilder.builder(session)
|
||||
// ...Store IV in header for decrypt side.
|
||||
.withHeader()
|
||||
// ...Use AES-GCM with 128-bit authentication tag.
|
||||
@@ -525,7 +529,7 @@ class HybridDerivedAesDemoTest {
|
||||
|
||||
// ...Build local responder (decapsulation) using our own private keys and
|
||||
// stored peer message.
|
||||
HybridKexContext decKex = HybridKexBuilder.builder()
|
||||
HybridKexContext decKex = HybridKexBuilder.builder(session)
|
||||
// ...Set mandatory profile.
|
||||
.profile(profile)
|
||||
// ...Bind derivation to transcript.
|
||||
@@ -577,7 +581,7 @@ class HybridDerivedAesDemoTest {
|
||||
// ...Same explicit AAD.
|
||||
.aad(aad)
|
||||
// ...Apply derived key and IV to AES-GCM with header.
|
||||
.applyToAesGcm(AesDataContentBuilder.builder()
|
||||
.applyToAesGcm(AesDataContentBuilder.builder(session)
|
||||
// ...Parse IV from header.
|
||||
.withHeader()
|
||||
// ...Use AES-GCM with 128-bit authentication tag.
|
||||
|
||||
@@ -42,13 +42,13 @@ import java.util.logging.Logger;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import zeroecho.core.CryptoAlgorithms;
|
||||
import zeroecho.core.alg.common.agreement.KeyPairKey;
|
||||
import zeroecho.core.alg.kyber.KyberKeyGenSpec;
|
||||
import zeroecho.core.alg.xdh.XdhSpec;
|
||||
import zeroecho.sdk.hybrid.kex.HybridKexContext;
|
||||
import zeroecho.sdk.hybrid.kex.HybridKexContexts;
|
||||
import zeroecho.sdk.hybrid.kex.HybridKexProfile;
|
||||
import zeroecho.sdk.ZeroEchoSession;
|
||||
import zeroecho.sdk.util.BouncyCastleActivator;
|
||||
|
||||
/**
|
||||
@@ -103,6 +103,7 @@ import zeroecho.sdk.util.BouncyCastleActivator;
|
||||
* </p>
|
||||
*/
|
||||
class HybridKexDemoTest {
|
||||
private final ZeroEchoSession session = new ZeroEchoSession();
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(HybridKexDemoTest.class.getName());
|
||||
|
||||
@@ -121,11 +122,12 @@ class HybridKexDemoTest {
|
||||
logBegin("CLASSIC_AGREEMENT + KEM_ADAPTER", "Xdh/X25519 + ML-KEM-768", "HKDF-SHA256", "OKM=32B");
|
||||
|
||||
// Classic leg keys (Xdh + XdhSpec.X25519).
|
||||
KeyPair aliceClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
KeyPair bobClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
KeyPair aliceClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
KeyPair bobClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
|
||||
// PQC leg: Bob is the KEM recipient (has ML-KEM keypair).
|
||||
KeyPair bobPqc = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
|
||||
KeyPair bobPqc = session.keyBuilders().asymmetric()
|
||||
.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
|
||||
|
||||
// Hybrid profile: default HKDF label, 32-byte output suitable for symmetric
|
||||
// keys.
|
||||
@@ -138,13 +140,13 @@ class HybridKexDemoTest {
|
||||
// Alice (initiator): classic uses Alice private + Bob classic public
|
||||
// (out-of-band).
|
||||
// ...PQC uses Bob PQC public and will produce a KEM ciphertext.
|
||||
alice = HybridKexContexts.initiator(profile, "Xdh", aliceClassic.getPrivate(), bobClassic.getPublic(),
|
||||
alice = HybridKexContexts.initiator(session, profile, "Xdh", aliceClassic.getPrivate(), bobClassic.getPublic(),
|
||||
XdhSpec.X25519, "ML-KEM", bobPqc.getPublic(), null);
|
||||
|
||||
// Bob (responder): classic uses Bob private + Alice classic public
|
||||
// (out-of-band).
|
||||
// ...PQC uses Bob PQC private and will consume Alice's ciphertext.
|
||||
bob = HybridKexContexts.responder(profile, "Xdh", bobClassic.getPrivate(), aliceClassic.getPublic(),
|
||||
bob = HybridKexContexts.responder(session, profile, "Xdh", bobClassic.getPrivate(), aliceClassic.getPublic(),
|
||||
XdhSpec.X25519, "ML-KEM", bobPqc.getPrivate(), null);
|
||||
|
||||
// Alice -> Bob: hybrid message carries PQC ciphertext; classic part is empty.
|
||||
@@ -175,11 +177,12 @@ class HybridKexDemoTest {
|
||||
logBegin("PAIR_MESSAGE + KEM_ADAPTER", "Xdh/X25519 + ML-KEM-768", "HKDF-SHA256", "OKM=32B");
|
||||
|
||||
// Classic leg keys (Xdh + XdhSpec.X25519).
|
||||
KeyPair aliceClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
KeyPair bobClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
KeyPair aliceClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
KeyPair bobClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
|
||||
// PQC leg: Bob is the KEM recipient (has ML-KEM keypair).
|
||||
KeyPair bobPqc = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
|
||||
KeyPair bobPqc = session.keyBuilders().asymmetric()
|
||||
.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
|
||||
|
||||
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
|
||||
|
||||
@@ -190,14 +193,14 @@ class HybridKexDemoTest {
|
||||
// Alice classic leg is message-based (PAIR_MESSAGE): it will emit her public
|
||||
// key as SPKI bytes.
|
||||
// ...PQC leg (KEM initiator) will emit ciphertext.
|
||||
alice = HybridKexContexts.initiatorPairMessage(profile, "Xdh", new KeyPairKey(aliceClassic), XdhSpec.X25519,
|
||||
alice = HybridKexContexts.initiatorPairMessage(session, profile, "Xdh", new KeyPairKey(aliceClassic), XdhSpec.X25519,
|
||||
"ML-KEM", bobPqc.getPublic(), null);
|
||||
|
||||
// Bob classic leg is message-based (PAIR_MESSAGE): it will emit his public key
|
||||
// as SPKI bytes.
|
||||
// ...PQC leg (KEM responder) consumes ciphertext and typically does not emit a
|
||||
// PQC message.
|
||||
bob = HybridKexContexts.responderPairMessage(profile, "Xdh", new KeyPairKey(bobClassic), XdhSpec.X25519,
|
||||
bob = HybridKexContexts.responderPairMessage(session, profile, "Xdh", new KeyPairKey(bobClassic), XdhSpec.X25519,
|
||||
"ML-KEM", bobPqc.getPrivate(), null);
|
||||
|
||||
// Alice -> Bob: hybrid message carries classic SPKI + PQC ciphertext.
|
||||
@@ -231,13 +234,13 @@ class HybridKexDemoTest {
|
||||
logBegin("Builder", "CLASSIC_AGREEMENT + KEM_ADAPTER", "Xdh/X25519 + ML-KEM-768", "HKDF-SHA256", "OKM=32B");
|
||||
|
||||
// ...Generate classic leg keys (Xdh + XdhSpec.X25519).
|
||||
java.security.KeyPair aliceClassic = zeroecho.core.CryptoAlgorithms.generateKeyPair("Xdh",
|
||||
java.security.KeyPair aliceClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh",
|
||||
zeroecho.core.alg.xdh.XdhSpec.X25519);
|
||||
java.security.KeyPair bobClassic = zeroecho.core.CryptoAlgorithms.generateKeyPair("Xdh",
|
||||
java.security.KeyPair bobClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh",
|
||||
zeroecho.core.alg.xdh.XdhSpec.X25519);
|
||||
|
||||
// ...Generate PQC (recipient) keys (ML-KEM-768).
|
||||
java.security.KeyPair bobPqc = zeroecho.core.CryptoAlgorithms.generateKeyPair("ML-KEM",
|
||||
java.security.KeyPair bobPqc = session.keyBuilders().asymmetric().generateKeyPair("ML-KEM",
|
||||
zeroecho.core.alg.kyber.KyberKeyGenSpec.kyber768());
|
||||
|
||||
// ...Create a profile for HKDF (output length 32 bytes).
|
||||
@@ -252,7 +255,7 @@ class HybridKexDemoTest {
|
||||
zeroecho.sdk.hybrid.kex.HybridKexPolicy policy = new zeroecho.sdk.hybrid.kex.HybridKexPolicy(128, 192, 32);
|
||||
|
||||
// ...Start the builder.
|
||||
zeroecho.sdk.builders.HybridKexBuilder b = zeroecho.sdk.builders.HybridKexBuilder.builder()
|
||||
zeroecho.sdk.builders.HybridKexBuilder b = zeroecho.sdk.builders.HybridKexBuilder.builder(session)
|
||||
// ...Set HKDF profile (salt/info/outLen).
|
||||
.profile(profile)
|
||||
// ...Bind HKDF info to transcript (protocol context).
|
||||
@@ -283,7 +286,7 @@ class HybridKexDemoTest {
|
||||
|
||||
// ...Build responder-side context (Bob) with symmetric configuration (note: PQC
|
||||
// uses private key).
|
||||
zeroecho.sdk.hybrid.kex.HybridKexContext bob = zeroecho.sdk.builders.HybridKexBuilder.builder()
|
||||
zeroecho.sdk.hybrid.kex.HybridKexContext bob = zeroecho.sdk.builders.HybridKexBuilder.builder(session)
|
||||
// ...Set the same HKDF profile to derive the same OKM.
|
||||
.profile(profile)
|
||||
// ...Bind the same transcript to ensure both sides derive the same OKM.
|
||||
@@ -346,11 +349,12 @@ class HybridKexDemoTest {
|
||||
logBegin("Builder", "PAIR_MESSAGE + KEM_ADAPTER", "Xdh/X25519 + ML-KEM-768", "HKDF-SHA256", "OKM=32B");
|
||||
|
||||
// ...Generate classic leg keys (Xdh + XdhSpec.X25519).
|
||||
KeyPair aliceClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
KeyPair bobClassic = CryptoAlgorithms.generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
KeyPair aliceClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
KeyPair bobClassic = session.keyBuilders().asymmetric().generateKeyPair("Xdh", XdhSpec.X25519);
|
||||
|
||||
// ...Generate PQC (recipient) keys (ML-KEM-768).
|
||||
KeyPair bobPqc = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
|
||||
KeyPair bobPqc = session.keyBuilders().asymmetric()
|
||||
.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber768());
|
||||
|
||||
// ...Create a profile for HKDF (output length 32 bytes).
|
||||
HybridKexProfile profile = HybridKexProfile.defaultProfile(32);
|
||||
@@ -365,7 +369,7 @@ class HybridKexDemoTest {
|
||||
zeroecho.sdk.hybrid.kex.HybridKexPolicy policy = new zeroecho.sdk.hybrid.kex.HybridKexPolicy(128, 192, 32);
|
||||
|
||||
// ...Start the initiator builder.
|
||||
zeroecho.sdk.builders.HybridKexBuilder initBuilder = zeroecho.sdk.builders.HybridKexBuilder.builder()
|
||||
zeroecho.sdk.builders.HybridKexBuilder initBuilder = zeroecho.sdk.builders.HybridKexBuilder.builder(session)
|
||||
// ...Set HKDF profile (salt/info/outLen).
|
||||
.profile(profile)
|
||||
// ...Bind HKDF info to transcript (protocol context).
|
||||
@@ -395,7 +399,7 @@ class HybridKexDemoTest {
|
||||
.buildInitiator();
|
||||
|
||||
// ...Start the responder builder.
|
||||
zeroecho.sdk.builders.HybridKexBuilder respBuilder = zeroecho.sdk.builders.HybridKexBuilder.builder()
|
||||
zeroecho.sdk.builders.HybridKexBuilder respBuilder = zeroecho.sdk.builders.HybridKexBuilder.builder(session)
|
||||
// ...Set the same HKDF profile to derive the same OKM.
|
||||
.profile(profile)
|
||||
// ...Bind the same transcript to ensure both sides derive the same OKM.
|
||||
|
||||
@@ -48,10 +48,11 @@ import javax.crypto.SecretKey;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import zeroecho.core.CryptoAlgorithm;
|
||||
import zeroecho.core.CryptoAlgorithms;
|
||||
import zeroecho.core.alg.ed25519.Ed25519KeyGenSpec;
|
||||
import zeroecho.core.alg.sphincsplus.SphincsPlusKeyGenSpec;
|
||||
import zeroecho.core.context.SignatureContext;
|
||||
import zeroecho.sdk.builders.TagTrailerDataContentBuilder;
|
||||
import zeroecho.sdk.ZeroEchoSession;
|
||||
import zeroecho.sdk.builders.alg.AesDataContentBuilder;
|
||||
import zeroecho.sdk.builders.core.DataContentChainBuilder;
|
||||
import zeroecho.sdk.builders.core.PlainBytesBuilder;
|
||||
@@ -99,7 +100,7 @@ class HybridSigningAesTest {
|
||||
byte[] msg = randomBytes(100);
|
||||
|
||||
// AES-GCM with header, runtime params are stored in header
|
||||
AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder().generateKey(256).modeGcm(128).withHeader();
|
||||
AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).generateKey(256).modeGcm(128).withHeader();
|
||||
|
||||
// Hybrid signature: Ed25519 + SPHINCS+ (AND)
|
||||
HybridSignatureProfile profile = new HybridSignatureProfile("Ed25519", "SPHINCS+", null, null,
|
||||
@@ -108,9 +109,9 @@ class HybridSigningAesTest {
|
||||
KeyPair ed = generateKeyPair("Ed25519");
|
||||
KeyPair spx = generateKeyPair("SPHINCS+");
|
||||
|
||||
SignatureContext tagEnc = HybridSignatureContexts.sign(profile, ed.getPrivate(), spx.getPrivate(),
|
||||
SignatureContext tagEnc = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), profile, ed.getPrivate(), spx.getPrivate(),
|
||||
2 * 1024 * 1024);
|
||||
SignatureContext tagDec = HybridSignatureContexts.verify(profile, ed.getPublic(), spx.getPublic(),
|
||||
SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, ed.getPublic(), spx.getPublic(),
|
||||
2 * 1024 * 1024);
|
||||
|
||||
// For verification, make mismatch behavior explicit (builder also supports
|
||||
@@ -141,7 +142,7 @@ class HybridSigningAesTest {
|
||||
// encrypted input
|
||||
.add(PlainBytesBuilder.builder().bytes(ciphertext))
|
||||
// AES-GCM decryption
|
||||
.add(AesDataContentBuilder.builder().importKeyRaw(aesKey.getEncoded()).modeGcm(128).withHeader())
|
||||
.add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).importKeyRaw(aesKey.getEncoded()).modeGcm(128).withHeader())
|
||||
// hybrid signature verification
|
||||
.add(new TagTrailerDataContentBuilder<Signature>(tagDec).bufferSize(8192).throwOnMismatch()).build();
|
||||
|
||||
@@ -161,7 +162,7 @@ class HybridSigningAesTest {
|
||||
byte[] msg = randomBytes(100);
|
||||
|
||||
// AES-GCM with header, runtime params are stored in header
|
||||
AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder().generateKey(256).modeGcm(128).withHeader();
|
||||
AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).generateKey(256).modeGcm(128).withHeader();
|
||||
|
||||
// Hybrid signature: Ed25519 + SPHINCS+ (AND)
|
||||
HybridSignatureProfile profile = new HybridSignatureProfile("Ed25519", "SPHINCS+", null, null,
|
||||
@@ -170,9 +171,9 @@ class HybridSigningAesTest {
|
||||
KeyPair ed = generateKeyPair("Ed25519");
|
||||
KeyPair spx = generateKeyPair("SPHINCS+");
|
||||
|
||||
SignatureContext tagEnc = HybridSignatureContexts.sign(profile, ed.getPrivate(), spx.getPrivate(),
|
||||
SignatureContext tagEnc = HybridSignatureContexts.sign(new zeroecho.sdk.ZeroEchoSession(), profile, ed.getPrivate(), spx.getPrivate(),
|
||||
2 * 1024 * 1024);
|
||||
SignatureContext tagDec = HybridSignatureContexts.verify(profile, ed.getPublic(), spx.getPublic(),
|
||||
SignatureContext tagDec = HybridSignatureContexts.verify(new zeroecho.sdk.ZeroEchoSession(), profile, ed.getPublic(), spx.getPublic(),
|
||||
2 * 1024 * 1024);
|
||||
|
||||
tagDec.setVerificationApproach(tagDec.getVerificationCore().getThrowOnMismatch());
|
||||
@@ -205,7 +206,7 @@ class HybridSigningAesTest {
|
||||
// hybrid signature verification
|
||||
.add(new TagTrailerDataContentBuilder<Signature>(tagDec).bufferSize(8192).throwOnMismatch())
|
||||
// AES-GCM decryption
|
||||
.add(AesDataContentBuilder.builder().importKeyRaw(aesKey.getEncoded()).modeGcm(128).withHeader())
|
||||
.add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).importKeyRaw(aesKey.getEncoded()).modeGcm(128).withHeader())
|
||||
.build();
|
||||
|
||||
byte[] decrypted;
|
||||
@@ -219,8 +220,14 @@ class HybridSigningAesTest {
|
||||
// helpers
|
||||
|
||||
private static KeyPair generateKeyPair(String algId) throws GeneralSecurityException {
|
||||
CryptoAlgorithm alg = CryptoAlgorithms.require(algId);
|
||||
return alg.generateKeyPair();
|
||||
ZeroEchoSession session = new ZeroEchoSession();
|
||||
return switch (algId) {
|
||||
case "Ed25519" -> session.keyBuilders().asymmetric().generateKeyPair(algId,
|
||||
Ed25519KeyGenSpec.defaultSpec());
|
||||
case "SPHINCS+" -> session.keyBuilders().asymmetric().generateKeyPair(algId,
|
||||
SphincsPlusKeyGenSpec.defaultSpec());
|
||||
default -> throw new IllegalArgumentException("Unsupported sample signature algorithm: " + algId);
|
||||
};
|
||||
}
|
||||
|
||||
private static byte[] randomBytes(int len) {
|
||||
|
||||
@@ -47,7 +47,6 @@ import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.TestInstance;
|
||||
|
||||
import zeroecho.core.CryptoAlgorithms;
|
||||
import zeroecho.core.alg.kyber.KyberKeyGenSpec;
|
||||
import zeroecho.core.util.Strings;
|
||||
import zeroecho.sdk.builders.alg.AesDataContentBuilder;
|
||||
@@ -56,9 +55,11 @@ import zeroecho.sdk.builders.core.DataContentChainBuilder;
|
||||
import zeroecho.sdk.builders.core.PlainBytesBuilder;
|
||||
import zeroecho.sdk.content.api.DataContent;
|
||||
import zeroecho.sdk.util.BouncyCastleActivator;
|
||||
import zeroecho.sdk.ZeroEchoSession;
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class PostQuantumTest {
|
||||
private final ZeroEchoSession session = new ZeroEchoSession();
|
||||
private static final Logger LOG = Logger.getLogger(PostQuantumTest.class.getName());
|
||||
|
||||
@BeforeAll
|
||||
@@ -67,7 +68,8 @@ class PostQuantumTest {
|
||||
}
|
||||
|
||||
KeyPair generateKyberKeys() throws GeneralSecurityException {
|
||||
KeyPair kp = CryptoAlgorithms.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber1024());
|
||||
KeyPair kp = session.keyBuilders().asymmetric()
|
||||
.generateKeyPair("ML-KEM", KyberKeyGenSpec.kyber1024());
|
||||
|
||||
LOG.log(Level.INFO, "ML-KEM key public={0} private={1}",
|
||||
new Object[] { Strings.toShortHexString(kp.getPublic().getEncoded()),
|
||||
@@ -87,7 +89,7 @@ class PostQuantumTest {
|
||||
// Start the chain with a plain byte array as input
|
||||
.add(PlainBytesBuilder.builder().bytes(msg))
|
||||
// Add a KEM (Key Encapsulation Mechanism) stage
|
||||
.add(KemDataContentBuilder.builder()
|
||||
.add(KemDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
|
||||
// Select a KEM-capable algorithm: ML-KEM (Kyber variant)
|
||||
.kem("ML-KEM")
|
||||
// Encrypt to this recipient’s public key
|
||||
@@ -95,7 +97,7 @@ class PostQuantumTest {
|
||||
// Derive a shared secret and expand/obfuscate it using HKDF-SHA256
|
||||
.hkdfSha256("KEM-demo".getBytes(StandardCharsets.US_ASCII))
|
||||
// Use the derived secret as a key for AES
|
||||
.withAes(AesDataContentBuilder.builder()
|
||||
.withAes(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
|
||||
// Configure AES in GCM mode with a 128-bit authentication tag
|
||||
.modeGcm(128)
|
||||
// Store auxiliary parameters (e.g., random IV) inside the stream header
|
||||
@@ -113,7 +115,7 @@ class PostQuantumTest {
|
||||
// Start with the encrypted byte array as input
|
||||
.add(PlainBytesBuilder.builder().bytes(encrypted))
|
||||
// Add the KEM stage again, matching the encryption parameters
|
||||
.add(KemDataContentBuilder.builder()
|
||||
.add(KemDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
|
||||
// Use the same KEM algorithm that was used for encryption
|
||||
.kem("ML-KEM")
|
||||
// Provide the recipient’s private key for decapsulation
|
||||
@@ -121,7 +123,7 @@ class PostQuantumTest {
|
||||
// Re-derive the shared secret with the same HKDF-SHA256 label
|
||||
.hkdfSha256("KEM-demo".getBytes(StandardCharsets.US_ASCII))
|
||||
// Use the secret to unlock AES payload decryption
|
||||
.withAes(AesDataContentBuilder.builder()
|
||||
.withAes(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession())
|
||||
// AES in GCM mode with a 128-bit tag, consistent with encryption
|
||||
.modeGcm(128)
|
||||
// Read auxiliary parameters (IV, etc.) back from the header
|
||||
|
||||
@@ -47,7 +47,6 @@ import javax.crypto.SecretKey;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import zeroecho.core.CryptoAlgorithms;
|
||||
import zeroecho.core.alg.rsa.RsaKeyGenSpec;
|
||||
import zeroecho.core.alg.rsa.RsaSigSpec;
|
||||
import zeroecho.core.tag.TagEngine;
|
||||
@@ -58,12 +57,15 @@ import zeroecho.sdk.builders.alg.AesDataContentBuilder;
|
||||
import zeroecho.sdk.builders.core.DataContentChainBuilder;
|
||||
import zeroecho.sdk.builders.core.PlainBytesBuilder;
|
||||
import zeroecho.sdk.content.api.DataContent;
|
||||
import zeroecho.sdk.ZeroEchoSession;
|
||||
|
||||
class SigningAesTest {
|
||||
private final ZeroEchoSession session = new ZeroEchoSession();
|
||||
private static final Logger LOG = Logger.getLogger(SigningAesTest.class.getName());
|
||||
|
||||
KeyPair generateRsaKeys() throws GeneralSecurityException {
|
||||
KeyPair kp = CryptoAlgorithms.generateKeyPair("RSA", RsaKeyGenSpec.rsa4096());
|
||||
KeyPair kp = session.keyBuilders().asymmetric()
|
||||
.generateKeyPair("RSA", RsaKeyGenSpec.rsa4096());
|
||||
|
||||
LOG.log(Level.INFO, "RSA key public={0} private={1}",
|
||||
new Object[] { Strings.toShortHexString(kp.getPublic().getEncoded()),
|
||||
@@ -81,7 +83,7 @@ class SigningAesTest {
|
||||
// Configure AES in GCM mode with a 128-bit authentication tag. A fresh 256-bit
|
||||
// AES key will be generated automatically, and runtime parameters (IV, AAD)
|
||||
// will be written into the header.
|
||||
AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder().generateKey(256).modeGcm(128).withHeader();
|
||||
AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).generateKey(256).modeGcm(128).withHeader();
|
||||
|
||||
// Generate RSA-4096 key pair (retrieved via algorithm registry for convenience)
|
||||
KeyPair rsa = generateRsaKeys();
|
||||
@@ -89,9 +91,9 @@ class SigningAesTest {
|
||||
// Configure PSS signature parameters: SHA-256 hash, salt length = 32 bytes
|
||||
RsaSigSpec pss = RsaSigSpec.pss(RsaSigSpec.Hash.SHA256, 32);
|
||||
// Create signing engine (RSA-PSS with private key)
|
||||
TagEngine<Signature> tagEnc = TagEngineBuilder.rsaSign(rsa.getPrivate(), pss).get();
|
||||
TagEngine<Signature> tagEnc = TagEngineBuilder.rsaSign(new zeroecho.sdk.ZeroEchoSession(), rsa.getPrivate(), pss).get();
|
||||
// Create verification engine (RSA-PSS with public key)
|
||||
TagEngine<Signature> tagDec = TagEngineBuilder.rsaVerify(rsa.getPublic(), pss).get();
|
||||
TagEngine<Signature> tagDec = TagEngineBuilder.rsaVerify(new zeroecho.sdk.ZeroEchoSession(), rsa.getPublic(), pss).get();
|
||||
|
||||
// Build the encryption pipeline
|
||||
DataContent dccb = DataContentChainBuilder.encrypt()
|
||||
@@ -119,7 +121,7 @@ class SigningAesTest {
|
||||
.add(PlainBytesBuilder.builder().bytes(encrypted))
|
||||
// AES-GCM decryption using the same key; IV and AAD are restored automatically
|
||||
// from the header
|
||||
.add(AesDataContentBuilder.builder().importKeyRaw(key.getEncoded()).modeGcm(128).withHeader())
|
||||
.add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).importKeyRaw(key.getEncoded()).modeGcm(128).withHeader())
|
||||
// Verify the RSA-PSS signature trailer at the end of the stream (configured to
|
||||
// throw on mismatch)
|
||||
.add(new TagTrailerDataContentBuilder<>(tagDec).bufferSize(8192).throwOnMismatch())
|
||||
@@ -142,15 +144,15 @@ class SigningAesTest {
|
||||
// Create a random sample message to be encrypted
|
||||
byte[] msg = randomBytes(100);
|
||||
|
||||
AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder().generateKey(256).modeGcm(128).withHeader();
|
||||
AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).generateKey(256).modeGcm(128).withHeader();
|
||||
|
||||
// Generate RSA-4096 key pair (retrieved via algorithm registry for convenience)
|
||||
KeyPair rsa = generateRsaKeys();
|
||||
|
||||
// Configure PSS signature parameters: SHA-256 hash, salt length = 32 bytes
|
||||
RsaSigSpec pss = RsaSigSpec.pss(RsaSigSpec.Hash.SHA256, 32);
|
||||
TagEngine<Signature> tagEnc = TagEngineBuilder.rsaSign(rsa.getPrivate(), pss).get();
|
||||
TagEngine<Signature> tagDec = TagEngineBuilder.rsaVerify(rsa.getPublic(), pss).get();
|
||||
TagEngine<Signature> tagEnc = TagEngineBuilder.rsaSign(new zeroecho.sdk.ZeroEchoSession(), rsa.getPrivate(), pss).get();
|
||||
TagEngine<Signature> tagDec = TagEngineBuilder.rsaVerify(new zeroecho.sdk.ZeroEchoSession(), rsa.getPublic(), pss).get();
|
||||
|
||||
// Build the encryption pipeline
|
||||
DataContent dccb = DataContentChainBuilder.encrypt()
|
||||
@@ -183,7 +185,7 @@ class SigningAesTest {
|
||||
.add(new TagTrailerDataContentBuilder<>(tagDec).bufferSize(8192).throwOnMismatch())
|
||||
// AES-GCM decryption using the same key; IV and AAD are restored automatically
|
||||
// from the header
|
||||
.add(AesDataContentBuilder.builder().importKeyRaw(key.getEncoded()).modeGcm(128).withHeader())
|
||||
.add(AesDataContentBuilder.builder(new zeroecho.sdk.ZeroEchoSession()).importKeyRaw(key.getEncoded()).modeGcm(128).withHeader())
|
||||
// Build the pipeline
|
||||
.build();
|
||||
byte[] decrypted;
|
||||
|
||||
Reference in New Issue
Block a user