Initial commit (history reset)

This commit is contained in:
2025-09-16 23:14:24 +02:00
commit 2cc988925a
396 changed files with 71058 additions and 0 deletions

13
samples/.classpath Normal file
View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="bin/test" path="src/test/java">
<attributes>
<attribute name="gradle_scope" value="test"/>
<attribute name="gradle_used_by_scope" value="test"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-21/"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/default"/>
</classpath>

23
samples/.project Normal file
View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>samples</name>
<comment>Project samples created by Buildship.</comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
</projectDescription>

31
samples/LICENSE Normal file
View File

@@ -0,0 +1,31 @@
Copyright (C) 2025, Leo Galambos
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software must
display the following acknowledgement:
This product includes software developed by the Egothor project.
4. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

45
samples/build.gradle Normal file
View File

@@ -0,0 +1,45 @@
plugins {
id 'java'
}
dependencies {
testImplementation(project(":lib"))
testImplementation 'org.egothor:conflux:[1.0,2.0)'
testImplementation("org.bouncycastle:bcpkix-jdk18on:1.81")
testImplementation(platform("org.junit:junit-bom:5.10.2"))
testImplementation 'org.junit.jupiter:junit-jupiter'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
repositories {
// for conflux lib
maven {
name = "GiteaMaven"
url = uri("https://gitea.egothor.org/api/packages/Egothor/maven")
}
// Use Maven Central for resolving dependencies.
mavenCentral()
}
// Compile, but don't produce any artifacts
tasks.named("jar").configure { enabled = false }
tasks.named("javadoc").configure { enabled = false }
tasks.named("assemble").configure { enabled = false }
// Make these tests opt-in so they dont run in normal CI builds
tasks.named("test", Test) {
useJUnitPlatform {
includeTags("sample") // only run tests that are explicitly tagged as samples
}
// Only run if -PrunSamples is supplied
onlyIf {
project.hasProperty("runSamples")
}
// Don't fail the build if zero tests match the tag
filter {
failOnNoMatchingTests = false
}
}

View File

@@ -0,0 +1,259 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package demo;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.SecureRandom;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.SecretKey;
import org.junit.jupiter.api.Test;
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;
import zeroecho.core.context.EncryptionContext;
import zeroecho.core.spi.ContextAware;
import zeroecho.core.util.Strings;
import zeroecho.sdk.builders.alg.AesDataContentBuilder;
import zeroecho.sdk.builders.core.DataContentChainBuilder;
import zeroecho.sdk.builders.core.PlainBytesBuilder;
import zeroecho.sdk.content.api.DataContent;
class AesTest {
private static final Logger LOG = Logger.getLogger(AesTest.class.getName());
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())
return key;
}
@Test
void aesEncryptCoreLevelAPI() throws GeneralSecurityException, IOException {
// Sample message to encrypt
byte[] msg = randomBytes(100);
// AES-GCM specification with a 128-bit authentication tag
// The null parameter indicates that no header codec is used (more on this
// below)
AesSpec spec = AesSpec.gcm128(null);
// Encryption will generate a random IV for us. Normally it would be saved
// through the header codec, but since we do not use it here, the IV and other
// values are stored in a "context" instead. The "context" is a key-value
// in-memory store; we create a private context with a unique name for this
// session.
CtxInterface session = Ctx.INSTANCE.getContext("aes-ctx-" + System.nanoTime());
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)) {
// 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
try (InputStream encryptedStream = enc.attach(new ByteArrayInputStream(msg))) {
// In this sample we consume the encrypted stream fully into memory
encrypted = readAll(encryptedStream);
}
}
LOG.log(Level.INFO, "Core: AES256/GCM128 IV={0} AAD={1} encrypted={2}", new Object[] {
// The IV was generated automatically (we could have provided one, but
// if omitted, it is created for us)
Strings.toShortHexString(session.get(ConfluxKeys.iv("AES"))),
// AAD is an additional parameter for GCM; again, automatically generated
// because we did not provide it
Strings.toShortHexString(session.get(ConfluxKeys.aad("AES"))),
// The final ciphertext
Strings.toShortHexString(encrypted) });
}
@Test
void aesEncryptSdkLevelAPI() throws GeneralSecurityException, IOException {
// Sample message to encrypt
byte[] msg = randomBytes(100);
SecretKey key = generateAesKey();
AesSpec spec = AesSpec.gcm128(null);
// 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()
// Use the generated AES key
.importKeyRaw(key.getEncoded())
// Specify AES-GCM-128 mode
.spec(spec)
// Provide the context to capture IV and AAD
.context(session);
DataContent dccb = DataContentChainBuilder
// Build an encryption pipeline
.encrypt()
// Input: the sample byte array
.add(PlainBytesBuilder.builder().bytes(msg))
// Process through AES encryption
.add(aesBuilder)
// Finalize the pipeline
.build();
byte[] encrypted;
try (InputStream encryptedStream = dccb.getStream()) {
// Consume the encrypted data into memory
encrypted = readAll(encryptedStream);
}
LOG.log(Level.INFO, "SDK: AES256/GCM128 IV={0} AAD={1} encrypted={2}", new Object[] {
// IV generated for us (we could provide one; if omitted, a random IV is
// created)
Strings.toShortHexString(session.get(ConfluxKeys.iv("AES"))),
// AAD generated automatically (not explicitly provided)
Strings.toShortHexString(session.get(ConfluxKeys.aad("AES"))),
// Final ciphertext
Strings.toShortHexString(encrypted) });
}
@Test
void aesEncryptSmarterSdkLevelAPI() throws GeneralSecurityException, IOException {
// Sample message to encrypt
byte[] msg = randomBytes(100);
AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder()
// Automatically generate a 256-bit AES key
.generateKey(256)
// Store ad-hoc generated parameters (IV, AAD) in the stream header
.withHeader()
// Use AES-GCM with a 128-bit authentication tag
.modeGcm(128);
// The builder stores all generated attributes inside the stream header
DataContent dccb = DataContentChainBuilder
// Build an encryption pipeline
.encrypt()
// Input: the sample byte array
.add(PlainBytesBuilder.builder().bytes(msg))
// Process through AES encryption
.add(aesBuilder)
// Finalize the pipeline
.build();
LOG.log(Level.INFO, "SDK-smart: AES256 key generated {0}",
// The key is generated within dccb's `build` method, not earlier
Strings.toShortHexString(aesBuilder.generatedKey().getEncoded()));
byte[] encrypted;
try (InputStream encryptedStream = dccb.getStream()) {
// Consume the encrypted data into memory
encrypted = readAll(encryptedStream);
}
LOG.log(Level.INFO, "SDK-smart: AES256/GCM128 IV=builtin AAD=builtin encrypted={0}",
// The ciphertext, with IV and AAD already embedded in the header
Strings.toShortHexString(encrypted));
}
@Test
void aesRoundSmarterSdkLevelAPI() throws GeneralSecurityException, IOException {
// Sample message to encrypt
byte[] msg = randomBytes(100);
AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder().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)
.build();
SecretKey key = aesBuilder.generatedKey();
LOG.log(Level.INFO, "SDK-smart: AES256 key generated {0}", Strings.toShortHexString(key.getEncoded()));
byte[] encrypted;
try (InputStream encryptedStream = dccb.getStream()) {
// Consume the encrypted data into memory
encrypted = readAll(encryptedStream);
}
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())
// Build the pipeline
.build();
byte[] decrypted;
try (InputStream decryptedStream = dccb.getStream()) {
// Consume the decrypted data into memory
decrypted = readAll(decryptedStream);
}
LOG.log(Level.INFO, "original message={0} after AES roundtrip={1}",
new Object[] { Strings.toShortHexString(msg), Strings.toShortHexString(decrypted) });
}
// helpers
private static byte[] randomBytes(int len) {
byte[] data = new byte[len];
new SecureRandom().nextBytes(data);
return data;
}
private static byte[] readAll(InputStream in) throws IOException {
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
in.transferTo(out);
out.flush();
return out.toByteArray();
}
}
}

