260 lines
11 KiB
Java
260 lines
11 KiB
Java
/*******************************************************************************
|
|
* 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();
|
|
}
|
|
}
|
|
}
|