View File

@@ -0,0 +1,194 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package demo;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.SecureRandom;
import java.util.logging.Level;
import java.util.logging.Logger;
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;
import zeroecho.core.alg.rsa.RsaKeyGenSpec;
import zeroecho.core.util.Strings;
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.guard.MultiRecipientDataSourceBuilder;
import zeroecho.sdk.guard.UnlockMaterial;
import zeroecho.sdk.util.BouncyCastleActivator;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class CombinedDeliveryTest {
private static final Logger LOG = Logger.getLogger(CombinedDeliveryTest.class.getName());
@BeforeAll
void setupProviders() {
BouncyCastleActivator.init();
}
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()) });
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()) });
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()) });
return kp;
}
@Test
void combinedSdkLevelAPI() throws GeneralSecurityException, IOException {
// Sample message to encrypt
byte[] msg = randomBytes(100);
KeyPair kem = generateKyberKeys();
KeyPair rsa = generateRsaKeys();
KeyPair elg = generateElGamalKeys();
char[] password = "p@ssw07d".toCharArray();
KeyPair decoy1rsa = generateRsaKeys();
KeyPair decoy2rsa = generateRsaKeys();
AesDataContentBuilder payload = AesDataContentBuilder.builder().modeCbcPkcs5().withHeader();
MultiRecipientDataSourceBuilder multi = MultiRecipientDataSourceBuilder.builder()
// 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()))
// ML-KEM PostQuantum uses AES for the inner payload
.addRecipient(CryptoAlgorithms.create("ML-KEM", KeyUsage.ENCAPSULATE, kem.getPublic()),
/* AES256 key size */
32,
/* salt size in hkdf */
32)
// Password (via KDF)
.addPasswordRecipient(password, /* iterations */ 200_000, /* saltLen */ 16, /* kekBytes */ 32)
// and some decoys
.addRecipient(CryptoAlgorithms.create("RSA", KeyUsage.ENCRYPT, decoy1rsa.getPublic()))
.addRecipient(CryptoAlgorithms.create("RSA", KeyUsage.ENCRYPT, decoy2rsa.getPublic()));
// shuffle all the recipients
multi.shuffle();
DataContent dccb = DataContentChainBuilder.encrypt().add(PlainBytesBuilder.builder().bytes(msg))
// encrypt for multi recipients of various types
.add(multi).build();
byte[] encrypted;
try (InputStream encryptedStream = dccb.getStream()) {
// Consume the encrypted data into memory
encrypted = readAll(encryptedStream);
}
recipientProcessing("ElGamal-ffdhe2048", msg, encrypted, new UnlockMaterial.Private(elg.getPrivate()));
recipientProcessing("RSA4096", msg, encrypted, new UnlockMaterial.Private(rsa.getPrivate()));
recipientProcessing("Kyber-1024", msg, encrypted, new UnlockMaterial.Private(kem.getPrivate()));
recipientProcessing("Password", msg, encrypted, new UnlockMaterial.Password(password));
}
private void recipientProcessing(String method, byte[] msg, byte[] encrypted, UnlockMaterial unlock)
throws IOException {
AesDataContentBuilder payload = AesDataContentBuilder.builder().modeCbcPkcs5().withHeader();
MultiRecipientDataSourceBuilder multi = MultiRecipientDataSourceBuilder.builder()
// define our payload
.payloadKeyBytes(32).withAes(payload)
// one recipient
.unlockWith(unlock);
// Decryption
DataContent dccb = DataContentChainBuilder.decrypt().add(PlainBytesBuilder.builder().bytes(encrypted))
// decrypt via multi
.add(multi).build();
byte[] decrypted;
try (InputStream decryptedStream = dccb.getStream()) {
// Consume the decrypted data into memory
decrypted = readAll(decryptedStream);
}
LOG.log(Level.INFO, "original message={0} after {2}/AES256/CBC/PKCS5 roundtrip={1}",
new Object[] { Strings.toShortHexString(msg), Strings.toShortHexString(decrypted), method });
}
// helpers
private static byte[] randomBytes(int len) {
byte[] data = new byte[len];
new SecureRandom().nextBytes(data);
return data;
}
private static byte[] readAll(InputStream in) throws IOException {
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
in.transferTo(out);
out.flush();
return out.toByteArray();
}
}
}

View File

@@ -0,0 +1,131 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package demo;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.SecureRandom;
import java.util.logging.Level;
import java.util.logging.Logger;
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;
import zeroecho.sdk.builders.alg.KemDataContentBuilder;
import zeroecho.sdk.builders.core.DataContentChainBuilder;
import zeroecho.sdk.builders.core.PlainBytesBuilder;
import zeroecho.sdk.content.api.DataContent;
import zeroecho.sdk.util.BouncyCastleActivator;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class PostQuantumTest {
private static final Logger LOG = Logger.getLogger(PostQuantumTest.class.getName());
@BeforeAll
void setupProviders() {
BouncyCastleActivator.init();
}
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()) });
return kp;
}
@Test
void pqcSdkLevelAPI() throws GeneralSecurityException, IOException {
// Sample message to encrypt
byte[] msg = randomBytes(100);
KeyPair kp = generateKyberKeys();
DataContent dccb = DataContentChainBuilder.encrypt().add(PlainBytesBuilder.builder().bytes(msg))
.add(KemDataContentBuilder.builder().kem("ML-KEM").recipientPublic(kp.getPublic())
.hkdfSha256("KEM-demo".getBytes(StandardCharsets.US_ASCII))
.withAes(AesDataContentBuilder.builder().modeGcm(128).withHeader()))
.build();
byte[] encrypted;
try (InputStream encryptedStream = dccb.getStream()) {
// Consume the encrypted data into memory
encrypted = readAll(encryptedStream);
}
// Decryption
dccb = DataContentChainBuilder.decrypt().add(PlainBytesBuilder.builder().bytes(encrypted))
.add(KemDataContentBuilder.builder().kem("ML-KEM").recipientPrivate(kp.getPrivate())
.hkdfSha256("KEM-demo".getBytes(StandardCharsets.US_ASCII))
.withAes(AesDataContentBuilder.builder().modeGcm(128).withHeader()))
.build();
byte[] decrypted;
try (InputStream decryptedStream = dccb.getStream()) {
// Consume the decrypted data into memory
decrypted = readAll(decryptedStream);
}
LOG.log(Level.INFO, "original message={0} after ML-KEM(Kyber)+AES/GCM128 roundtrip={1}",
new Object[] { Strings.toShortHexString(msg), Strings.toShortHexString(decrypted) });
}
// helpers
private static byte[] randomBytes(int len) {
byte[] data = new byte[len];
new SecureRandom().nextBytes(data);
return data;
}
private static byte[] readAll(InputStream in) throws IOException {
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
in.transferTo(out);
out.flush();
return out.toByteArray();
}
}
}

View File

@@ -0,0 +1,212 @@
/*******************************************************************************
* Copyright (C) 2025, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package demo;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.SecureRandom;
import java.security.Signature;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.SecretKey;
import org.junit.jupiter.api.Test;
import zeroecho.core.CryptoAlgorithm;
import zeroecho.core.CryptoAlgorithms;
import zeroecho.core.alg.aes.AesKeyGenSpec;
import zeroecho.core.alg.rsa.RsaKeyGenSpec;
import zeroecho.core.alg.rsa.RsaSigSpec;
import zeroecho.core.tag.TagEngine;
import zeroecho.core.tag.TagEngineBuilder;
import zeroecho.core.util.Strings;
import zeroecho.sdk.builders.TagTrailerDataContentBuilder;
import zeroecho.sdk.builders.alg.AesDataContentBuilder;
import zeroecho.sdk.builders.core.DataContentChainBuilder;
import zeroecho.sdk.builders.core.PlainBytesBuilder;
import zeroecho.sdk.content.api.DataContent;
class SigningAesTest {
private static final Logger LOG = Logger.getLogger(SigningAesTest.class.getName());
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())
return key;
}
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()) });
return kp;
}
@Test
void aesRoundStESdkLevelAPI() throws GeneralSecurityException, IOException {
LOG.info("aesRoundSmarterSdkLevelAPI - Sign then Encrypt");
// Sample message to encrypt
byte[] msg = randomBytes(100);
AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder().generateKey(256).modeGcm(128).withHeader();
// RSA-2048 keys (use registry for convenience)
KeyPair rsa = generateRsaKeys();
// Tag engines (SHA-256, saltLen=32)
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();
// The builder stores generated IV and AAD inside the stream header
DataContent dccb = DataContentChainBuilder.encrypt().add(PlainBytesBuilder.builder().bytes(msg))
// sign the data
.add(new TagTrailerDataContentBuilder<>(tagEnc).bufferSize(8192))
// and then encrypt
.add(aesBuilder).build();
SecretKey key = aesBuilder.generatedKey();
LOG.log(Level.INFO, "SDK-smart: AES256 key generated {0}", Strings.toShortHexString(key.getEncoded()));
byte[] encrypted;
try (InputStream encryptedStream = dccb.getStream()) {
// Consume the encrypted data into memory
encrypted = readAll(encryptedStream);
}
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())
// the decrypted stream must be verified
.add(new TagTrailerDataContentBuilder<>(tagDec).bufferSize(8192).throwOnMismatch())
// Build the pipeline
.build();
byte[] decrypted;
try (InputStream decryptedStream = dccb.getStream()) {
// Consume the decrypted data into memory
decrypted = readAll(decryptedStream);
}
LOG.log(Level.INFO, "original message={0} after AES roundtrip={1}",
new Object[] { Strings.toShortHexString(msg), Strings.toShortHexString(decrypted) });
}
@Test
void aesRoundEtSSdkLevelAPI() throws GeneralSecurityException, IOException {
LOG.info("aesRoundSmarterSdkLevelAPI - Encrypt then Sign");
// Sample message to encrypt
byte[] msg = randomBytes(100);
AesDataContentBuilder aesBuilder = AesDataContentBuilder.builder().generateKey(256).modeGcm(128).withHeader();
// RSA-2048 keys (use registry for convenience)
KeyPair rsa = generateRsaKeys();
// Tag engines (SHA-256, saltLen=32)
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();
// The builder stores generated IV and AAD inside the stream header
DataContent dccb = DataContentChainBuilder.encrypt().add(PlainBytesBuilder.builder().bytes(msg))
// encrypt
.add(aesBuilder)
// and then sign
.add(new TagTrailerDataContentBuilder<>(tagEnc).bufferSize(8192))
//
.build();
SecretKey key = aesBuilder.generatedKey();
LOG.log(Level.INFO, "SDK-smart: AES256 key generated {0}", Strings.toShortHexString(key.getEncoded()));
byte[] encrypted;
try (InputStream encryptedStream = dccb.getStream()) {
// Consume the encrypted data into memory
encrypted = readAll(encryptedStream);
}
dccb = DataContentChainBuilder.decrypt().add(PlainBytesBuilder.builder().bytes(encrypted))
// the stream must be verified, but encryption still runs as data flows through
.add(new TagTrailerDataContentBuilder<>(tagDec).bufferSize(8192).throwOnMismatch())
// Use the same AES key for decryption; IV and AAD are restored from the header
.add(AesDataContentBuilder.builder().importKeyRaw(key.getEncoded()).modeGcm(128).withHeader())
// Build the pipeline
.build();
byte[] decrypted;
try (InputStream decryptedStream = dccb.getStream()) {
// Consume the decrypted data into memory
decrypted = readAll(decryptedStream);
}
LOG.log(Level.INFO, "original message={0} after AES roundtrip={1}",
new Object[] { Strings.toShortHexString(msg), Strings.toShortHexString(decrypted) });
}
// helpers
private static byte[] randomBytes(int len) {
byte[] data = new byte[len];
new SecureRandom().nextBytes(data);
return data;
}
private static byte[] readAll(InputStream in) throws IOException {
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
in.transferTo(out);
out.flush();
return out.toByteArray();
}
}
}