Initial commit
This commit is contained in:
26
lib/.classpath
Normal file
26
lib/.classpath
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="bin/main" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="gradle_scope" value="main"/>
|
||||
<attribute name="gradle_used_by_scope" value="main,test"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<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="src" output="bin/test" path="src/test/resources">
|
||||
<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
lib/.project
Normal file
23
lib/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>lib</name>
|
||||
<comment>Project lib 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
lib/LICENSE
Normal file
31
lib/LICENSE
Normal 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.
|
||||
13
lib/build.gradle
Normal file
13
lib/build.gradle
Normal file
@@ -0,0 +1,13 @@
|
||||
plugins {
|
||||
id 'buildlogic.java-library-conventions'
|
||||
id 'com.palantir.git-version' version '4.0.0'
|
||||
}
|
||||
|
||||
group 'org.egothor'
|
||||
version gitVersion(prefix:'release@')
|
||||
|
||||
dependencies {
|
||||
implementation 'org.bouncycastle:bcpkix-jdk18on'
|
||||
implementation 'org.egothor:conflux'
|
||||
implementation 'org.apache.commons:commons-imaging'
|
||||
}
|
||||
93
lib/src/main/java/zeroecho/builder/AesBuilder.java
Normal file
93
lib/src/main/java/zeroecho/builder/AesBuilder.java
Normal file
@@ -0,0 +1,93 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.builder;
|
||||
|
||||
import zeroecho.data.DataContent;
|
||||
import zeroecho.data.processing.AesDecryptor;
|
||||
import zeroecho.data.processing.AesEncryptor;
|
||||
|
||||
/**
|
||||
* Builder interface for constructing AES encryption or decryption
|
||||
* {@link DataContent} instances.
|
||||
* <p>
|
||||
* The builder can be configured to build either an AES encryptor or decryptor.
|
||||
* The constructed {@code DataContent} will perform the corresponding
|
||||
* cryptographic operation.
|
||||
*
|
||||
* <p>
|
||||
* Usage example:
|
||||
*
|
||||
* <pre>{@code
|
||||
* DataContent encryptor = AesBuilder.builder().build(true);
|
||||
*
|
||||
* DataContent decryptor = AesBuilder.builder().build(false);
|
||||
* }</pre>
|
||||
*/
|
||||
public interface AesBuilder extends DataContentBuilder<DataContent> { // NOPMD
|
||||
/**
|
||||
* Creates a new instance of the default AES builder implementation.
|
||||
*
|
||||
* @return a new {@code AesBuilder}
|
||||
*/
|
||||
static AesBuilder builder() {
|
||||
return new DefaultAesContentBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of the {@link AesBuilder} interface.
|
||||
* <p>
|
||||
* Builds an AES encryption or decryption content instance depending on the
|
||||
* {@code encrypt} parameter passed to {@link #build(boolean)}.
|
||||
* </p>
|
||||
*/
|
||||
final class DefaultAesContentBuilder implements AesBuilder {
|
||||
|
||||
private DefaultAesContentBuilder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns an AES encryptor or decryptor.
|
||||
*
|
||||
* @param encrypt {@code true} to build an {@link AesEncryptor}, {@code false}
|
||||
* to build an {@link AesDecryptor}
|
||||
* @return a {@link DataContent} instance configured for encryption or
|
||||
* decryption
|
||||
*/
|
||||
@Override
|
||||
public DataContent build(final boolean encrypt) {
|
||||
return encrypt ? new AesEncryptor(null) : new AesDecryptor(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
197
lib/src/main/java/zeroecho/builder/AesRandomBuilder.java
Normal file
197
lib/src/main/java/zeroecho/builder/AesRandomBuilder.java
Normal file
@@ -0,0 +1,197 @@
|
||||
/**
|
||||
* 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 zeroecho.builder;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import zeroecho.data.DataContent;
|
||||
import zeroecho.data.PlainContent;
|
||||
import zeroecho.data.processing.SecretAesRandom;
|
||||
import zeroecho.util.aes.AesCipherType;
|
||||
import zeroecho.util.aes.AesMode;
|
||||
|
||||
/**
|
||||
* A builder interface for constructing {@link SecretAesRandom} instances, which
|
||||
* produce AES-based pseudorandom data wrapped as {@link PlainContent}.
|
||||
*
|
||||
* <p>
|
||||
* This builder allows configuring:
|
||||
* <ul>
|
||||
* <li>The AES mode (which determines key length: 128, 192, or 256 bits)</li>
|
||||
* <li>The AES cipher type (e.g., CBC, GCM)</li>
|
||||
* <li>An optional input source that feeds into the random generator</li>
|
||||
* <li>Additional Authenticated Data (AAD) to be associated with AES cipher
|
||||
* operations</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* The {@link #build(boolean)} method produces the configured instance and
|
||||
* accepts a boolean flag indicating whether the content should operate in
|
||||
* encryption or decryption mode. When an input is provided via
|
||||
* {@link #input(DataContent)}, the meaning of this flag becomes relevant:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>If {@code encrypt} is {@code true}, the builder produces a randomizer
|
||||
* suitable for encryption.</li>
|
||||
* <li>If {@code encrypt} is {@code false}, the builder configures the content
|
||||
* for decryption based on the input.</li>
|
||||
* <li>If no input is provided, the {@code encrypt} flag may be ignored.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* Usage example:
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
* PlainContent random = AesRandomBuilder.builder()
|
||||
* .mode(AesMode.AES_256)
|
||||
* .cipherType(AesCipherType.CBC)
|
||||
* .withAad(new byte[] { ... })
|
||||
* .build(true);
|
||||
* }</pre>
|
||||
*/
|
||||
public interface AesRandomBuilder extends DataContentBuilder<PlainContent> {
|
||||
|
||||
/**
|
||||
* Sets the AES mode, which determines the key length (128, 192, or 256 bits).
|
||||
*
|
||||
* @param mode the AES mode; must not be null
|
||||
* @return this builder instance for method chaining
|
||||
* @throws NullPointerException if {@code mode} is null
|
||||
*/
|
||||
AesRandomBuilder mode(AesMode mode);
|
||||
|
||||
/**
|
||||
* Sets the AES cipher type (e.g., CBC, GCM).
|
||||
*
|
||||
* @param cipherType the cipher type; must not be null
|
||||
* @return this builder instance for method chaining
|
||||
* @throws NullPointerException if {@code cipherType} is null
|
||||
*/
|
||||
AesRandomBuilder cipherType(AesCipherType cipherType);
|
||||
|
||||
/**
|
||||
* Sets the input {@link DataContent} to be used as source.
|
||||
*
|
||||
* @param input the data content to wrap; must not be null
|
||||
* @return this builder instance for method chaining
|
||||
* @throws NullPointerException if {@code input} is null
|
||||
*/
|
||||
AesRandomBuilder input(DataContent input);
|
||||
|
||||
/**
|
||||
* Sets the Additional Authenticated Data (AAD) to be associated with the AES
|
||||
* cipher.
|
||||
*
|
||||
* @param aad the additional authenticated data to use; may be {@code null} if
|
||||
* none
|
||||
* @return this builder instance for method chaining
|
||||
*/
|
||||
AesRandomBuilder withAad(byte[] aad);
|
||||
|
||||
/**
|
||||
* Creates a new instance of the default builder implementation.
|
||||
*
|
||||
* @return a new {@code AesRandomBuilder}
|
||||
*/
|
||||
static AesRandomBuilder builder() {
|
||||
return new DefaultAesRandomBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of the {@link AesRandomBuilder} interface.
|
||||
* <p>
|
||||
* Builds a {@link SecretAesRandom} instance with the configured AES mode,
|
||||
* cipher type, and AAD.
|
||||
* </p>
|
||||
*/
|
||||
final class DefaultAesRandomBuilder implements AesRandomBuilder {
|
||||
private AesMode modeField;
|
||||
private AesCipherType cipherTypeField;
|
||||
private DataContent source;
|
||||
private byte[] aadField;
|
||||
|
||||
private DefaultAesRandomBuilder() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AesRandomBuilder mode(final AesMode mode) {
|
||||
this.modeField = Objects.requireNonNull(mode, "mode must not be null");
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AesRandomBuilder cipherType(final AesCipherType cipherType) {
|
||||
this.cipherTypeField = Objects.requireNonNull(cipherType, "cipherType must not be null");
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AesRandomBuilder input(final DataContent input) {
|
||||
this.source = Objects.requireNonNull(input, "input must not be null");
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AesRandomBuilder withAad(final byte[] aad) {
|
||||
this.aadField = aad; // NOPMD
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds and returns the {@link SecretAesRandom} instance configured by this
|
||||
* builder.
|
||||
*
|
||||
* @param encrypt {@code true} to configure for encryption mode, {@code false}
|
||||
* for decryption mode
|
||||
* @return a configured {@link PlainContent} instance representing the AES
|
||||
* random content
|
||||
* @throws IllegalStateException if AES mode or cipher type has not been set
|
||||
*/
|
||||
@Override
|
||||
public PlainContent build(final boolean encrypt) {
|
||||
if (modeField == null) {
|
||||
throw new IllegalStateException("AES mode must be set before building");
|
||||
}
|
||||
if (cipherTypeField == null) {
|
||||
throw new IllegalStateException("AES cipher type must be set before building");
|
||||
}
|
||||
final SecretAesRandom secretAesRandom = new SecretAesRandom(modeField, cipherTypeField, aadField);
|
||||
if (source != null) {
|
||||
secretAesRandom.setInput(source);
|
||||
}
|
||||
return secretAesRandom;
|
||||
}
|
||||
}
|
||||
}
|
||||
78
lib/src/main/java/zeroecho/builder/DataContentBuilder.java
Normal file
78
lib/src/main/java/zeroecho/builder/DataContentBuilder.java
Normal file
@@ -0,0 +1,78 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.builder;
|
||||
|
||||
import zeroecho.data.DataContent;
|
||||
|
||||
/**
|
||||
* A builder interface for constructing instances of {@link DataContent}.
|
||||
* <p>
|
||||
* This interface enables a uniform and extensible way to create various
|
||||
* {@code DataContent} implementations in a fluent, builder-based style. It is
|
||||
* intended to be used with builder classes that encapsulate construction
|
||||
* parameters and logic specific to each {@code DataContent} subtype.
|
||||
* <p>
|
||||
* The {@link #build(boolean)} method configures the resulting
|
||||
* {@code DataContent} instance for either encryption or decryption mode. When
|
||||
* encryption is selected, additional metadata (such as headers) may be added to
|
||||
* the output stream; this metadata must be preserved and reused during
|
||||
* decryption.
|
||||
* <p>
|
||||
* Typical usage example:
|
||||
*
|
||||
* <pre>{@code
|
||||
* DataContent content = SomeDataContentBuilder.builder()
|
||||
* .parameterX(...)
|
||||
* .parameterY(...)
|
||||
* .build(true); // true for encryption mode
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T> the type of {@link DataContent} produced by this builder
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public interface DataContentBuilder<T extends DataContent> { // NOPMD
|
||||
/**
|
||||
* Constructs and returns a new {@link DataContent} instance based on the
|
||||
* builder's current configuration.
|
||||
*
|
||||
* @param encrypt whether the resulting {@code DataContent} should be configured
|
||||
* for encryption ({@code true}) or decryption ({@code false}).
|
||||
* In encryption mode, additional headers or metadata may be
|
||||
* inserted into the stream which must be retained for successful
|
||||
* decryption.
|
||||
* @return the constructed {@code DataContent} instance
|
||||
*/
|
||||
T build(boolean encrypt);
|
||||
}
|
||||
158
lib/src/main/java/zeroecho/builder/DataContentChainBuilder.java
Normal file
158
lib/src/main/java/zeroecho/builder/DataContentChainBuilder.java
Normal file
@@ -0,0 +1,158 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.builder;
|
||||
|
||||
import zeroecho.data.DataContent;
|
||||
|
||||
/**
|
||||
* A builder interface for constructing a chain of {@link DataContent}
|
||||
* components, where each content unit passes its output as the input to the
|
||||
* next one.
|
||||
*
|
||||
* <p>
|
||||
* This builder simplifies the composition of processing pipelines such as:
|
||||
* <ul>
|
||||
* <li>Encryption with multiple layers (e.g., compression → encryption →
|
||||
* signing)</li>
|
||||
* <li>Decryption chains (e.g., verification → decryption → decompression)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* The builder supports both encryption and decryption modes, which are applied
|
||||
* consistently across all {@link DataContentBuilder} instances in the chain.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Usage example: <pre>{@code
|
||||
* DataContent chain = DataContentChainBuilder.encrypt()
|
||||
* .add(PlainStringBuilder.builder().value("hello"))
|
||||
* .add(DerivedAesParametersBuilder.builder().password("secret"))
|
||||
* .build();
|
||||
* }</pre>
|
||||
*/
|
||||
public interface DataContentChainBuilder {
|
||||
/**
|
||||
* Adds a {@link DataContentBuilder} to the chain and links its output to the
|
||||
* input of the previously added {@code DataContent}, if any.
|
||||
*
|
||||
* <p>
|
||||
* The {@link DataContent} produced by this builder becomes the new tail of the
|
||||
* chain.
|
||||
* </p>
|
||||
*
|
||||
* @param builder the builder producing the next {@code DataContent}; must not
|
||||
* be null
|
||||
* @return this chain builder instance, allowing for fluent chaining
|
||||
* @throws NullPointerException if {@code builder} is null
|
||||
*/
|
||||
DataContentChainBuilder add(DataContentBuilder<? extends DataContent> builder);
|
||||
|
||||
/**
|
||||
* Finalizes the chain and returns the tail {@link DataContent} instance.
|
||||
*
|
||||
* <p>
|
||||
* The returned content may internally reference earlier content via
|
||||
* {@link DataContent#setInput(DataContent)} chaining.
|
||||
* </p>
|
||||
*
|
||||
* @return the last {@code DataContent} in the chain, or {@code null} if no
|
||||
* builders were added
|
||||
*/
|
||||
DataContent build();
|
||||
|
||||
/**
|
||||
* Creates a new {@code DataContentChainBuilder} configured for encryption mode.
|
||||
*
|
||||
* <p>
|
||||
* All added {@link DataContentBuilder} instances will receive {@code true} for
|
||||
* their {@code build(boolean encrypt)} method, indicating encryption behavior.
|
||||
* </p>
|
||||
*
|
||||
* @return a new chain builder in encryption mode
|
||||
*/
|
||||
static DataContentChainBuilder encrypt() {
|
||||
return new DefaultDataContentChainBuilder(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@code DataContentChainBuilder} configured for decryption mode.
|
||||
*
|
||||
* <p>
|
||||
* All added {@link DataContentBuilder} instances will receive {@code false} for
|
||||
* their {@code build(boolean encrypt)} method, indicating decryption behavior.
|
||||
* </p>
|
||||
*
|
||||
* @return a new chain builder in decryption mode
|
||||
*/
|
||||
static DataContentChainBuilder decrypt() {
|
||||
return new DefaultDataContentChainBuilder(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of {@link DataContentChainBuilder}.
|
||||
*
|
||||
* <p>
|
||||
* Maintains a reference to the tail of the content chain. Each added builder is
|
||||
* built in the specified encryption/decryption mode and connected via
|
||||
* {@link DataContent#setInput(DataContent)} to the previously built content.
|
||||
* </p>
|
||||
*/
|
||||
final class DefaultDataContentChainBuilder implements DataContentChainBuilder {
|
||||
private DataContent tail;
|
||||
private final boolean encrypt;
|
||||
|
||||
private DefaultDataContentChainBuilder(final boolean encrypt) {
|
||||
this.encrypt = encrypt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataContentChainBuilder add(final DataContentBuilder<? extends DataContent> builder) {
|
||||
final DataContent previous = tail;
|
||||
|
||||
tail = builder.build(encrypt);
|
||||
|
||||
if (previous != null) {
|
||||
tail.setInput(previous);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataContent build() {
|
||||
return tail;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.builder;
|
||||
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import zeroecho.data.SecretContent;
|
||||
import zeroecho.data.processing.SecretDerivedAesParameters;
|
||||
import zeroecho.util.aes.AesCipherType;
|
||||
import zeroecho.util.aes.AesMode;
|
||||
import zeroecho.util.aes.AesSupport;
|
||||
|
||||
/**
|
||||
* A builder interface for constructing {@link SecretDerivedAesParameters}
|
||||
* instances that encapsulate AES encryption parameters derived from a password.
|
||||
*
|
||||
* <p>
|
||||
* This builder enables configuration of password-based encryption using PBKDF2
|
||||
* (or a similar KDF), including selection of key strength, cipher type,
|
||||
* iteration count, and optional Additional Authenticated Data (AAD) for AEAD
|
||||
* cipher modes.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Typical usage:
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
* SecretContent secret = DerivedAesParametersBuilder.builder()
|
||||
* .password("myStrongPassword")
|
||||
* .iterations(100_000)
|
||||
* .mode(AesMode.AES_256)
|
||||
* .cipherType(AesCipherType.GCM)
|
||||
* .withAad(aadBytes)
|
||||
* .build(true); // true for encryption mode
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* If invalid parameters are provided (e.g., nulls or unsupported key specs),
|
||||
* {@link IllegalArgumentException} will be thrown during the build process.
|
||||
* </p>
|
||||
*/
|
||||
public interface DerivedAesParametersBuilder extends DataContentBuilder<SecretContent> {
|
||||
/**
|
||||
* Sets the password used to derive the AES key.
|
||||
* <p>
|
||||
* This password must be non-null and should have sufficient entropy for secure
|
||||
* encryption.
|
||||
* </p>
|
||||
*
|
||||
* @param password the password string; must not be {@code null} or empty
|
||||
* @return this builder instance
|
||||
* @throws IllegalArgumentException if password is null or empty
|
||||
*/
|
||||
DerivedAesParametersBuilder password(String password);
|
||||
|
||||
/**
|
||||
* Specifies the number of iterations for the key derivation function.
|
||||
* <p>
|
||||
* Higher iteration counts slow down brute-force attacks, but may increase
|
||||
* processing time.
|
||||
* </p>
|
||||
*
|
||||
* @param iterations number of iterations; must be a positive integer
|
||||
* @return this builder instance
|
||||
* @throws IllegalArgumentException if iterations is not positive
|
||||
*/
|
||||
DerivedAesParametersBuilder iterations(int iterations);
|
||||
|
||||
/**
|
||||
* Defines the AES mode to use, which determines the derived key length (e.g.,
|
||||
* 128-bit, 192-bit, or 256-bit).
|
||||
*
|
||||
* @param mode the AES key size mode; must not be {@code null}
|
||||
* @return this builder instance
|
||||
* @throws NullPointerException if {@code mode} is null
|
||||
*/
|
||||
DerivedAesParametersBuilder mode(AesMode mode);
|
||||
|
||||
/**
|
||||
* Specifies the AES cipher type (e.g., CBC or GCM) to be used for encryption or
|
||||
* decryption.
|
||||
*
|
||||
* @param cipherType the cipher mode; must not be {@code null}
|
||||
* @return this builder instance
|
||||
* @throws NullPointerException if {@code cipherType} is null
|
||||
*/
|
||||
DerivedAesParametersBuilder cipherType(AesCipherType cipherType);
|
||||
|
||||
/**
|
||||
* Sets Additional Authenticated Data (AAD) for the AES cipher.
|
||||
* <p>
|
||||
* This data will be included in the authentication tag for AEAD cipher modes
|
||||
* such as GCM. It may be {@code null} if no AAD is used.
|
||||
* </p>
|
||||
*
|
||||
* @param aad the additional authenticated data bytes, or {@code null}
|
||||
* @return this builder instance
|
||||
*/
|
||||
DerivedAesParametersBuilder withAad(byte[] aad);
|
||||
|
||||
/**
|
||||
* Creates a new builder instance with default values:
|
||||
* <ul>
|
||||
* <li>{@link AesMode#AES_256}</li>
|
||||
* <li>{@link AesCipherType#CBC}</li>
|
||||
* <li>{@code iterations = AesSupport.KEY_ITERATIONS}</li>
|
||||
* <li>{@code aad = null}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return a new {@code DerivedAesParametersBuilder}
|
||||
*/
|
||||
static DerivedAesParametersBuilder builder() {
|
||||
return new DefaultDerivedAesParametersBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of the {@link DerivedAesParametersBuilder} interface.
|
||||
* <p>
|
||||
* Builds a {@link SecretDerivedAesParameters} instance configured with the
|
||||
* supplied parameters.
|
||||
* <p>
|
||||
* On failure to create the secret content due to invalid parameters or key
|
||||
* specification errors, this builder logs the error and rethrows as an
|
||||
* {@link IllegalArgumentException}.
|
||||
*/
|
||||
final class DefaultDerivedAesParametersBuilder implements DerivedAesParametersBuilder {
|
||||
private static final Logger LOG = Logger
|
||||
.getLogger(DerivedAesParametersBuilder.DefaultDerivedAesParametersBuilder.class.getName());
|
||||
|
||||
private String passwordField;
|
||||
private int iterationsField = AesSupport.KEY_ITERATIONS;
|
||||
private AesMode modeField = AesMode.AES_256;
|
||||
private AesCipherType cipherTypeField = AesCipherType.CBC;
|
||||
private byte[] aadField;
|
||||
|
||||
private DefaultDerivedAesParametersBuilder() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DerivedAesParametersBuilder password(final String password) {
|
||||
if (password == null || password.isEmpty()) {
|
||||
throw new IllegalArgumentException("password must not be null or empty");
|
||||
}
|
||||
this.passwordField = password;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DerivedAesParametersBuilder iterations(final int iterations) {
|
||||
if (iterations <= 0) {
|
||||
throw new IllegalArgumentException("iterations must be positive");
|
||||
}
|
||||
this.iterationsField = iterations;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DerivedAesParametersBuilder mode(final AesMode mode) {
|
||||
this.modeField = Objects.requireNonNull(mode, "mode must not be null");
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DerivedAesParametersBuilder cipherType(final AesCipherType cipherType) {
|
||||
this.cipherTypeField = Objects.requireNonNull(cipherType, "cipherType must not be null");
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DerivedAesParametersBuilder withAad(final byte[] aad) {
|
||||
this.aadField = aad; // NOPMD
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SecretContent build(final boolean encrypt) {
|
||||
try {
|
||||
return new SecretDerivedAesParameters(passwordField, iterationsField, aadField, modeField,
|
||||
cipherTypeField, encrypt);
|
||||
} catch (IllegalArgumentException | InvalidKeySpecException e) {
|
||||
LOG.log(Level.WARNING, "Exception during build", e);
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
257
lib/src/main/java/zeroecho/builder/KEMAesParametersBuilder.java
Normal file
257
lib/src/main/java/zeroecho/builder/KEMAesParametersBuilder.java
Normal file
@@ -0,0 +1,257 @@
|
||||
/**
|
||||
* 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 zeroecho.builder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.Key;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Objects;
|
||||
|
||||
import zeroecho.data.processing.SecretKEMAesParameters;
|
||||
import zeroecho.util.KeySupport;
|
||||
import zeroecho.util.aes.AesCipherType;
|
||||
import zeroecho.util.aes.AesMode;
|
||||
import zeroecho.util.asymmetric.AsymmetricContext;
|
||||
import zeroecho.util.asymmetric.KEMAsymmetricContext;
|
||||
|
||||
/**
|
||||
* Builder for {@link SecretKEMAesParameters}, allowing construction from either
|
||||
* a {@link PublicKey} (for encryption) or a {@link PrivateKey} (for
|
||||
* decryption).
|
||||
*
|
||||
* <p>
|
||||
* This builder ensures that the provided key corresponds to a
|
||||
* {@link KEMAsymmetricContext}. If the key type is not supported by the KEM
|
||||
* infrastructure, the {@link #build(boolean)} method will fail.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Validation rules:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>For encryption mode ({@code encrypt = true}), the resulting
|
||||
* {@link KEMAsymmetricContext} must have a {@code null} extractor (meaning it
|
||||
* can only generate encapsulated keys, not decapsulate).</li>
|
||||
* <li>For decryption mode ({@code encrypt = false}), the resulting
|
||||
* {@link KEMAsymmetricContext} must have a {@code null} generator (meaning it
|
||||
* can only extract secrets, not generate them).</li>
|
||||
* </ul>
|
||||
*/
|
||||
public interface KEMAesParametersBuilder extends DataContentBuilder<SecretKEMAesParameters> {
|
||||
/**
|
||||
* Sets the asymmetric key used to build the KEM context.
|
||||
*
|
||||
* <p>
|
||||
* The provided key determines the operational mode:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>Public key → encryption mode.</li>
|
||||
* <li>Private key → decryption mode.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param key the public or private key; must not be {@code null}
|
||||
* @return this builder instance for method chaining
|
||||
* @throws NullPointerException if {@code key} is {@code null}
|
||||
*/
|
||||
KEMAesParametersBuilder withKey(Key key);
|
||||
|
||||
/**
|
||||
* Sets the AES mode.
|
||||
*
|
||||
* @param aesMode the AES mode (e.g., {@link AesMode#AES_128},
|
||||
* {@link AesMode#AES_256}); must not be {@code null}
|
||||
* @return this builder instance for method chaining
|
||||
* @throws NullPointerException if {@code aesMode} is {@code null}
|
||||
*/
|
||||
KEMAesParametersBuilder withAesMode(AesMode aesMode);
|
||||
|
||||
/**
|
||||
* Sets the AES cipher type.
|
||||
*
|
||||
* @param cipherType the cipher type (e.g., {@link AesCipherType#CBC},
|
||||
* {@link AesCipherType#GCM}); must not be {@code null}
|
||||
* @return this builder instance for method chaining
|
||||
* @throws NullPointerException if {@code cipherType} is {@code null}
|
||||
*/
|
||||
KEMAesParametersBuilder withCipherType(AesCipherType cipherType);
|
||||
|
||||
/**
|
||||
* Sets optional Additional Authenticated Data (AAD) for authenticated modes.
|
||||
*
|
||||
* @param aad the AAD bytes; may be {@code null}
|
||||
* @return this builder instance for method chaining
|
||||
*/
|
||||
KEMAesParametersBuilder withAAD(byte[] aad);
|
||||
|
||||
/**
|
||||
* Creates a new {@code KEMAesParametersBuilder} instance with default settings:
|
||||
* <ul>
|
||||
* <li>{@link AesMode#AES_256}</li>
|
||||
* <li>{@link AesCipherType#GCM}</li>
|
||||
* <li>empty AAD ({@code new byte[0]})</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return a new builder instance
|
||||
*/
|
||||
static KEMAesParametersBuilder builder() {
|
||||
return new DefaultKEMAesParametersBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of {@link KEMAesParametersBuilder}.
|
||||
*
|
||||
* <p>
|
||||
* Performs parameter validation and constructs a {@link SecretKEMAesParameters}
|
||||
* instance based on the configured key, AES mode, cipher type, and optional
|
||||
* AAD.
|
||||
* </p>
|
||||
*/
|
||||
final class DefaultKEMAesParametersBuilder implements KEMAesParametersBuilder {
|
||||
private Key key; // May be PublicKey or PrivateKey
|
||||
private AesMode aesMode = AesMode.AES_256;
|
||||
private AesCipherType cipherType = AesCipherType.GCM;
|
||||
private byte[] aad = {};
|
||||
|
||||
private DefaultKEMAesParametersBuilder() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public KEMAesParametersBuilder withKey(final Key key) {
|
||||
this.key = Objects.requireNonNull(key, "key must not be null");
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KEMAesParametersBuilder withAesMode(final AesMode aesMode) {
|
||||
this.aesMode = Objects.requireNonNull(aesMode, "aesMode must not be null");
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KEMAesParametersBuilder withCipherType(final AesCipherType cipherType) {
|
||||
this.cipherType = Objects.requireNonNull(cipherType, "cipherType must not be null");
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KEMAesParametersBuilder withAAD(final byte[] aad) {
|
||||
this.aad = aad; // NOPMD
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a {@link SecretKEMAesParameters} instance.
|
||||
*
|
||||
* <p>
|
||||
* The method determines whether encryption or decryption mode should be used
|
||||
* based on the provided key type:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>{@link java.security.PublicKey} → encryption</li>
|
||||
* <li>{@link java.security.PrivateKey} → decryption</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* Validation includes:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>Ensuring the key corresponds to a KEM-capable context.</li>
|
||||
* <li>Verifying generator/extractor presence depending on the mode.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param encrypt {@code true} for encryption mode; {@code false} for decryption
|
||||
* mode
|
||||
* @return a fully configured {@link SecretKEMAesParameters} instance
|
||||
* @throws IllegalArgumentException if the key type is invalid, if mode and key
|
||||
* mismatch, or if the underlying context
|
||||
* cannot be created
|
||||
* @throws NullPointerException if required fields are not set
|
||||
*/
|
||||
@Override
|
||||
public SecretKEMAesParameters build(final boolean encrypt) { // NOPMD
|
||||
Objects.requireNonNull(key, "Key must be set before building");
|
||||
Objects.requireNonNull(aesMode, "AES mode must be set before building");
|
||||
Objects.requireNonNull(cipherType, "Cipher type must be set before building");
|
||||
|
||||
try {
|
||||
AsymmetricContext ctx = switch (key) { // NOPMD
|
||||
case PublicKey pubk -> {
|
||||
if (!encrypt) {
|
||||
throw new IllegalArgumentException("Decrypt needs a private key");
|
||||
}
|
||||
|
||||
yield KeySupport.fromKey(pubk);
|
||||
}
|
||||
case PrivateKey privk -> {
|
||||
if (encrypt) {
|
||||
throw new IllegalArgumentException("Encrypt needs a public key");
|
||||
}
|
||||
|
||||
yield KeySupport.fromKey(privk);
|
||||
}
|
||||
default -> {
|
||||
throw new IllegalArgumentException("Provided key does not correspond to a KEM-capable context: "
|
||||
+ key.getClass().getName());
|
||||
}
|
||||
};
|
||||
|
||||
if (!(ctx instanceof KEMAsymmetricContext kemContext)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Provided key does not correspond to a KEM-capable context: " + key.getClass().getName());
|
||||
}
|
||||
|
||||
// Validate context capability - just to be sure - if it fails here,
|
||||
// KeySupport.fromKey is buggy
|
||||
if (encrypt) {
|
||||
if (kemContext.generator() == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"KEM generator is not defined; this key cannot be used for encryption.");
|
||||
}
|
||||
} else {
|
||||
if (kemContext.extractor() == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"KEM extractor is not defined; this key cannot be used for decryption.");
|
||||
}
|
||||
}
|
||||
|
||||
return new SecretKEMAesParameters(kemContext, aesMode, cipherType, aad);
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
* 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 zeroecho.builder;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import zeroecho.data.EncryptedContent;
|
||||
import zeroecho.data.processing.SecretMultiRecipientCryptor;
|
||||
|
||||
/**
|
||||
* A builder interface for creating instances of
|
||||
* {@link SecretMultiRecipientCryptor}, supporting either encryption with
|
||||
* multiple public keys or decryption with a private key.
|
||||
* <p>
|
||||
* This builder enables fluent and type-safe construction of multi-recipient
|
||||
* cryptographic content handlers. It enforces mutual exclusivity between
|
||||
* encryption and decryption modes: you may either add one or more recipients
|
||||
* (and optionally decoys) for encryption, or specify a private key for
|
||||
* decryption, but not both.
|
||||
*
|
||||
* <h2>Usage Examples</h2>
|
||||
*
|
||||
* <h3>Encryption with recipients and decoys:</h3> <pre>{@code
|
||||
* EncryptedContent cryptor = MultiRecipientCryptorBuilder.builder()
|
||||
* .addRecipient(publicKey1)
|
||||
* .addRecipient(publicKey2)
|
||||
* .addDecoy(decoyKey1)
|
||||
* .addDecoy(decoyKey2)
|
||||
* .build(true); // true = encryption mode
|
||||
* }</pre>
|
||||
*
|
||||
* <h3>Decryption with private key:</h3> <pre>{@code
|
||||
* EncryptedContent cryptor = MultiRecipientCryptorBuilder.builder()
|
||||
* .privateKey(myPrivateKey)
|
||||
* .build(false); // false = decryption mode
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* Attempting to mix public recipients/decoys and a private key within the same
|
||||
* builder instance will result in an {@link IllegalStateException}.
|
||||
*
|
||||
* @see SecretMultiRecipientCryptor
|
||||
* @see EncryptedContent
|
||||
*/
|
||||
public interface MultiRecipientCryptorBuilder extends DataContentBuilder<EncryptedContent> {
|
||||
|
||||
/**
|
||||
* Adds a collection of public key recipients for encryption mode. If a private
|
||||
* key has already been configured, this call will throw an
|
||||
* {@link IllegalStateException}.
|
||||
*
|
||||
* @param recipients a non-null collection of public keys
|
||||
* @return this builder instance
|
||||
* @throws NullPointerException if {@code recipients} is null
|
||||
* @throws IllegalStateException if the builder is already configured with a
|
||||
* private key
|
||||
*/
|
||||
MultiRecipientCryptorBuilder addRecipients(Collection<PublicKey> recipients);
|
||||
|
||||
/**
|
||||
* Adds a single public key recipient for encryption mode. Multiple calls are
|
||||
* cumulative. If a private key has already been configured, this call will
|
||||
* throw an {@link IllegalStateException}.
|
||||
*
|
||||
* @param recipient a non-null public key
|
||||
* @return this builder instance
|
||||
* @throws NullPointerException if {@code recipient} is null
|
||||
* @throws IllegalStateException if the builder is already configured with a
|
||||
* private key
|
||||
*/
|
||||
MultiRecipientCryptorBuilder addRecipient(PublicKey recipient);
|
||||
|
||||
/**
|
||||
* Configures the builder for decryption using the specified private key. Once
|
||||
* set, any previously added public key recipients will be ignored. This method
|
||||
* cannot be used if recipients were already added.
|
||||
*
|
||||
* @param privateKey a non-null private key
|
||||
* @return this builder instance
|
||||
* @throws NullPointerException if {@code privateKey} is null
|
||||
* @throws IllegalStateException if recipients have already been configured
|
||||
*/
|
||||
MultiRecipientCryptorBuilder privateKey(PrivateKey privateKey);
|
||||
|
||||
/**
|
||||
* Adds a collection of decoy public keys for encryption mode. These keys will
|
||||
* be indistinguishable from actual recipients but are not able to decrypt the
|
||||
* message. Useful for obfuscating true recipients.
|
||||
*
|
||||
* @param decoys a non-null collection of public keys
|
||||
* @return this builder instance
|
||||
* @throws NullPointerException if {@code decoys} is null
|
||||
* @throws IllegalStateException if the builder is already configured with a
|
||||
* private key
|
||||
*/
|
||||
MultiRecipientCryptorBuilder addDecoys(Collection<PublicKey> decoys);
|
||||
|
||||
/**
|
||||
* Adds a single decoy public key for encryption mode. Multiple calls are
|
||||
* cumulative. Decoys do not participate in decryption.
|
||||
*
|
||||
* @param decoy a non-null public key
|
||||
* @return this builder instance
|
||||
* @throws NullPointerException if {@code decoy} is null
|
||||
* @throws IllegalStateException if the builder is already configured with a
|
||||
* private key
|
||||
*/
|
||||
MultiRecipientCryptorBuilder addDecoy(PublicKey decoy);
|
||||
|
||||
/**
|
||||
* Creates a new builder instance.
|
||||
*
|
||||
* @return a fresh {@code MultiRecipientCryptorBuilder}
|
||||
*/
|
||||
static MultiRecipientCryptorBuilder builder() {
|
||||
return new DefaultMultiRecipientCryptorBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of {@link MultiRecipientCryptorBuilder}.
|
||||
*/
|
||||
final class DefaultMultiRecipientCryptorBuilder implements MultiRecipientCryptorBuilder {
|
||||
private final Set<PublicKey> allRecipients = new HashSet<>();
|
||||
private final Set<PublicKey> allDecoys = new HashSet<>();
|
||||
private PrivateKey privateKeyField;
|
||||
|
||||
private DefaultMultiRecipientCryptorBuilder() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiRecipientCryptorBuilder addRecipients(final Collection<PublicKey> recipients) {
|
||||
if (privateKeyField == null) {
|
||||
Objects.requireNonNull(recipients, "recipients must not be null");
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
"Both public keys and the private key cannot be configured for the cryptor.");
|
||||
}
|
||||
|
||||
allRecipients.addAll(recipients);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiRecipientCryptorBuilder addRecipient(final PublicKey recipient) {
|
||||
if (privateKeyField == null) {
|
||||
Objects.requireNonNull(recipient, "recipient must not be null");
|
||||
} else {
|
||||
throw new IllegalStateException(
|
||||
"Both public keys and the private key cannot be configured for the cryptor.");
|
||||
}
|
||||
|
||||
allRecipients.add(recipient);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiRecipientCryptorBuilder privateKey(final PrivateKey privateKey) {
|
||||
if (!allRecipients.isEmpty() || !allDecoys.isEmpty()) {
|
||||
throw new IllegalStateException(
|
||||
"Both public keys and the private key cannot be configured for the cryptor.");
|
||||
}
|
||||
Objects.requireNonNull(privateKey, "privateKey must not be null");
|
||||
this.privateKeyField = privateKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiRecipientCryptorBuilder addDecoys(final Collection<PublicKey> decoys) {
|
||||
if (privateKeyField != null) {
|
||||
throw new IllegalStateException("Cannot add decoys when a private key is configured.");
|
||||
}
|
||||
Objects.requireNonNull(decoys, "decoys must not be null");
|
||||
allDecoys.addAll(decoys);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiRecipientCryptorBuilder addDecoy(final PublicKey decoy) {
|
||||
if (privateKeyField != null) {
|
||||
throw new IllegalStateException("Cannot add decoy when a private key is configured.");
|
||||
}
|
||||
Objects.requireNonNull(decoy, "decoy must not be null");
|
||||
allDecoys.add(decoy);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EncryptedContent build(final boolean encrypt) {
|
||||
if (privateKeyField != null) {
|
||||
if (encrypt) {
|
||||
throw new IllegalStateException("You requested encryption with a private key.");
|
||||
}
|
||||
return new SecretMultiRecipientCryptor(privateKeyField);
|
||||
}
|
||||
|
||||
if (allRecipients.isEmpty() && allDecoys.isEmpty()) {
|
||||
throw new IllegalStateException("No recipients or decoys configured for encryption.");
|
||||
}
|
||||
|
||||
if (!encrypt) {
|
||||
throw new IllegalStateException("You requested decryption with public keys.");
|
||||
}
|
||||
|
||||
PublicKey[] recipients = allRecipients.toArray(new PublicKey[0]);
|
||||
PublicKey[] decoys = allDecoys.toArray(new PublicKey[0]);
|
||||
|
||||
return new SecretMultiRecipientCryptor(recipients, decoys);
|
||||
}
|
||||
}
|
||||
}
|
||||
110
lib/src/main/java/zeroecho/builder/PlainBytesBuilder.java
Normal file
110
lib/src/main/java/zeroecho/builder/PlainBytesBuilder.java
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* 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 zeroecho.builder;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import zeroecho.data.PlainContent;
|
||||
import zeroecho.data.processing.PlainBytes;
|
||||
|
||||
/**
|
||||
* Builder interface for constructing {@link PlainBytes} instances that
|
||||
* encapsulate unencrypted byte array content.
|
||||
* <p>
|
||||
* This builder allows specifying a raw byte array to be wrapped as
|
||||
* {@code PlainContent}, which can be used as input for cryptographic operations
|
||||
* or as standalone plaintext data.
|
||||
* <p>
|
||||
* The {@link #build(boolean)} method accepts an {@code encrypt} flag for API
|
||||
* consistency, but the flag has no effect for plain byte content.
|
||||
*
|
||||
* <p>
|
||||
* <strong>Usage example:</strong>
|
||||
*
|
||||
* <pre>{@code
|
||||
* PlainContent content = PlainBytesBuilder.builder()
|
||||
* .bytes(new byte[] { 1, 2, 3, 4 })
|
||||
* .build(false); // encrypt flag is ignored for PlainBytes
|
||||
* }</pre>
|
||||
*
|
||||
* @see PlainBytes
|
||||
*/
|
||||
public interface PlainBytesBuilder extends DataContentBuilder<PlainContent> {
|
||||
|
||||
/**
|
||||
* Sets the byte array content to be wrapped by {@link PlainBytes}.
|
||||
*
|
||||
* @param bytes the byte array; must not be null
|
||||
* @return this builder instance for method chaining
|
||||
* @throws NullPointerException if {@code bytes} is null
|
||||
*/
|
||||
PlainBytesBuilder bytes(byte[] bytes);
|
||||
|
||||
/**
|
||||
* Creates a new instance of the default builder implementation.
|
||||
*
|
||||
* @return a new {@code PlainBytesBuilder}
|
||||
*/
|
||||
static PlainBytesBuilder builder() {
|
||||
return new DefaultPlainBytesBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of the {@link PlainBytesBuilder} interface.
|
||||
* <p>
|
||||
* Builds a {@link PlainBytes} instance wrapping the specified byte array.
|
||||
* </p>
|
||||
*/
|
||||
final class DefaultPlainBytesBuilder implements PlainBytesBuilder {
|
||||
private byte[] bytesField;
|
||||
|
||||
private DefaultPlainBytesBuilder() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlainBytesBuilder bytes(final byte[] bytes) {
|
||||
this.bytesField = Objects.requireNonNull(bytes, "bytes must not be null");
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlainContent build(final boolean encrypt) {
|
||||
if (bytesField == null) {
|
||||
throw new IllegalStateException("bytes must be set before building");
|
||||
}
|
||||
return new PlainBytes(bytesField);
|
||||
}
|
||||
}
|
||||
}
|
||||
105
lib/src/main/java/zeroecho/builder/PlainFileBuilder.java
Normal file
105
lib/src/main/java/zeroecho/builder/PlainFileBuilder.java
Normal file
@@ -0,0 +1,105 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.builder;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Objects;
|
||||
|
||||
import zeroecho.data.PlainContent;
|
||||
import zeroecho.data.processing.PlainFile;
|
||||
|
||||
/**
|
||||
* Builder interface for constructing {@link PlainContent} instances that
|
||||
* represent unencrypted file-based content sourced from a {@link URL}.
|
||||
* <p>
|
||||
* This builder allows specifying the source URL of a file to be used as plain
|
||||
* (non-encrypted) content. It is typically used as input for cryptographic
|
||||
* operations or for representing raw file data.
|
||||
* <p>
|
||||
* The {@link #build(boolean)} method accepts an {@code encrypt} flag to comply
|
||||
* with the {@link DataContentBuilder} interface; however, this flag is ignored
|
||||
* for plain content types.
|
||||
* <p>
|
||||
* <strong>Usage example:</strong>
|
||||
*
|
||||
* <pre>{@code
|
||||
* PlainContent plainFile = PlainFileBuilder.builder()
|
||||
* .url(new URL("file:///path/to/file.txt"))
|
||||
* .build(true); // encrypt flag is ignored for PlainFile
|
||||
* }</pre>
|
||||
*
|
||||
* @see PlainFile
|
||||
*/
|
||||
public interface PlainFileBuilder extends DataContentBuilder<PlainContent> {
|
||||
/**
|
||||
* Sets the URL of the file to be used as the plain content source.
|
||||
*
|
||||
* @param url the URL of the file; must not be {@code null}
|
||||
* @return this builder instance
|
||||
*/
|
||||
PlainFileBuilder url(URL url);
|
||||
|
||||
/**
|
||||
* Creates a new instance of the default builder implementation.
|
||||
*
|
||||
* @return a new {@code PlainFileBuilder}
|
||||
*/
|
||||
static PlainFileBuilder builder() {
|
||||
return new DefaultPlainFileBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of the {@link PlainFileBuilder} interface.
|
||||
* <p>
|
||||
* Builds a {@link PlainFile} instance using the specified URL.
|
||||
*/
|
||||
final class DefaultPlainFileBuilder implements PlainFileBuilder {
|
||||
private URL urlField;
|
||||
|
||||
private DefaultPlainFileBuilder() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlainFileBuilder url(final URL url) {
|
||||
this.urlField = Objects.requireNonNull(url);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlainContent build(final boolean encrypt) {
|
||||
return new PlainFile(urlField);
|
||||
}
|
||||
}
|
||||
}
|
||||
101
lib/src/main/java/zeroecho/builder/PlainStringBuilder.java
Normal file
101
lib/src/main/java/zeroecho/builder/PlainStringBuilder.java
Normal file
@@ -0,0 +1,101 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.builder;
|
||||
|
||||
import zeroecho.data.processing.PlainString;
|
||||
|
||||
/**
|
||||
* Builder interface for constructing {@link PlainString} instances, which
|
||||
* represent unencrypted textual content.
|
||||
* <p>
|
||||
* This builder supports specifying a string value to be encapsulated and
|
||||
* optionally used in cryptographic processing pipelines, although the value
|
||||
* itself remains unencrypted.
|
||||
* <p>
|
||||
* The {@link #build(boolean)} method accepts an {@code encrypt} flag to comply
|
||||
* with the {@link DataContentBuilder} interface. However, this flag is ignored
|
||||
* because {@code PlainString} represents unencrypted data.
|
||||
* <p>
|
||||
* Usage example:
|
||||
*
|
||||
* <pre>{@code
|
||||
* PlainString plainText = PlainStringBuilder.builder()
|
||||
* .value("Hello, World!")
|
||||
* .build(true); // encrypt flag is ignored for PlainString
|
||||
* }</pre>
|
||||
*/
|
||||
public interface PlainStringBuilder extends DataContentBuilder<PlainString> {
|
||||
|
||||
/**
|
||||
* Creates a new instance of the default builder implementation.
|
||||
*
|
||||
* @return a new {@code PlainStringBuilder}
|
||||
*/
|
||||
static PlainStringBuilder builder() {
|
||||
return new DefaultPlainStringBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the string value that the {@link PlainString} instance will contain.
|
||||
*
|
||||
* @param value the string value; may be {@code null} or empty depending on
|
||||
* usage
|
||||
* @return this builder instance
|
||||
*/
|
||||
PlainStringBuilder value(String value);
|
||||
|
||||
/**
|
||||
* Default implementation of the {@link PlainStringBuilder} interface.
|
||||
* <p>
|
||||
* Builds a {@link PlainString} instance using the specified string value.
|
||||
*/
|
||||
final class DefaultPlainStringBuilder implements PlainStringBuilder {
|
||||
private String valueField;
|
||||
|
||||
private DefaultPlainStringBuilder() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlainStringBuilder value(final String value) {
|
||||
this.valueField = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlainString build(final boolean encrypt) {
|
||||
return new PlainString(valueField);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.builder;
|
||||
|
||||
import zeroecho.data.DataContent;
|
||||
import zeroecho.data.PlainContent;
|
||||
import zeroecho.data.processing.ReclassifiedPlain;
|
||||
|
||||
/**
|
||||
* Builder interface for constructing {@link ReclassifiedPlain} instances, which
|
||||
* wrap an existing {@link DataContent} stream and reclassify it as plain
|
||||
* content without modifying the underlying data.
|
||||
* <p>
|
||||
* Typically used to repurpose an encrypted or transformed stream back into a
|
||||
* plain data stream for further processing.
|
||||
* <p>
|
||||
* Usage example:
|
||||
*
|
||||
* <pre>{@code
|
||||
* PlainContent reclassified = ReclassifiedPlainBuilder.builder().build();
|
||||
* }</pre>
|
||||
*/
|
||||
@Deprecated
|
||||
public interface ReclassifiedPlainBuilder extends DataContentBuilder<PlainContent> { // NOPMD
|
||||
/**
|
||||
* Creates a new instance of the default {@code ReclassifiedPlainBuilder}.
|
||||
*
|
||||
* @return a new builder instance
|
||||
*/
|
||||
static ReclassifiedPlainBuilder builder() {
|
||||
return new DefaultReclassifiedPlainBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of {@link ReclassifiedPlainBuilder}.
|
||||
*/
|
||||
final class DefaultReclassifiedPlainBuilder implements ReclassifiedPlainBuilder {
|
||||
private DefaultReclassifiedPlainBuilder() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlainContent build(final boolean encrypt) {
|
||||
return new ReclassifiedPlain();
|
||||
}
|
||||
}
|
||||
}
|
||||
89
lib/src/main/java/zeroecho/builder/package-info.java
Normal file
89
lib/src/main/java/zeroecho/builder/package-info.java
Normal file
@@ -0,0 +1,89 @@
|
||||
/*******************************************************************************
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
/**
|
||||
* Provides builder interfaces and implementations for constructing various
|
||||
* {@link zeroecho.data.DataContent} types and assembling content processing
|
||||
* pipelines, including encryption and decryption workflows.
|
||||
* <p>
|
||||
* This package offers a modular and extensible approach to build
|
||||
* {@code DataContent} chains in a fluent style. It currently supports:
|
||||
* <ul>
|
||||
* <li>Plain content builders such as {@link PlainStringBuilder} and
|
||||
* {@link PlainFileBuilder}</li>
|
||||
* <li>Symmetric encryption parameter builders, including
|
||||
* {@link DerivedAesParametersBuilder} and {@link AesBuilder}</li>
|
||||
* <li>Utility builders like {@link ReclassifiedPlainBuilder} for stream
|
||||
* reclassification</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* The builders can be combined to form flexible data processing pipelines. Each
|
||||
* builder produces an instance of {@code DataContent} or its subtype, which can
|
||||
* be chained by setting the input stream of the next stage.
|
||||
* <p>
|
||||
* <b>Example usage:</b>
|
||||
*
|
||||
* <pre>{@code
|
||||
* DataContent output = DataContentChainBuilder.builder()
|
||||
* // Start from a plain string input
|
||||
* .add(PlainStringBuilder.builder().value("Example plaintext"))
|
||||
* // Encrypt the input
|
||||
* .add(AesBuilder.builder().encrypt())
|
||||
* // Apply derived AES parameters with a password
|
||||
* .add(DerivedAesParametersBuilder.builder().password("secretPassword").iterations(10000).mode(AesMode.AES_256)
|
||||
* .cipherType(AesCipherType.CBC))
|
||||
* // Reclassify encrypted stream as plain for further processing
|
||||
* .add(ReclassifiedPlainBuilder.builder())
|
||||
* // Decrypt using the same password-derived parameters
|
||||
* .add(DerivedAesParametersBuilder.builder().password("secretPassword").iterations(10000).mode(AesMode.AES_256)
|
||||
* .cipherType(AesCipherType.CBC))
|
||||
* .add(AesBuilder.builder().decrypt())
|
||||
* // Build the final data content chain
|
||||
* .build();
|
||||
* String decrypted = output.toText();
|
||||
* }</pre>
|
||||
* <p>
|
||||
* Future extensions will include additional cryptographic parameter builders
|
||||
* (e.g., for asymmetric algorithms), and support for other data content
|
||||
* transformations.
|
||||
*
|
||||
* @author Leo Galambos
|
||||
* @see zeroecho.data.DataContent
|
||||
* @see PlainStringBuilder
|
||||
* @see PlainFileBuilder
|
||||
* @see DerivedAesParametersBuilder
|
||||
* @see AesBuilder
|
||||
* @see ReclassifiedPlainBuilder
|
||||
*/
|
||||
package zeroecho.builder;
|
||||
178
lib/src/main/java/zeroecho/covert/TextualCodec.java
Normal file
178
lib/src/main/java/zeroecho/covert/TextualCodec.java
Normal file
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* 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 zeroecho.covert;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Map;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.Queue;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import zeroecho.util.RandomSupport;
|
||||
|
||||
/**
|
||||
* A utility class for generating pseudo-random textual content based on
|
||||
* predefined character frequency distributions.
|
||||
* <p>
|
||||
* The {@code TextualCodec} class contains a nested {@link Generator} class that
|
||||
* can be configured with a set of character frequencies (e.g., English letter
|
||||
* frequencies) to produce text that mimics the statistical distribution of the
|
||||
* given language or character set.
|
||||
*/
|
||||
public class TextualCodec { // NOPMD
|
||||
/**
|
||||
* Private constructor to prevent instantiation of {@code TextualCodec}.
|
||||
* <p>
|
||||
* This class is intended to be used as a utility container for static members
|
||||
* and should not be instantiated.
|
||||
*/
|
||||
private TextualCodec() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates characters or strings using a frequency-based distribution.
|
||||
* <p>
|
||||
* This generator uses a cumulative frequency table internally to map random
|
||||
* numbers to characters, enabling the creation of realistic-looking text that
|
||||
* follows the given character frequency distribution. The generator also avoids
|
||||
* consecutive duplicate characters by employing a simple queuing mechanism.
|
||||
*/
|
||||
public static class Generator {
|
||||
/**
|
||||
* Internal map representing the cumulative frequency ranges mapped to
|
||||
* characters.
|
||||
*/
|
||||
private final NavigableMap<Double, Character> ranges = new TreeMap<>();
|
||||
/**
|
||||
* The maximum value of the cumulative frequency range.
|
||||
*/
|
||||
private final double maxRange;
|
||||
/**
|
||||
* A predefined English character frequency distribution including the space
|
||||
* character, based on typical usage in English text.
|
||||
*/
|
||||
public final static Map<Character, Double> ENGLISH = Map.ofEntries(Map.entry('a', 8.2), Map.entry('b', 1.5),
|
||||
Map.entry('c', 2.8), Map.entry('d', 4.3), Map.entry('e', 12.7), Map.entry('f', 2.2),
|
||||
Map.entry('g', 2.0), Map.entry('h', 6.1), Map.entry('i', 7.0), Map.entry('j', 0.15),
|
||||
Map.entry('k', 0.77), Map.entry('l', 4.0), Map.entry('m', 2.4), Map.entry('n', 6.7),
|
||||
Map.entry('o', 7.5), Map.entry('p', 1.9), Map.entry('q', 0.095), Map.entry('r', 6.0),
|
||||
Map.entry('s', 6.3), Map.entry('t', 9.1), Map.entry('u', 2.8), Map.entry('v', 0.98),
|
||||
Map.entry('w', 2.4), Map.entry('x', 0.15), Map.entry('y', 2.0), Map.entry('z', 0.074),
|
||||
Map.entry(' ', 25.4));
|
||||
/**
|
||||
* A default generator using the {@link #ENGLISH} frequency distribution.
|
||||
*/
|
||||
public final static Generator EN = new Generator(ENGLISH);
|
||||
|
||||
private Character lastChar = '~';
|
||||
private final Queue<Character> backlog = new ArrayDeque<>();
|
||||
|
||||
/**
|
||||
* Constructs a new {@code Generator} with the specified character frequency
|
||||
* distribution.
|
||||
*
|
||||
* @param frequencies a map of characters to their relative frequencies (must be
|
||||
* non-negative)
|
||||
*/
|
||||
public Generator(Map<Character, Double> frequencies) {
|
||||
double cumulative = 0.0;
|
||||
for (Map.Entry<Character, Double> entry : frequencies.entrySet()) {
|
||||
double freq = entry.getValue();
|
||||
if (freq <= 0) {
|
||||
continue;
|
||||
}
|
||||
ranges.put(cumulative, entry.getKey());
|
||||
cumulative = cumulative + freq;
|
||||
}
|
||||
|
||||
maxRange = cumulative;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a string of the specified length using the configured character
|
||||
* frequency distribution. Consecutive duplicate characters are avoided when
|
||||
* possible.
|
||||
*
|
||||
* @param length the number of characters to generate
|
||||
* @return a randomly generated string
|
||||
*/
|
||||
public String getText(int length) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
|
||||
while (length-- > 0) { // NOPMD
|
||||
sb.append(getChar());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next randomly generated character, avoiding consecutive
|
||||
* duplicates when possible.
|
||||
*
|
||||
* @return the next character in the generated sequence
|
||||
*/
|
||||
public char getChar() {
|
||||
if (backlog.isEmpty() || lastChar.equals(backlog.peek())) {
|
||||
Character next = getChar(RandomSupport.getRandom().nextDouble(maxRange));
|
||||
while (lastChar.equals(next)) {
|
||||
backlog.add(next);
|
||||
next = getChar(RandomSupport.getRandom().nextDouble(maxRange));
|
||||
}
|
||||
lastChar = next;
|
||||
return lastChar;
|
||||
}
|
||||
|
||||
return lastChar = backlog.poll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a character based on the provided value in the frequency range.
|
||||
*
|
||||
* @param value a value between 0 (inclusive) and {@code maxRange} (exclusive)
|
||||
* @return the corresponding character for the specified value
|
||||
* @throws IllegalArgumentException if the value is out of range
|
||||
*/
|
||||
public char getChar(double value) {
|
||||
if (value < 0.0 || value >= maxRange) {
|
||||
throw new IllegalArgumentException("Value must be in [0.0, " + maxRange + ")");
|
||||
}
|
||||
|
||||
Map.Entry<Double, Character> entry = ranges.floorEntry(value);
|
||||
return (entry == null) ? ranges.firstEntry().getValue() : entry.getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
166
lib/src/main/java/zeroecho/covert/jpeg/JpegExifEmbedder.java
Normal file
166
lib/src/main/java/zeroecho/covert/jpeg/JpegExifEmbedder.java
Normal file
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* 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 zeroecho.covert.jpeg;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.imaging.Imaging;
|
||||
import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata;
|
||||
import org.apache.commons.imaging.formats.jpeg.exif.ExifRewriter;
|
||||
import org.apache.commons.imaging.formats.tiff.fieldtypes.AbstractFieldType;
|
||||
import org.apache.commons.imaging.formats.tiff.write.TiffOutputDirectory;
|
||||
import org.apache.commons.imaging.formats.tiff.write.TiffOutputField;
|
||||
import org.apache.commons.imaging.formats.tiff.write.TiffOutputSet;
|
||||
|
||||
/**
|
||||
* Embeds binary payloads into the EXIF metadata of a JPEG file using predefined
|
||||
* {@link SlotType}s.
|
||||
* <p>
|
||||
* This class enables lossless modification of JPEG images by injecting custom
|
||||
* data into specific EXIF fields. These fields are typically unused or
|
||||
* repurposable for application-specific steganography or metadata tagging.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The embedding process respects total capacity constraints and allows per-slot
|
||||
* overrides to fine-tune the number of bytes stored in each {@link SlotType}.
|
||||
* </p>
|
||||
*/
|
||||
public class JpegExifEmbedder {
|
||||
/**
|
||||
* Optional overrides for the default capacity of each {@link SlotType}. This
|
||||
* allows finer control over how the payload is partitioned and embedded.
|
||||
*/
|
||||
private final Map<SlotType, Integer> capacityOverrides = new EnumMap<>(SlotType.class); // NOPMD
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@code JpegExifEmbedder}.
|
||||
* <p>
|
||||
* This default constructor initializes the embedder without any specific
|
||||
* configuration. Further setup may be required before using it to embed EXIF
|
||||
* metadata into JPEG images.
|
||||
*/
|
||||
public JpegExifEmbedder() { // NOPMD
|
||||
// empty
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the default capacity for a specific EXIF {@link SlotType}.
|
||||
*
|
||||
* @param slot the EXIF slot whose capacity should be overridden
|
||||
* @param bytes the new capacity in bytes for this slot
|
||||
*/
|
||||
public void overrideCapacity(SlotType slot, int bytes) {
|
||||
capacityOverrides.put(slot, bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Embeds the given payload into the EXIF metadata of a JPEG file and writes the
|
||||
* result to the provided output stream.
|
||||
* <p>
|
||||
* The payload is split across the available EXIF {@link SlotType}s. The
|
||||
* embedding respects the total capacity defined either by defaults or the
|
||||
* overridden values.
|
||||
* </p>
|
||||
*
|
||||
* @param jpegPath the path to the source JPEG file
|
||||
* @param payloadInput the input stream containing the binary payload to embed
|
||||
* @param jpegOutput the output stream to which the modified JPEG is written
|
||||
* @throws IOException if file access or modification fails
|
||||
* @throws IllegalArgumentException if the payload exceeds the total EXIF
|
||||
* capacity
|
||||
*/
|
||||
public void embed(Path jpegPath, InputStream payloadInput, OutputStream jpegOutput) throws IOException {
|
||||
byte[] jpegBytes = Files.readAllBytes(jpegPath);
|
||||
byte[] payload = payloadInput.readAllBytes();
|
||||
|
||||
int totalCapacity = 0;
|
||||
for (SlotType slot : SlotType.values()) {
|
||||
totalCapacity += capacityOverrides.getOrDefault(slot, slot.defaultCapacity);
|
||||
}
|
||||
|
||||
if (payload.length > totalCapacity) {
|
||||
throw new IllegalArgumentException("Payload too large. Max capacity: " + totalCapacity + " bytes.");
|
||||
}
|
||||
|
||||
Map<SlotType, byte[]> slotMap = splitPayload(payload);
|
||||
|
||||
JpegImageMetadata jpegMetadata = (JpegImageMetadata) Imaging.getMetadata(jpegBytes);
|
||||
TiffOutputSet outputSet = (jpegMetadata != null && jpegMetadata.getExif() != null)
|
||||
? jpegMetadata.getExif().getOutputSet()
|
||||
: new TiffOutputSet();
|
||||
|
||||
TiffOutputDirectory exifDirectory = outputSet.getOrCreateExifDirectory();
|
||||
|
||||
for (Map.Entry<SlotType, byte[]> entry : slotMap.entrySet()) {
|
||||
SlotType slot = entry.getKey();
|
||||
byte[] data = entry.getValue();
|
||||
exifDirectory.removeField(slot.tagInfo);
|
||||
exifDirectory.add(new TiffOutputField(slot.tagInfo, AbstractFieldType.BYTE, data.length, data)); // NOPMD
|
||||
}
|
||||
|
||||
try (ByteArrayInputStream jpegInputStream = new ByteArrayInputStream(jpegBytes)) {
|
||||
new ExifRewriter().updateExifMetadataLossless(jpegInputStream, jpegOutput, outputSet);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<SlotType, byte[]> splitPayload(byte[] payload) {
|
||||
Map<SlotType, byte[]> result = new LinkedHashMap<>(); // NOPMD
|
||||
int offset = 0;
|
||||
|
||||
for (SlotType slot : SlotType.values()) {
|
||||
int capacity = capacityOverrides.getOrDefault(slot, slot.defaultCapacity);
|
||||
if (offset > payload.length) {
|
||||
break;
|
||||
}
|
||||
|
||||
int chunkSize = Math.min(capacity, payload.length - offset);
|
||||
byte[] chunk = Arrays.copyOfRange(payload, offset, offset + chunkSize);
|
||||
result.put(slot, chunk);
|
||||
offset += chunkSize;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
140
lib/src/main/java/zeroecho/covert/jpeg/JpegExifExtractor.java
Normal file
140
lib/src/main/java/zeroecho/covert/jpeg/JpegExifExtractor.java
Normal file
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* 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 zeroecho.covert.jpeg;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.imaging.Imaging;
|
||||
import org.apache.commons.imaging.formats.jpeg.JpegImageMetadata;
|
||||
import org.apache.commons.imaging.formats.tiff.TiffField;
|
||||
|
||||
/**
|
||||
* Extracts embedded binary payloads from the EXIF metadata of a JPEG image.
|
||||
* <p>
|
||||
* This class is designed to reverse the embedding process performed by
|
||||
* {@code JpegExifEmbedder}. It reads a JPEG file, locates specific EXIF fields
|
||||
* designated for data storage (as defined by {@link SlotType}), and
|
||||
* reconstructs the original payload into a binary stream.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The extraction respects custom per-slot capacity overrides, ensuring that
|
||||
* embedded payloads that don't fully utilize the maximum slot size can be
|
||||
* detected by an early termination during read.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This class assumes that the EXIF fields were populated sequentially and that
|
||||
* the payload ends when a field is smaller than its declared or default
|
||||
* capacity.
|
||||
* </p>
|
||||
*/
|
||||
public class JpegExifExtractor {
|
||||
/**
|
||||
* Optional overrides for the capacity of each {@link SlotType}. These can be
|
||||
* used to control how much data is expected from each slot during extraction.
|
||||
*/
|
||||
private final Map<SlotType, Integer> capacityOverrides = new EnumMap<>(SlotType.class); // NOPMD
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@code JpegExifExtractor}.
|
||||
* <p>
|
||||
* This default constructor creates an extractor that can be used to read and
|
||||
* retrieve EXIF metadata from JPEG image files.
|
||||
*/
|
||||
public JpegExifExtractor() { // NOPMD
|
||||
// empty
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a custom capacity for the given slot. This allows the extractor to
|
||||
* determine how much data to expect from each EXIF field.
|
||||
*
|
||||
* @param slot the slot whose capacity is being overridden
|
||||
* @param bytes the maximum number of bytes expected from this slot
|
||||
*/
|
||||
public void overrideCapacity(SlotType slot, int bytes) {
|
||||
capacityOverrides.put(slot, bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts an embedded binary payload from the EXIF fields of the specified
|
||||
* JPEG image.
|
||||
* <p>
|
||||
* This method reads the JPEG file, parses its EXIF metadata, and collects
|
||||
* binary data from all configured {@link SlotType} entries in the order they
|
||||
* are defined. If a field contains fewer bytes than its capacity (default or
|
||||
* overridden), extraction is stopped early, assuming the end of the payload has
|
||||
* been reached.
|
||||
* </p>
|
||||
*
|
||||
* @param jpegPath the path to the JPEG file containing embedded data
|
||||
* @param payloadOutput the output stream to which the extracted binary data
|
||||
* will be written
|
||||
* @throws IOException if an I/O error occurs during file reading
|
||||
* or writing
|
||||
* @throws IllegalArgumentException if the JPEG file does not contain valid EXIF
|
||||
* metadata
|
||||
*/
|
||||
public void extract(Path jpegPath, OutputStream payloadOutput) throws IOException {
|
||||
byte[] jpegBytes = Files.readAllBytes(jpegPath);
|
||||
JpegImageMetadata jpegMetadata = (JpegImageMetadata) Imaging.getMetadata(jpegBytes);
|
||||
|
||||
if (jpegMetadata == null || jpegMetadata.getExif() == null) {
|
||||
throw new IllegalArgumentException("No EXIF metadata found.");
|
||||
}
|
||||
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
|
||||
for (SlotType slot : SlotType.values()) {
|
||||
TiffField field = jpegMetadata.findExifValue(slot.tagInfo);
|
||||
if (field != null && field.getByteArrayValue() != null) {
|
||||
final byte[] data = field.getByteArrayValue();
|
||||
buffer.write(data);
|
||||
if (data.length < capacityOverrides.getOrDefault(slot, slot.defaultCapacity)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
payloadOutput.write(buffer.toByteArray());
|
||||
}
|
||||
}
|
||||
93
lib/src/main/java/zeroecho/covert/jpeg/SlotType.java
Normal file
93
lib/src/main/java/zeroecho/covert/jpeg/SlotType.java
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 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 zeroecho.covert.jpeg;
|
||||
|
||||
import org.apache.commons.imaging.formats.tiff.constants.ExifTagConstants;
|
||||
import org.apache.commons.imaging.formats.tiff.taginfos.TagInfo;
|
||||
|
||||
/**
|
||||
* Represents predefined EXIF metadata slots that can be used for embedding or
|
||||
* extracting textual data within a JPEG image's metadata.
|
||||
* <p>
|
||||
* Each slot is associated with a specific {@link TagInfo} EXIF tag and a
|
||||
* default capacity (in bytes), which can be used to determine how much data can
|
||||
* be stored in that slot.
|
||||
*/
|
||||
public enum SlotType {
|
||||
|
||||
/**
|
||||
* EXIF tag used for user comments. Commonly used for storing textual
|
||||
* annotations.
|
||||
*/
|
||||
USER_COMMENT(ExifTagConstants.EXIF_TAG_USER_COMMENT, 4096),
|
||||
|
||||
/**
|
||||
* EXIF tag typically used by camera manufacturers to store proprietary data.
|
||||
*/
|
||||
MAKER_NOTE(ExifTagConstants.EXIF_TAG_MAKER_NOTE, 4096),
|
||||
|
||||
/**
|
||||
* EXIF tag indicating the version of the EXIF specification used.
|
||||
*/
|
||||
EXIF_VERSION(ExifTagConstants.EXIF_TAG_EXIF_VERSION, 1024),
|
||||
|
||||
/**
|
||||
* EXIF tag representing the software used to process or generate the image.
|
||||
*/
|
||||
SOFTWARE(ExifTagConstants.EXIF_TAG_SOFTWARE, 2048);
|
||||
|
||||
/**
|
||||
* The EXIF {@link TagInfo} associated with this slot.
|
||||
*/
|
||||
public final TagInfo tagInfo; // NOPMD
|
||||
|
||||
/**
|
||||
* The default storage capacity (in bytes) for this EXIF slot.
|
||||
*/
|
||||
public final int defaultCapacity;
|
||||
|
||||
/**
|
||||
* Constructs a {@code SlotType} with the specified EXIF tag and default
|
||||
* capacity.
|
||||
*
|
||||
* @param tagInfo the {@link TagInfo} representing the EXIF tag
|
||||
* @param defaultCapacity the default capacity in bytes for this slot
|
||||
*/
|
||||
SlotType(TagInfo tagInfo, int defaultCapacity) {
|
||||
this.tagInfo = tagInfo;
|
||||
this.defaultCapacity = defaultCapacity;
|
||||
}
|
||||
|
||||
}
|
||||
38
lib/src/main/java/zeroecho/covert/jpeg/package-info.java
Normal file
38
lib/src/main/java/zeroecho/covert/jpeg/package-info.java
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 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 zeroecho.covert.jpeg;
|
||||
61
lib/src/main/java/zeroecho/covert/package-info.java
Normal file
61
lib/src/main/java/zeroecho/covert/package-info.java
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
/**
|
||||
* Provides implementations of covert data embedding and extraction techniques.
|
||||
* <p>
|
||||
* The {@code zeroecho.covert} package includes utilities for hiding binary
|
||||
* payloads within standard media formats, particularly images, using
|
||||
* metadata-based steganography and other non-invasive channels. These
|
||||
* techniques are designed to conceal the presence of data without altering the
|
||||
* visible or functional aspects of the host content.
|
||||
* <p>
|
||||
* Current capabilities focus on:
|
||||
* <ul>
|
||||
* <li>Embedding encrypted or unstructured data into JPEG EXIF metadata
|
||||
* fields</li>
|
||||
* <li>Configurable allocation across multiple EXIF fields with capacity
|
||||
* enforcement</li>
|
||||
* <li>Reversible extraction pipelines for recovery of embedded payloads</li>
|
||||
* <li>Modular design suitable for extending to new carrier formats and
|
||||
* methods</li>
|
||||
* </ul>
|
||||
* Future support may include techniques based on file structure abuse, protocol
|
||||
* headers, or steganographic encoding in binary or multimedia content.
|
||||
* <p>
|
||||
* This package is intended for research, secure communication, and
|
||||
* privacy-preserving applications where the existence of a payload must be
|
||||
* obfuscated or made non-obvious.
|
||||
*/
|
||||
package zeroecho.covert;
|
||||
131
lib/src/main/java/zeroecho/data/DataContent.java
Normal file
131
lib/src/main/java/zeroecho/data/DataContent.java
Normal 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 zeroecho.data;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Represents a generic unit of data content. This may include plain, secret, or
|
||||
* encrypted forms of data.
|
||||
*/
|
||||
public interface DataContent { // NOPMD
|
||||
/**
|
||||
* Default maximum number of bytes that {@link #toBytes()} will read. This
|
||||
* prevents accidental memory overload when reading large content into RAM.
|
||||
*/
|
||||
int DEFAULT_MAX_READ_SIZE = 32 * 1024 * 1024; // 32 MB
|
||||
|
||||
/**
|
||||
* Returns an {@link InputStream} representing the content's data.
|
||||
* <p>
|
||||
* The stream may be raw or transformed depending on the content type.
|
||||
*
|
||||
* @return input stream of the content; never {@code null}
|
||||
* @throws IOException if an I/O error occurs opening the stream
|
||||
*/
|
||||
InputStream getStream() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns the content's data as a byte array, with a size limit of 16MB by
|
||||
* default.
|
||||
* <p>
|
||||
* This method is intended for small to medium-sized content. If the content
|
||||
* exceeds {@link #DEFAULT_MAX_READ_SIZE}, a {@link IllegalStateException} is
|
||||
* thrown.
|
||||
*
|
||||
* @return the content data as {@code byte[]}
|
||||
* @throws RuntimeException if reading from the stream fails
|
||||
* @throws IllegalStateException if content exceeds allowed memory limit
|
||||
*/
|
||||
default byte[] toBytes() {
|
||||
try (InputStream in = getStream(); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||
final byte[] buffer = new byte[4096];
|
||||
int total = 0;
|
||||
int n;
|
||||
while ((n = in.read(buffer)) != -1) { // NOPMD
|
||||
total += n;
|
||||
if (total > DEFAULT_MAX_READ_SIZE) {
|
||||
throw new IllegalStateException("Content too large to buffer (" + total + " bytes)");
|
||||
}
|
||||
out.write(buffer, 0, n);
|
||||
}
|
||||
return out.toByteArray();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Failed to read content stream", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content's data as a UTF-8 encoded string.
|
||||
* <p>
|
||||
* The default implementation converts {@code toBytes()} using UTF-8.
|
||||
* <strong>Warning:</strong> This method is only safe if the content represents
|
||||
* valid UTF-8 text. For arbitrary binary data (e.g., encrypted content), this
|
||||
* may produce invalid or garbled output.
|
||||
*
|
||||
* @return the content data as {@code String}
|
||||
* @throws RuntimeException if reading from the stream fails
|
||||
* @throws IllegalArgumentException if the bytes are not valid UTF-8 (optional)
|
||||
*/
|
||||
default String toText() {
|
||||
return new String(toBytes(), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects this content to the output of a previous stage in the pipeline.
|
||||
* <p>
|
||||
* This method is used to supply the input data that this content will process
|
||||
* (e.g., a {@code PlainContent} being encrypted, or an encrypted content being
|
||||
* decrypted).
|
||||
* </p>
|
||||
*
|
||||
* @param input the {@code DataContent} instance providing upstream data; may be
|
||||
* {@code null} if this is the start of the pipeline
|
||||
* @throws UnsupportedOperationException if not overridden by a subclass
|
||||
* @throws IllegalArgumentException if the input is not allowed by the
|
||||
* implementation
|
||||
*
|
||||
* @implSpec The default implementation of this method throws
|
||||
* {@link UnsupportedOperationException}. Subclasses that support
|
||||
* chaining must override this method.
|
||||
*/
|
||||
default void setInput(DataContent input) {
|
||||
throw new UnsupportedOperationException("This content type does not accept input chaining.");
|
||||
}
|
||||
}
|
||||
45
lib/src/main/java/zeroecho/data/EncryptedContent.java
Normal file
45
lib/src/main/java/zeroecho/data/EncryptedContent.java
Normal file
@@ -0,0 +1,45 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.data;
|
||||
|
||||
/**
|
||||
* Represents encrypted content, which is the result of an encryption process
|
||||
* and can be safely deployed to a public space without security concerns.
|
||||
* Deployment methods may include saving to a file, writing to standard output,
|
||||
* or using steganography for enhanced secrecy.
|
||||
*/
|
||||
public interface EncryptedContent extends DataContent { // NOPMD
|
||||
|
||||
}
|
||||
99
lib/src/main/java/zeroecho/data/ExportableDataContent.java
Normal file
99
lib/src/main/java/zeroecho/data/ExportableDataContent.java
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 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 zeroecho.data;
|
||||
|
||||
/**
|
||||
* An extension of {@link DataContent} that provides export capabilities in
|
||||
* different modes.
|
||||
* <p>
|
||||
* This interface is intended for data sources that support not only raw binary
|
||||
* streaming but also platform-specific script export, such as Bash or Windows
|
||||
* CMD. The export mode determines how the underlying content is transformed or
|
||||
* wrapped.
|
||||
*
|
||||
* <p>
|
||||
* Typical use cases include:
|
||||
* <ul>
|
||||
* <li>Uploading images or data directly to a remote server (RAW mode)</li>
|
||||
* <li>Generating self-contained Bash scripts that embed the encoded data
|
||||
* (BASH_SCRIPT mode)</li>
|
||||
* <li>Generating CMD scripts for use on Windows systems (CMD_SCRIPT mode)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* Implementations are responsible for adapting the content to the selected
|
||||
* {@code ExportMode}.
|
||||
*
|
||||
* @see ExportableDataContent.ExportMode
|
||||
* @see DataContent
|
||||
*/
|
||||
public interface ExportableDataContent extends DataContent {
|
||||
/**
|
||||
* Enumeration of supported export modes.
|
||||
*/
|
||||
enum ExportMode {
|
||||
/**
|
||||
* Raw mode returns the content as a direct InputStream, without any
|
||||
* transformation or encoding.
|
||||
*/
|
||||
RAW,
|
||||
/**
|
||||
* Bash script mode returns a shell script with embedded Base64-encoded content,
|
||||
* suitable for execution on Unix-like systems.
|
||||
*/
|
||||
BASH_SCRIPT,
|
||||
/**
|
||||
* CMD script mode returns a Windows batch file containing encoded content that
|
||||
* can be decoded and used locally.
|
||||
*/
|
||||
CMD_SCRIPT
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current export mode.
|
||||
*
|
||||
* @return the export mode that determines how the content will be formatted or
|
||||
* transformed
|
||||
*/
|
||||
ExportMode getExportMode();
|
||||
|
||||
/**
|
||||
* Sets the desired export mode.
|
||||
*
|
||||
* @param mode the export mode to use; must not be {@code null}
|
||||
* @throws NullPointerException if the mode is {@code null}
|
||||
*/
|
||||
void setExportMode(ExportMode mode);
|
||||
}
|
||||
44
lib/src/main/java/zeroecho/data/PlainContent.java
Normal file
44
lib/src/main/java/zeroecho/data/PlainContent.java
Normal file
@@ -0,0 +1,44 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.data;
|
||||
|
||||
/**
|
||||
* Represents a plain content that includes some original content, such as data
|
||||
* from interactive input, a file, or the result of decrypting encrypted
|
||||
* content.
|
||||
*/
|
||||
public interface PlainContent extends DataContent { // NOPMD
|
||||
|
||||
}
|
||||
44
lib/src/main/java/zeroecho/data/SecretContent.java
Normal file
44
lib/src/main/java/zeroecho/data/SecretContent.java
Normal file
@@ -0,0 +1,44 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.data;
|
||||
|
||||
/**
|
||||
* Represents secret content, which is a specific type of plain content that
|
||||
* stores a secret phrase. This phrase may be obtained from input, read from a
|
||||
* file, or generated dynamically.
|
||||
*/
|
||||
public interface SecretContent extends PlainContent { // NOPMD
|
||||
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* 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 zeroecho.data.output;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import zeroecho.data.DataContent;
|
||||
import zeroecho.data.ExportableDataContent;
|
||||
|
||||
/**
|
||||
* An abstract base class for exportable data content, providing default
|
||||
* implementations for managing input data and export mode.
|
||||
*
|
||||
* <p>
|
||||
* This class is intended to be extended by concrete implementations that
|
||||
* generate various forms of export output, such as raw binary streams, shell
|
||||
* scripts, or platform-specific wrappers.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It manages:
|
||||
* <ul>
|
||||
* <li>The {@link DataContent} input that is to be exported</li>
|
||||
* <li>The {@link ExportMode} that controls how the export is formatted</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* Implementing classes must override {@link ExportableDataContent#getStream()}
|
||||
* to provide the actual export logic based on the configured mode.
|
||||
* </p>
|
||||
*/
|
||||
abstract class AbstractExportableDataContent implements ExportableDataContent {
|
||||
/**
|
||||
* The export mode (RAW, BASH_SCRIPT, CMD_SCRIPT, etc.). Defaults to RAW.
|
||||
*/
|
||||
protected ExportMode mode = ExportMode.RAW;
|
||||
/**
|
||||
* The input data to be exported. Must be non-null before use.
|
||||
*/
|
||||
protected DataContent input;
|
||||
|
||||
/**
|
||||
* Sets the input {@link DataContent} for export.
|
||||
*
|
||||
* @param input the input data to be exported
|
||||
* @throws NullPointerException if {@code input} is {@code null}
|
||||
*/
|
||||
@Override
|
||||
public void setInput(DataContent input) {
|
||||
this.input = Objects.requireNonNull(input, "Input content cannot be null.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the currently selected export mode.
|
||||
*
|
||||
* @return the {@link ExportMode} in use
|
||||
*/
|
||||
@Override
|
||||
public ExportMode getExportMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the export mode to control how the data is output.
|
||||
*
|
||||
* @param mode the desired {@link ExportMode}
|
||||
*/
|
||||
@Override
|
||||
public void setExportMode(ExportMode mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
}
|
||||
194
lib/src/main/java/zeroecho/data/output/Base64Stream.java
Normal file
194
lib/src/main/java/zeroecho/data/output/Base64Stream.java
Normal 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 zeroecho.data.output;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Base64;
|
||||
import java.util.Base64.Encoder;
|
||||
|
||||
/**
|
||||
* A streaming {@link InputStream} that encodes binary input data into Base64
|
||||
* format with optional line prefixes and suffixes for each encoded line.
|
||||
*
|
||||
* <p>
|
||||
* This class is designed for script-friendly output formatting, such as
|
||||
* generating platform-specific batch or shell script lines where each line
|
||||
* might begin with a command (e.g., {@code echo }) and end with a
|
||||
* platform-specific line terminator (e.g., {@code \n} for UNIX-like systems or
|
||||
* {@code \r\n} for Windows).
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It supports chunked streaming and avoids holding the entire Base64-encoded
|
||||
* content in memory, making it suitable for large binary inputs. Lines are
|
||||
* split according to the specified maximum line length and are composed as
|
||||
* follows:
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* [prefix][base64-encoded data][suffix]
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* If a suffix is defined, the actual Base64 data per line will be truncated to
|
||||
* {@code lineLength - suffix.length} characters to ensure total line length
|
||||
* does not exceed {@code lineLength}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This stream does not automatically insert newlines between lines unless
|
||||
* explicitly provided via the {@code suffix} argument (e.g.,
|
||||
* {@code "\n".getBytes()}).
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <strong>Usage example:</strong>
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
* InputStream source = new FileInputStream("input.bin");
|
||||
* InputStream encoded = new Base64Stream(
|
||||
* source,
|
||||
* "echo ".getBytes(StandardCharsets.UTF_8),
|
||||
* 76,
|
||||
* "\n".getBytes(StandardCharsets.UTF_8)
|
||||
* );
|
||||
* encoded.transferTo(System.out);
|
||||
* }</pre>
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public class Base64Stream extends InputStream {
|
||||
|
||||
InputStream source;
|
||||
InputStream in, prefix, suffix;
|
||||
Encoder base64;
|
||||
int pos = 0;
|
||||
final int lineLength;
|
||||
int lineBreak;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code Base64Stream}.
|
||||
*
|
||||
* @param source the raw binary input stream to be Base64 encoded
|
||||
* @param linePrefix optional prefix to prepend at the beginning of each line
|
||||
* (e.g., {@code "echo "}); can be {@code null} for no prefix
|
||||
* @param lineLength the total maximum length of each output line, including any
|
||||
* prefix and suffix
|
||||
* @param lineSuffix optional suffix to append at the end of each line (e.g.,
|
||||
* newline); can be {@code null}
|
||||
*/
|
||||
public Base64Stream(InputStream source, byte[] linePrefix, int lineLength, byte[] lineSuffix) {
|
||||
this.source = source;
|
||||
this.lineLength = lineLength;
|
||||
in = InputStream.nullInputStream();
|
||||
base64 = Base64.getEncoder();
|
||||
if (linePrefix != null) {
|
||||
prefix = new ByteArrayInputStream(linePrefix);
|
||||
}
|
||||
if (lineSuffix != null) {
|
||||
suffix = new ByteArrayInputStream(lineSuffix);
|
||||
}
|
||||
lineBreak = lineLength - ((lineSuffix == null) ? 0 : lineSuffix.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
byte[] result = { 0 };
|
||||
int count = read(result, 0, 1);
|
||||
return (count == 0) ? -1 : result[0] & 0xff;
|
||||
}
|
||||
|
||||
InputStream is = InputStream.nullInputStream();
|
||||
int breakPos;
|
||||
boolean closed = false;
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
if (pos == lineLength) {
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
if (pos == 0 && prefix != null) {
|
||||
prefix.reset();
|
||||
breakPos = Integer.MAX_VALUE;
|
||||
is = prefix;
|
||||
} else {
|
||||
if (pos == lineBreak && suffix != null && !closed) {
|
||||
suffix.reset();
|
||||
breakPos = Integer.MAX_VALUE;
|
||||
is = suffix;
|
||||
} else {
|
||||
if (in.available() == 0) {
|
||||
ensureData();
|
||||
}
|
||||
breakPos = lineBreak;
|
||||
is = in;
|
||||
}
|
||||
}
|
||||
|
||||
int l = Math.min(Math.min(is.available(), len), breakPos - pos);
|
||||
pos += l;
|
||||
return is.read(b, off, l);
|
||||
}
|
||||
|
||||
final static int TRIPLES_INPUT = 1000;
|
||||
|
||||
/**
|
||||
* Reads the next block of raw bytes from the source and encodes them into
|
||||
* Base64. Handles stream exhaustion and final line suffix if applicable.
|
||||
*/
|
||||
private void ensureData() throws IOException {
|
||||
byte[] buf = source.readNBytes(3 * TRIPLES_INPUT);
|
||||
|
||||
if (buf.length == 0 && suffix != null && !closed) {
|
||||
System.out.println("suffix");
|
||||
|
||||
closed = true;
|
||||
suffix.reset();
|
||||
breakPos = lineBreak = Integer.MAX_VALUE;
|
||||
in = suffix;
|
||||
return;
|
||||
}
|
||||
|
||||
if (buf.length != 3 * TRIPLES_INPUT) {
|
||||
in = new ByteArrayInputStream(base64.encode(buf));
|
||||
} else {
|
||||
in = new ByteArrayInputStream(base64.withoutPadding().encode(buf));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
/**
|
||||
* 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 zeroecho.data.output;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.SequenceInputStream;
|
||||
import java.io.Writer;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* {@code PiwigoExportDataContent} is a specialized exportable content class
|
||||
* that supports uploading an image file to a Piwigo photo gallery server,
|
||||
* either directly via HTTP POST or by generating platform-specific scripts for
|
||||
* deferred uploading.
|
||||
*
|
||||
* <p>
|
||||
* Depending on the export mode (RAW, BASH_SCRIPT, CMD_SCRIPT), it can:
|
||||
* <ul>
|
||||
* <li>Upload the image to a Piwigo server directly using HTTP
|
||||
* multipart/form-data</li>
|
||||
* <li>Generate a Bash script that decodes the base64-encoded image and uploads
|
||||
* it using curl</li>
|
||||
* <li>Generate a CMD (Windows batch) script that reconstructs the image using
|
||||
* certutil and uploads it</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* This class integrates with {@code AbstractExportableDataContent} and expects
|
||||
* its {@code input} field to be set before invoking {@link #getStream()}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The image is uploaded using the {@code pwg.images.add} method of the Piwigo
|
||||
* API.
|
||||
* </p>
|
||||
*/
|
||||
class PiwigoExportDataContent extends AbstractExportableDataContent {
|
||||
private final String imageFileName;
|
||||
private final String piwigoUrl;
|
||||
private final String username;
|
||||
private final String password;
|
||||
private final String albumId;
|
||||
|
||||
/**
|
||||
* Constructs a new exportable Piwigo upload object.
|
||||
*
|
||||
* @param imageFileName the name of the image file to assign during upload or
|
||||
* script output
|
||||
* @param piwigoUrl the URL of the Piwigo API endpoint
|
||||
* @param username the Piwigo username for authentication
|
||||
* @param password the Piwigo password
|
||||
* @param albumId the ID of the Piwigo album to which the image will be
|
||||
* uploaded
|
||||
*/
|
||||
public PiwigoExportDataContent(String imageFileName, String piwigoUrl, String username, String password,
|
||||
String albumId) {
|
||||
this.imageFileName = imageFileName;
|
||||
this.piwigoUrl = piwigoUrl;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.albumId = albumId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@code InputStream} that provides either the raw upload stream, or
|
||||
* a platform-specific script depending on the export mode.
|
||||
*
|
||||
* @return the resulting {@code InputStream}
|
||||
* @throws IOException if reading the input or creating the stream fails
|
||||
*/
|
||||
@Override
|
||||
public InputStream getStream() throws IOException {
|
||||
if (input == null) {
|
||||
throw new IllegalStateException("Input not set.");
|
||||
}
|
||||
|
||||
return switch (mode) {
|
||||
case RAW -> performDirectUpload(input.getStream());
|
||||
case BASH_SCRIPT -> generateBashScript(input.getStream());
|
||||
case CMD_SCRIPT -> generateCmdScript(input.getStream());
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a direct upload of the image data to the Piwigo server using a
|
||||
* multipart/form-data HTTP POST request.
|
||||
*
|
||||
* @param dataStream the input stream of the binary image data
|
||||
* @return the server's response stream
|
||||
* @throws IOException if the upload fails
|
||||
*/
|
||||
private InputStream performDirectUpload(InputStream dataStream) throws IOException {
|
||||
String boundary = "----Boundary" + System.currentTimeMillis();
|
||||
|
||||
URL url = URI.create(piwigoUrl).toURL();
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setDoOutput(true);
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
|
||||
|
||||
try (OutputStream out = conn.getOutputStream();
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))) {
|
||||
|
||||
writeFormField(writer, boundary, "method", "pwg.images.add");
|
||||
writeFormField(writer, boundary, "username", username);
|
||||
writeFormField(writer, boundary, "password", password);
|
||||
writeFormField(writer, boundary, "category", albumId);
|
||||
|
||||
writer.write("--" + boundary + "\r\n");
|
||||
writer.write("Content-Disposition: form-data; name=\"image\"; filename=\"" + imageFileName + "\"\r\n");
|
||||
writer.write("Content-Type: image/jpeg\r\n\r\n");
|
||||
writer.flush();
|
||||
|
||||
dataStream.transferTo(out);
|
||||
out.flush();
|
||||
writer.write("\r\n--" + boundary + "--\r\n");
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
InputStream responseStream;
|
||||
try {
|
||||
responseStream = conn.getInputStream();
|
||||
} catch (IOException e) {
|
||||
InputStream errorStream = conn.getErrorStream();
|
||||
if (errorStream != null) {
|
||||
return errorStream;
|
||||
}
|
||||
return new ByteArrayInputStream(("Error: " + e.getMessage()).getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
return responseStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a single form field as part of a multipart/form-data HTTP request.
|
||||
*
|
||||
* @param writer the writer to output the field to
|
||||
* @param boundary the multipart boundary
|
||||
* @param name the name of the form field
|
||||
* @param value the value of the form field
|
||||
* @throws IOException if writing fails
|
||||
*/
|
||||
private void writeFormField(Writer writer, String boundary, String name, String value) throws IOException {
|
||||
writer.write("--" + boundary + "\r\n");
|
||||
writer.write("Content-Disposition: form-data; name=\"" + name + "\"\r\n\r\n");
|
||||
writer.write(value + "\r\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a Bash script that reconstructs the image using a Base64 heredoc
|
||||
* block and uploads it using {@code curl}.
|
||||
*
|
||||
* @param originalStream the original binary stream of the image
|
||||
* @return a stream containing the complete shell script
|
||||
*/
|
||||
private InputStream generateBashScript(InputStream originalStream) {
|
||||
InputStream header = new ByteArrayInputStream(("#!/bin/bash\nset -e\n\ncurl -X POST \"" + piwigoUrl + "\" \\\n"
|
||||
+ " -F method=\"pwg.images.add\" \\\n" + " -F username=\"" + username + "\" \\\n" + " -F password=\""
|
||||
+ password + "\" \\\n" + " -F category=\"" + albumId + "\" \\\n" + " -F image=@<(base64 -d <<'EOF'\n")
|
||||
.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
InputStream body = new Base64Stream(originalStream, null, 76, new byte[] { 10 });
|
||||
InputStream footer = new ByteArrayInputStream("EOF\n)\n".getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
return new SequenceInputStream(new SequenceInputStream(header, body), footer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a CMD batch script that reconstructs the image using certutil and
|
||||
* uploads it using {@code curl}.
|
||||
*
|
||||
* @param originalStream the original binary stream of the image
|
||||
* @return a stream containing the complete Windows batch script
|
||||
*/
|
||||
private InputStream generateCmdScript(InputStream originalStream) {
|
||||
InputStream header = new ByteArrayInputStream(
|
||||
("@echo off\nsetlocal\necho -----BEGIN BASE64----- > tmp.b64\n").getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
@SuppressWarnings("resource")
|
||||
InputStream body = new Base64Stream(originalStream, "echo ".getBytes(), 76, " >> tmp.b64\r\n".getBytes());
|
||||
InputStream footer = new ByteArrayInputStream(
|
||||
("echo -----END BASE64----- >> tmp.b64\n" + "certutil -decode tmp.b64 \"" + imageFileName + "\" >nul\n"
|
||||
+ "del tmp.b64\n" + "curl -X POST \"" + piwigoUrl + "\" ^\n" + " -F method=pwg.images.add ^\n"
|
||||
+ " -F username=" + username + " ^\n" + " -F password=" + password + " ^\n" + " -F category="
|
||||
+ albumId + " ^\n" + " -F image=@" + imageFileName + "\n" + "del \"" + imageFileName + "\"\n")
|
||||
.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
return new SequenceInputStream(new SequenceInputStream(header, body), footer);
|
||||
}
|
||||
}
|
||||
62
lib/src/main/java/zeroecho/data/output/package-info.java
Normal file
62
lib/src/main/java/zeroecho/data/output/package-info.java
Normal file
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
/**
|
||||
* Provides classes for exporting binary data in various formats including:
|
||||
* <ul>
|
||||
* <li>Raw binary stream uploads</li>
|
||||
* <li>Platform-specific shell script encodings (e.g., Bash or Windows CMD)</li>
|
||||
* <li>Base64 transformation and line-wrapping</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* The core components of this package include:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>{@link zeroecho.data.output.Base64Stream} – a stream that applies Base64
|
||||
* encoding and formats output per-line with optional prefixes and
|
||||
* suffixes.</li>
|
||||
* <li>{@link zeroecho.data.output.AbstractExportableDataContent} – a reusable
|
||||
* base class for any exportable content, managing input and export mode.</li>
|
||||
* <li>{@link zeroecho.data.output.PiwigoExportDataContent} – an implementation
|
||||
* that exports images to a Piwigo gallery, supporting direct upload or
|
||||
* script-based methods.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* These tools allow platform-neutral data packaging and transport, and support
|
||||
* automation scenarios.
|
||||
* </p>
|
||||
*/
|
||||
package zeroecho.data.output;
|
||||
53
lib/src/main/java/zeroecho/data/package-info.java
Normal file
53
lib/src/main/java/zeroecho/data/package-info.java
Normal file
@@ -0,0 +1,53 @@
|
||||
/*******************************************************************************
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
/**
|
||||
* Provides abstractions for representing different types of content used in
|
||||
* secure data handling.
|
||||
* <p>
|
||||
* The package defines the following interfaces:
|
||||
* <ul>
|
||||
* <li>{@link PlainContent} – Represents unprocessed or original content
|
||||
* obtained from input, files, or after decryption.</li>
|
||||
* <li>{@link SecretContent} – Represents sensitive plain content, typically a
|
||||
* secret phrase that may be input manually, read from a file, or generated
|
||||
* dynamically.</li>
|
||||
* <li>{@link EncryptedContent} – Represents the result of encrypting content,
|
||||
* which can be safely shared or stored publicly. It may also support
|
||||
* steganographic methods for additional secrecy.</li>
|
||||
* </ul>
|
||||
* These interfaces form the foundation for building secure data exchange
|
||||
* mechanisms and cryptographic workflows.
|
||||
*/
|
||||
package zeroecho.data;
|
||||
139
lib/src/main/java/zeroecho/data/processing/AesCommon.java
Normal file
139
lib/src/main/java/zeroecho/data/processing/AesCommon.java
Normal file
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* 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 zeroecho.data.processing;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import conflux.Ctx;
|
||||
import conflux.Key;
|
||||
import zeroecho.data.DataContent;
|
||||
import zeroecho.util.aes.AesCipherType;
|
||||
import zeroecho.util.aes.AesMode;
|
||||
import zeroecho.util.aes.AesParameters;
|
||||
import zeroecho.util.aes.AesSupport;
|
||||
|
||||
/**
|
||||
* A base class for AES encryption and decryption utilities.
|
||||
*
|
||||
* <p>
|
||||
* This class manages AES key material, initialization vectors (IVs), optional
|
||||
* Additional Authenticated Data (AAD), cipher type selection, and block size
|
||||
* configuration. It is designed to be extended by more specific encryption or
|
||||
* decryption implementations that rely on a consistent context for AES
|
||||
* parameter management.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Instances automatically populate the shared {@link Ctx} context with
|
||||
* AES-related parameters, ensuring they are available for use by cipher
|
||||
* initialization routines.
|
||||
* </p>
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public class AesCommon {
|
||||
|
||||
/**
|
||||
* Parameter key for AES block size.
|
||||
*/
|
||||
public static final Key<Integer> BLOCK_SIZE = Key.of("aes.block.size", Integer.class);
|
||||
|
||||
/**
|
||||
* Parameter key for AES encryption key bytes.
|
||||
*/
|
||||
public static final Key<byte[]> KEY = Key.of("aes.key", byte[].class);
|
||||
|
||||
/**
|
||||
* Parameter key for AES initialization vector (IV) bytes.
|
||||
*/
|
||||
public static final Key<byte[]> IV = Key.of("aes.iv", byte[].class);
|
||||
|
||||
/**
|
||||
* Parameter key for AES mode.
|
||||
*/
|
||||
public static final Key<AesMode> MODE = Key.of("aes.mode", AesMode.class);
|
||||
|
||||
/**
|
||||
* Parameter key for AES cipher type.
|
||||
*/
|
||||
public static final Key<AesCipherType> CIPHER_TYPE = Key.of("aes.cipher.type", AesCipherType.class);
|
||||
|
||||
/**
|
||||
* Parameter key for Additional Authenticated Data (AAD) used in AEAD modes such
|
||||
* as AES-GCM. This value must be identical for both encryption and decryption
|
||||
* to ensure authentication succeeds.
|
||||
*/
|
||||
public static final Key<byte[]> AAD = Key.of("aes.aad", byte[].class);
|
||||
|
||||
/**
|
||||
* The source data content to be encrypted or decrypted.
|
||||
*/
|
||||
protected DataContent source;
|
||||
|
||||
/**
|
||||
* Constructs an {@code AesCommon} instance and initializes the encryption
|
||||
* context with AES-specific parameters.
|
||||
*
|
||||
* <p>
|
||||
* This constructor sets the required AES block size in the shared {@link Ctx}
|
||||
* context, and, if {@code params} is non-null, it stores the provided AES mode,
|
||||
* key material, IV, cipher type, and any AAD values. The {@link Ctx} singleton
|
||||
* acts as a storage mechanism to hold the encryption-related state needed for
|
||||
* downstream operations.
|
||||
* </p>
|
||||
*
|
||||
* @param params the {@link AesParameters} to be saved into the encryption
|
||||
* context; may be {@code null} if no user parameters are to be
|
||||
* configured
|
||||
*/
|
||||
public AesCommon(final AesParameters params) {
|
||||
Ctx.INSTANCE.put(BLOCK_SIZE, AesSupport.BLOCK_SIZE);
|
||||
|
||||
if (params != null) {
|
||||
params.save(Ctx.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the input data content to be encrypted or decrypted.
|
||||
*
|
||||
* @param input the {@link DataContent} to set as source
|
||||
* @throws NullPointerException if {@code input} is {@code null}
|
||||
*/
|
||||
public void setInput(final DataContent input) {
|
||||
Objects.requireNonNull(input, "input must not be null");
|
||||
source = input;
|
||||
}
|
||||
}
|
||||
109
lib/src/main/java/zeroecho/data/processing/AesDecryptor.java
Normal file
109
lib/src/main/java/zeroecho/data/processing/AesDecryptor.java
Normal file
@@ -0,0 +1,109 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.data.processing;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import conflux.Ctx;
|
||||
import zeroecho.data.PlainContent;
|
||||
import zeroecho.util.aes.AesCipherType;
|
||||
import zeroecho.util.aes.AesParameters;
|
||||
import zeroecho.util.aes.AesSupport;
|
||||
|
||||
/**
|
||||
* Decrypts AES-encrypted content from an input stream.
|
||||
*
|
||||
* This class extends {@link AesCommon} and implements {@link PlainContent},
|
||||
* providing functionality to retrieve a decrypted {@link InputStream} from an
|
||||
* encrypted source using AES parameters such as key, IV, and mode.
|
||||
*
|
||||
* This class itself does not perform password-based key derivation; subclasses
|
||||
* should implement such logic if needed.
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public class AesDecryptor extends AesCommon implements PlainContent {
|
||||
/** Logger for internal messages and error reporting. */
|
||||
private static final Logger LOG = Logger.getLogger(AesDecryptor.class.getName());
|
||||
|
||||
/**
|
||||
* Constructs an {@code AesDecryptor} initialized with the specified AES
|
||||
* parameters.
|
||||
*
|
||||
* @param params the AES parameters including mode, key, and IV
|
||||
*/
|
||||
public AesDecryptor(final AesParameters params) {
|
||||
super(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a decrypted {@link InputStream} by applying AES decryption on the
|
||||
* input stream provided by the underlying source.
|
||||
*
|
||||
* This method retrieves cryptographic parameters including the encryption key,
|
||||
* initialization vector (IV), and cipher type from a contextual configuration
|
||||
* (via {@link Ctx}). It then delegates decryption to
|
||||
* {@link AesSupport#decrypt(byte[], byte[], byte[], AesCipherType, InputStream)}.
|
||||
*
|
||||
* If the source stream is {@code null}, a {@link NullPointerException} is
|
||||
* thrown. If any decryption error occurs (e.g., invalid parameters or corrupted
|
||||
* input), it is logged and rethrown as an {@link IOException}.
|
||||
*
|
||||
* @return the decrypted {@code InputStream}
|
||||
* @throws IOException if decryption fails or an I/O error occurs
|
||||
* during stream processing
|
||||
* @throws NullPointerException if the input stream retrieved from the source is
|
||||
* {@code null}
|
||||
*/
|
||||
@Override
|
||||
public InputStream getStream() throws IOException {
|
||||
final InputStream previousInput = source.getStream();
|
||||
|
||||
Objects.requireNonNull(previousInput, "input stream must not be null");
|
||||
|
||||
try {
|
||||
return AesSupport.decrypt(Ctx.INSTANCE.get(KEY), Ctx.INSTANCE.get(IV), Ctx.INSTANCE.get(AAD),
|
||||
Ctx.INSTANCE.get(CIPHER_TYPE), previousInput);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.logp(Level.WARNING, "AesDecryptor", "getStream", "Exception during decryption", e);
|
||||
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
111
lib/src/main/java/zeroecho/data/processing/AesEncryptor.java
Normal file
111
lib/src/main/java/zeroecho/data/processing/AesEncryptor.java
Normal file
@@ -0,0 +1,111 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.data.processing;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import conflux.Ctx;
|
||||
import zeroecho.data.EncryptedContent;
|
||||
import zeroecho.util.aes.AesParameters;
|
||||
import zeroecho.util.aes.AesSupport;
|
||||
|
||||
/**
|
||||
* Encrypts the provided content using AES and exposes the result as an
|
||||
* {@link InputStream}.
|
||||
*
|
||||
* The encryption key, IV, and mode are specified by the configured AES
|
||||
* parameters. The source content is streamed and encrypted on-the-fly using
|
||||
* {@link AesSupport#encrypt}.
|
||||
*
|
||||
* This implementation logs encryption errors and wraps them in
|
||||
* {@link IOException}s to ensure compatibility with streaming APIs.
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public class AesEncryptor extends AesCommon implements EncryptedContent {
|
||||
/** Logger for internal messages and error reporting. */
|
||||
private static final Logger LOG = Logger.getLogger(AesEncryptor.class.getName());
|
||||
|
||||
/**
|
||||
* Constructs an {@code AesEncryptor} initialized with the specified AES
|
||||
* parameters.
|
||||
*
|
||||
* @param params the AES parameters including mode, key, and IV
|
||||
* @throws IllegalArgumentException if {@code params} is {@code null}
|
||||
*/
|
||||
public AesEncryptor(final AesParameters params) {
|
||||
super(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link InputStream} that applies AES encryption to the underlying
|
||||
* input data.
|
||||
*
|
||||
* This method retrieves the original {@code InputStream} from the configured
|
||||
* {@code source}, verifies its presence, and then wraps it in an AES-encrypting
|
||||
* stream using the encryption context provided by {@link Ctx}. The encryption
|
||||
* parameters used include the AES key, initialization vector (IV), and cipher
|
||||
* type, all of which must be pre-configured in the context.
|
||||
*
|
||||
* If the original stream is {@code null}, or if encryption setup fails due to
|
||||
* invalid or missing parameters, this method throws an {@link IOException}. Any
|
||||
* internal {@link IllegalArgumentException} is logged and rethrown as an
|
||||
* {@code IOException} to ensure consistent error handling.
|
||||
*
|
||||
* @return an {@code InputStream} that provides AES-encrypted data as it is read
|
||||
* @throws IOException if the input stream is missing or encryption
|
||||
* setup fails
|
||||
* @throws NullPointerException if the source stream is {@code null}
|
||||
*/
|
||||
@Override
|
||||
public InputStream getStream() throws IOException {
|
||||
final InputStream previousInput = source.getStream();
|
||||
|
||||
Objects.requireNonNull(previousInput, "input stream must not be null");
|
||||
|
||||
try {
|
||||
return AesSupport.encrypt(Ctx.INSTANCE.get(KEY), Ctx.INSTANCE.get(IV), Ctx.INSTANCE.get(AAD),
|
||||
Ctx.INSTANCE.get(CIPHER_TYPE), previousInput);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.logp(Level.WARNING, "AesEncryptor", "getStream", "Exception", e);
|
||||
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
144
lib/src/main/java/zeroecho/data/processing/KEMDecryptor.java
Normal file
144
lib/src/main/java/zeroecho/data/processing/KEMDecryptor.java
Normal file
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* 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 zeroecho.data.processing;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import zeroecho.data.DataContent;
|
||||
import zeroecho.data.PlainContent;
|
||||
import zeroecho.util.aes.AesCipherType;
|
||||
import zeroecho.util.asymmetric.KEMAsymmetricContext;
|
||||
|
||||
/**
|
||||
* A KEM-based AES decryptor implementation of {@link PlainContent}.
|
||||
* <p>
|
||||
* This class uses a {@link KEMAsymmetricContext} configured with a key
|
||||
* extractor to perform key decapsulation, combined with AES decryption for the
|
||||
* payload.
|
||||
* </p>
|
||||
* <p>
|
||||
* The decrypted content can be accessed as a stream after setting the encrypted
|
||||
* input content.
|
||||
* </p>
|
||||
*/
|
||||
public final class KEMDecryptor implements PlainContent {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(KEMDecryptor.class.getName());
|
||||
/**
|
||||
* The KEM context configured for decapsulation (extractor must be non-null).
|
||||
*/
|
||||
private final KEMAsymmetricContext kemContext;
|
||||
/**
|
||||
* AES cipher variant used for symmetric decryption.
|
||||
*/
|
||||
private final AesCipherType cipherType;
|
||||
/**
|
||||
* Optional Additional Authenticated Data used during decryption; may be
|
||||
* {@code null}.
|
||||
*/
|
||||
private final byte[] aad;
|
||||
|
||||
/**
|
||||
* The encrypted content input to be decrypted.
|
||||
* <p>
|
||||
* Must be set before calling {@link #getStream()}.
|
||||
* </p>
|
||||
*/
|
||||
private DataContent encryptedContent;
|
||||
|
||||
/**
|
||||
* Constructs a KEM-based AES decryptor.
|
||||
*
|
||||
* @param kemContext the KEM context configured for decapsulation mode; must
|
||||
* have a non-null extractor for key decapsulation
|
||||
* @param cipherType the AES cipher type used during encryption (must match)
|
||||
* @param aad optional Additional Authenticated Data for AEAD ciphers;
|
||||
* may be {@code null}
|
||||
* @throws IllegalArgumentException if {@code kemContext} has no extractor or if
|
||||
* {@code cipherType} is {@code null}
|
||||
*/
|
||||
public KEMDecryptor(KEMAsymmetricContext kemContext, AesCipherType cipherType, byte[] aad) {
|
||||
if (kemContext.extractor() == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"KEMDecryptor requires a KEMAsymmetricContext in decapsulation mode (extractor must not be null)");
|
||||
}
|
||||
this.kemContext = kemContext;
|
||||
this.cipherType = Objects.requireNonNull(cipherType, "cipherType must not be null");
|
||||
this.aad = aad; // NOPMD
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the encrypted input content to be decrypted.
|
||||
*
|
||||
* @param input the encrypted {@link DataContent} input; must not be
|
||||
* {@code null}
|
||||
* @throws IllegalArgumentException if {@code input} is {@code null}
|
||||
*/
|
||||
@Override
|
||||
public void setInput(DataContent input) {
|
||||
if (input == null) {
|
||||
throw new IllegalArgumentException("Input DataContent cannot be null");
|
||||
}
|
||||
this.encryptedContent = input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link InputStream} providing the decrypted plaintext.
|
||||
* <p>
|
||||
* This method requires that the encrypted input content has been set via
|
||||
* {@link #setInput(DataContent)}.
|
||||
* </p>
|
||||
*
|
||||
* @return an input stream yielding the decrypted plaintext; never {@code null}
|
||||
* @throws IllegalStateException if no encrypted content has been set prior to
|
||||
* invocation
|
||||
* @throws IOException if an I/O error occurs during stream retrieval
|
||||
* or decryption
|
||||
*/
|
||||
@Override
|
||||
public InputStream getStream() throws IOException {
|
||||
if (encryptedContent == null) {
|
||||
throw new IllegalStateException("No encrypted content set for decryption");
|
||||
}
|
||||
if (LOG.isLoggable(Level.FINE)) {
|
||||
LOG.fine("Starting KEM decryption using cipher type: " + cipherType);
|
||||
}
|
||||
return kemContext.getDecryptedStream(encryptedContent.getStream(), cipherType, aad);
|
||||
}
|
||||
}
|
||||
174
lib/src/main/java/zeroecho/data/processing/KEMEncryptor.java
Normal file
174
lib/src/main/java/zeroecho/data/processing/KEMEncryptor.java
Normal file
@@ -0,0 +1,174 @@
|
||||
/**
|
||||
* 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 zeroecho.data.processing;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import zeroecho.data.DataContent;
|
||||
import zeroecho.data.EncryptedContent;
|
||||
import zeroecho.util.aes.AesCipherType;
|
||||
import zeroecho.util.asymmetric.KEMAsymmetricContext;
|
||||
|
||||
/**
|
||||
* Implements encryption of content using a Key Encapsulation Mechanism (KEM)
|
||||
* combined with AES symmetric encryption.
|
||||
* <p>
|
||||
* This class wraps a {@link KEMAsymmetricContext} configured with a KEM key
|
||||
* generator and applies AES encryption (specified by {@link AesCipherType}) to
|
||||
* the input data. The encryption output stream consists of the concatenation of
|
||||
* the KEM encapsulated key, the AES initialization vector (IV), and the
|
||||
* AES-encrypted payload.
|
||||
* <p>
|
||||
* Optionally supports Additional Authenticated Data (AAD) to be included in
|
||||
* authenticated cipher modes (e.g., AES-GCM).
|
||||
* <p>
|
||||
* Usage:
|
||||
* <ul>
|
||||
* <li>Create an instance with a properly initialized
|
||||
* {@code KEMAsymmetricContext} in encapsulation mode.</li>
|
||||
* <li>Set the input content using {@link #setInput(DataContent)}.</li>
|
||||
* <li>Obtain the encrypted output stream via {@link #getStream()}.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Note: The {@code KEMAsymmetricContext} must have a non-null generator to
|
||||
* perform encryption.
|
||||
*
|
||||
* @implSpec The {@link #getStream()} method produces an InputStream that yields
|
||||
* the concatenation of:
|
||||
* <ol>
|
||||
* <li>KEM encapsulated key bytes</li>
|
||||
* <li>AES initialization vector bytes</li>
|
||||
* <li>Encrypted payload bytes</li>
|
||||
* </ol>
|
||||
* This stream never returns null and throws IOException on failure.
|
||||
*/
|
||||
public class KEMEncryptor implements EncryptedContent {
|
||||
private static final Logger LOG = Logger.getLogger(KEMEncryptor.class.getName());
|
||||
|
||||
/**
|
||||
* The KEM context configured for encapsulation (generator must be non-null).
|
||||
*/
|
||||
private final KEMAsymmetricContext kemContext;
|
||||
/**
|
||||
* AES cipher variant used for symmetric decryption.
|
||||
*/
|
||||
private final AesCipherType cipherType;
|
||||
/**
|
||||
* Optional Additional Authenticated Data used during decryption; may be
|
||||
* {@code null}.
|
||||
*/
|
||||
private final byte[] aad;
|
||||
|
||||
/**
|
||||
* The input content to be encrypted.
|
||||
* <p>
|
||||
* Must be set before {@link #getStream()} is called. It represents the
|
||||
* plaintext data that will be encrypted using the KEM + AES process.
|
||||
*/
|
||||
private DataContent inputContent;
|
||||
|
||||
/**
|
||||
* Constructs a new KEM-based AES encryptor.
|
||||
*
|
||||
* @param kemContext the KEM context configured for encapsulation mode; must
|
||||
* have a non-null generator for key encapsulation
|
||||
* @param cipherType the AES cipher variant to use for symmetric encryption
|
||||
* (e.g., CBC, GCM)
|
||||
* @param aad optional Additional Authenticated Data bytes for AEAD
|
||||
* ciphers; may be {@code null} if not applicable
|
||||
* @throws IllegalArgumentException if the {@code kemContext} does not have a
|
||||
* generator or if {@code cipherType} is
|
||||
* {@code null}
|
||||
*/
|
||||
public KEMEncryptor(KEMAsymmetricContext kemContext, AesCipherType cipherType, byte[] aad) {
|
||||
if (kemContext.generator() == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"KEMEncryptor requires a KEMAsymmetricContext in encapsulation mode (generator must not be null)");
|
||||
}
|
||||
this.kemContext = kemContext;
|
||||
this.cipherType = Objects.requireNonNull(cipherType, "cipherType must not be null");
|
||||
this.aad = aad; // NOPMD
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the input content that will be encrypted.
|
||||
*
|
||||
* @param input the {@link DataContent} providing the plaintext data; must not
|
||||
* be {@code null}
|
||||
* @throws IllegalArgumentException if {@code input} is {@code null}
|
||||
*/
|
||||
@Override
|
||||
public void setInput(DataContent input) {
|
||||
if (input == null) {
|
||||
throw new IllegalArgumentException("Input DataContent cannot be null");
|
||||
}
|
||||
this.inputContent = input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link InputStream} that yields the encrypted data stream.
|
||||
* <p>
|
||||
* The stream consists of the concatenation of:
|
||||
* <ul>
|
||||
* <li>KEM encapsulated key bytes</li>
|
||||
* <li>AES initialization vector (IV)</li>
|
||||
* <li>AES-encrypted payload</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* This method requires that the input content has been set via
|
||||
* {@link #setInput(DataContent)}.
|
||||
*
|
||||
* @return an input stream providing the encrypted content; never {@code null}
|
||||
* @throws IllegalStateException if no input content has been set prior to
|
||||
* invocation
|
||||
* @throws IOException if an I/O error occurs during stream retrieval
|
||||
* or encryption
|
||||
*/
|
||||
@Override
|
||||
public InputStream getStream() throws IOException {
|
||||
if (inputContent == null) {
|
||||
throw new IllegalStateException("No input content set for encryption");
|
||||
}
|
||||
if (LOG.isLoggable(Level.FINE)) {
|
||||
LOG.fine("Starting KEM encryption using cipher type: " + cipherType);
|
||||
}
|
||||
return kemContext.getEncryptedStream(inputContent.getStream(), cipherType, aad);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
/**
|
||||
* 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 zeroecho.data.processing;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import conflux.Ctx;
|
||||
import zeroecho.util.IOUtil;
|
||||
import zeroecho.util.aes.AesCipherType;
|
||||
import zeroecho.util.aes.AesMode;
|
||||
import zeroecho.util.aes.AesSupport;
|
||||
import zeroecho.util.aes.DerivedAesParameters;
|
||||
|
||||
/**
|
||||
* A password-based AES decryption utility that reads key derivation parameters
|
||||
* (salt and iteration count) from the encrypted stream header and performs
|
||||
* on-the-fly decryption using the derived cryptographic key and IV.
|
||||
*
|
||||
* This class is designed to complement {@link PasswordBasedAesEncryptor}, which
|
||||
* prepends the salt and iteration count to the encrypted stream. The decryptor
|
||||
* reads these values to securely re-derive the same AES key and IV using the
|
||||
* configured {@link AesMode} and {@link AesCipherType}.
|
||||
*
|
||||
* Internally, it uses PBKDF2 (Password-Based Key Derivation Function 2) to
|
||||
* derive the key material and delegates decryption to the {@link AesDecryptor}
|
||||
* superclass.
|
||||
*
|
||||
* The shared {@link Ctx} context is used to temporarily store cryptographic
|
||||
* configuration and derived parameters during decryption. The password is held
|
||||
* in memory only long enough to perform re-derivation and is not retained
|
||||
* beyond that.
|
||||
*
|
||||
* If the encrypted stream header is malformed, or if the key derivation fails,
|
||||
* an exception is raised to indicate potential tampering or incompatibility.
|
||||
*
|
||||
* @see PasswordBasedAesEncryptor
|
||||
* @see AesSupport#rederiveKeyAndIv(String, byte[], int, AesMode, byte[],
|
||||
* AesCipherType)
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public class PasswordBasedAesDecryptor extends AesDecryptor {
|
||||
private static final Logger LOG = Logger.getLogger(PasswordBasedAesDecryptor.class.getName());
|
||||
private final String password;
|
||||
|
||||
/**
|
||||
* Constructs a PasswordBasedAesDecryptor with password, AES mode, and default
|
||||
* cipher type (CBC).
|
||||
*
|
||||
* @param password the password for key derivation
|
||||
* @param mode the AES mode (128, 192, or 256-bit)
|
||||
* @throws IllegalArgumentException if arguments are invalid
|
||||
* @throws InvalidKeySpecException if key derivation fails
|
||||
*/
|
||||
public PasswordBasedAesDecryptor(final String password, final AesMode mode) throws InvalidKeySpecException {
|
||||
this(password, null, mode, AesCipherType.CBC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a password-based AES decryptor with specified AES mode, cipher
|
||||
* type, and Additional Authenticated Data (AAD).
|
||||
*
|
||||
* <p>
|
||||
* This decryptor uses the provided password to derive the AES key for
|
||||
* decryption. The AES mode and cipher type define the algorithm parameters
|
||||
* (e.g., key size and block mode). The optional AAD is used for authenticated
|
||||
* encryption modes like GCM to ensure integrity and authenticity of additional
|
||||
* associated data.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The provided AAD bytes will be stored in the global context for use during
|
||||
* decryption. If the cipher mode does not support AAD or if no AAD is needed,
|
||||
* this parameter can be {@code null}.
|
||||
* </p>
|
||||
*
|
||||
* @param password the password used for AES key derivation; must not be
|
||||
* {@code null}
|
||||
* @param aad additional authenticated data bytes; may be {@code null} if
|
||||
* not used
|
||||
* @param mode the AES mode indicating key length (e.g., AES-128,
|
||||
* AES-256); must not be {@code null}
|
||||
* @param cipherType the AES cipher type (e.g., CBC, GCM); must not be
|
||||
* {@code null}
|
||||
* @throws InvalidKeySpecException if key derivation or initialization fails
|
||||
* @throws NullPointerException if {@code password}, {@code mode}, or
|
||||
* {@code cipherType} is {@code null}
|
||||
*/
|
||||
public PasswordBasedAesDecryptor(final String password, final byte[] aad, final AesMode mode,
|
||||
final AesCipherType cipherType) throws InvalidKeySpecException {
|
||||
super(null);
|
||||
|
||||
Ctx.INSTANCE.put(MODE, mode);
|
||||
Ctx.INSTANCE.put(CIPHER_TYPE, cipherType);
|
||||
if (aad != null) {
|
||||
Ctx.INSTANCE.put(AAD, aad);
|
||||
}
|
||||
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link InputStream} that decrypts AES-encrypted data using a key
|
||||
* and IV derived from the password.
|
||||
* <p>
|
||||
* This method expects the encrypted input stream to begin with:
|
||||
* <ul>
|
||||
* <li>A packed 7-bit encoded salt length</li>
|
||||
* <li>The salt bytes</li>
|
||||
* <li>A packed 7-bit encoded PBKDF iteration count</li>
|
||||
* </ul>
|
||||
* These parameters are used to re-derive the decryption key and IV using PBKDF,
|
||||
* which are then used to decrypt the rest of the stream.
|
||||
*
|
||||
* @return a decrypted {@link InputStream}
|
||||
* @throws IOException if header parsing or stream decryption fails
|
||||
* @throws IllegalStateException if key derivation fails due to an invalid or
|
||||
* corrupt state
|
||||
*/
|
||||
@Override
|
||||
public InputStream getStream() throws IOException {
|
||||
final InputStream in = source.getStream();
|
||||
|
||||
// Read salt and iteration count from input stream header
|
||||
final int saltLength = IOUtil.readPack7I(in);
|
||||
if (saltLength > 4 * AesSupport.BLOCK_SIZE) {
|
||||
throw new IOException("Salt length " + saltLength + " is weird");
|
||||
}
|
||||
final byte[] salt = in.readNBytes(saltLength);
|
||||
final int iterations = IOUtil.readPack7I(in);
|
||||
|
||||
if (LOG.isLoggable(Level.INFO)) {
|
||||
LOG.log(Level.INFO, "processing AES (iterations={0} salt={1})",
|
||||
new Object[] { iterations, Arrays.toString(salt) });
|
||||
}
|
||||
|
||||
final DerivedAesParameters params;
|
||||
try {
|
||||
params = AesSupport.rederiveKeyAndIv(password, salt, iterations, Ctx.INSTANCE.get(MODE),
|
||||
Ctx.INSTANCE.get(AAD), Ctx.INSTANCE.get(CIPHER_TYPE));
|
||||
params.save(Ctx.INSTANCE);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
LOG.logp(Level.WARNING, "PasswordBasedAesDecryptor", "getStream", "Exception", e);
|
||||
throw new IllegalStateException("Failed to generate key: invalid state", e);
|
||||
}
|
||||
|
||||
try {
|
||||
return AesSupport.decrypt(Ctx.INSTANCE.get(KEY), Ctx.INSTANCE.get(IV), Ctx.INSTANCE.get(AAD),
|
||||
Ctx.INSTANCE.get(CIPHER_TYPE), in);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.logp(Level.WARNING, "PasswordBasedAesDecryptor", "getStream", "Exception during decryption", e);
|
||||
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
/**
|
||||
* 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 zeroecho.data.processing;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.SequenceInputStream;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import conflux.Ctx;
|
||||
import zeroecho.util.IOUtil;
|
||||
import zeroecho.util.aes.AesCipherType;
|
||||
import zeroecho.util.aes.AesMode;
|
||||
import zeroecho.util.aes.AesSupport;
|
||||
import zeroecho.util.aes.DerivedAesParameters;
|
||||
|
||||
/**
|
||||
* A password-based AES encryption utility that derives a cryptographic key and
|
||||
* IV using PBKDF2 (Password-Based Key Derivation Function 2) and performs AES
|
||||
* encryption on a given input stream.
|
||||
* <p>
|
||||
* This class encapsulates the logic required to securely derive AES parameters
|
||||
* from a user-supplied password and to stream-encrypt data using the derived
|
||||
* values.
|
||||
* <p>
|
||||
* A header containing the salt and iteration count is prepended to the
|
||||
* encrypted stream, allowing the corresponding decryption mechanism to
|
||||
* re-derive the key for symmetric operations. The underlying encryption is
|
||||
* performed using the {@link AesEncryptor} superclass.
|
||||
* <p>
|
||||
* For security and immutability reasons, critical cryptographic parameters
|
||||
* (iterations, mode, cipher type) are made read-only after construction.
|
||||
* Attempts to modify them will result in an {@link IllegalStateException}.
|
||||
*
|
||||
* @see AesSupport#deriveKeyAndIv(String, int, AesMode, byte[], AesCipherType)
|
||||
* @see PasswordBasedAesDecryptor
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public class PasswordBasedAesEncryptor extends AesEncryptor {
|
||||
private static final Logger LOG = Logger.getLogger(PasswordBasedAesEncryptor.class.getName());
|
||||
|
||||
/**
|
||||
* Constructs a PasswordBasedAesEncryptor using a password, iteration count, AES
|
||||
* mode, and default cipher type (CBC).
|
||||
*
|
||||
* @param password the password for key derivation
|
||||
* @param iterations the PBKDF2 iteration count
|
||||
* @param mode the AES mode (128, 192, or 256-bit)
|
||||
* @throws IllegalArgumentException if parameters are invalid
|
||||
* @throws InvalidKeySpecException if key derivation fails
|
||||
*/
|
||||
public PasswordBasedAesEncryptor(final String password, final int iterations, final AesMode mode)
|
||||
throws InvalidKeySpecException {
|
||||
this(password, iterations, null, mode, AesCipherType.CBC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code PasswordBasedAesEncryptor} instance that derives a
|
||||
* cryptographic key and initialization vector (IV) from a given password using
|
||||
* the specified number of iterations, AES mode, associated data (AAD), and
|
||||
* cipher type.
|
||||
*
|
||||
* <p>
|
||||
* This constructor uses a password-based key derivation function to generate
|
||||
* the necessary encryption parameters, which are then passed to the parent
|
||||
* {@link AesCommon} class.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* To ensure the integrity of derived cryptographic material, listeners are
|
||||
* registered on sensitive context parameters (iterations, mode, and cipher
|
||||
* type). Any attempt to modify these parameters after construction will result
|
||||
* in an {@link IllegalStateException}.
|
||||
* </p>
|
||||
*
|
||||
* @param password the password used to derive the encryption key and IV
|
||||
* @param iterations the number of iterations for key derivation
|
||||
* @param mode the AES mode to use (e.g., 128-bit, 192-bit, 256-bit)
|
||||
* @param aad additional authenticated data (AAD) to be included in the
|
||||
* derived parameters; may be {@code null} if unused
|
||||
* @param cipherType the cipher type to use (e.g., CBC, GCM, CTR)
|
||||
*
|
||||
* @throws IllegalArgumentException if any parameter is invalid
|
||||
* @throws InvalidKeySpecException if the key derivation fails due to an
|
||||
* invalid specification
|
||||
*
|
||||
* @see AesSupport#deriveKeyAndIv(String, int, AesMode, byte[], AesCipherType)
|
||||
*/
|
||||
public PasswordBasedAesEncryptor(final String password, final int iterations, final byte[] aad, final AesMode mode,
|
||||
final AesCipherType cipherType) throws InvalidKeySpecException {
|
||||
super(AesSupport.deriveKeyAndIv(password, iterations, mode, aad, cipherType));
|
||||
|
||||
Ctx.INSTANCE.addListener(DerivedAesParameters.ITERATIONS, newValue -> {
|
||||
throw new IllegalStateException(DerivedAesParameters.ITERATIONS
|
||||
+ " value cannot be modified because it would affect previously computed crypto material");
|
||||
});
|
||||
Ctx.INSTANCE.addListener(MODE, newValue -> {
|
||||
throw new IllegalStateException(
|
||||
MODE + " value cannot be modified because it would affect previously computed crypto material");
|
||||
});
|
||||
Ctx.INSTANCE.addListener(CIPHER_TYPE, newValue -> {
|
||||
throw new IllegalStateException(CIPHER_TYPE
|
||||
+ " value cannot be modified because it would affect previously computed crypto material");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link InputStream} that provides AES-encrypted data derived from
|
||||
* the configured password-based key and IV.
|
||||
*
|
||||
* This method prepends a header to the encrypted stream containing the salt and
|
||||
* iteration count used for key derivation, which is necessary for corresponding
|
||||
* decryption processes to re-derive the correct key.
|
||||
*
|
||||
* The encryption itself is delegated to the superclass's
|
||||
* {@link AesEncryptor#getStream()}, which performs on-the-fly encryption of the
|
||||
* underlying source data.
|
||||
*
|
||||
* @return an input stream of AES-encrypted content including a header with salt
|
||||
* and iterations
|
||||
* @throws IOException if encryption or stream access fails
|
||||
*/
|
||||
@Override
|
||||
public InputStream getStream() throws IOException {
|
||||
// Compose header: salt + iterations
|
||||
final byte[] salt = Ctx.INSTANCE.get(DerivedAesParameters.SALT);
|
||||
final int iterations = Ctx.INSTANCE.get(DerivedAesParameters.ITERATIONS);
|
||||
final ByteArrayOutputStream header = new ByteArrayOutputStream();
|
||||
IOUtil.writePack7I(header, salt.length);
|
||||
header.write(salt);
|
||||
IOUtil.writePack7I(header, iterations);
|
||||
|
||||
// salt is not a secret, so we can log it
|
||||
if (LOG.isLoggable(Level.INFO)) {
|
||||
LOG.log(Level.INFO, "processing AES (iterations={0} salt={1}",
|
||||
new Object[] { iterations, Arrays.toString(salt) });
|
||||
}
|
||||
|
||||
final InputStream encryptedStream = super.getStream();
|
||||
return new SequenceInputStream(new ByteArrayInputStream(header.toByteArray()), encryptedStream);
|
||||
}
|
||||
}
|
||||
135
lib/src/main/java/zeroecho/data/processing/PlainBytes.java
Normal file
135
lib/src/main/java/zeroecho/data/processing/PlainBytes.java
Normal file
@@ -0,0 +1,135 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.data.processing;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import zeroecho.data.DataContent;
|
||||
import zeroecho.data.PlainContent;
|
||||
|
||||
/**
|
||||
* An implementation of {@link PlainContent} that encapsulates a byte array.
|
||||
*
|
||||
* Provides read-only access to the content through an {@link InputStream}
|
||||
* backed by the internal byte buffer.
|
||||
*
|
||||
* This class represents the start of a {@link DataContent} processing chain,
|
||||
* thus it does not accept input from preceding content.
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public class PlainBytes implements PlainContent {
|
||||
/**
|
||||
* Logger instance for the {@code PlainBytes} class used to log runtime
|
||||
* information, warnings, or errors. It is statically initialized with the class
|
||||
* name to ensure consistent logging context throughout the class.
|
||||
*/
|
||||
private static final Logger LOG = Logger.getLogger(PlainBytes.class.getName());
|
||||
/**
|
||||
* Byte array buffer that holds the raw data managed by this instance.
|
||||
*/
|
||||
protected final byte[] buffer;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code PlainBytes} instance by copying the provided byte
|
||||
* array.
|
||||
*
|
||||
* @param buffer the byte array to wrap
|
||||
*/
|
||||
public PlainBytes(final byte[] buffer) {
|
||||
Objects.requireNonNull(buffer, "PlainBytes cannot operate with null buffer");
|
||||
|
||||
this.buffer = Arrays.copyOf(buffer, buffer.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code PlainBytes} instance with a buffer of the specified
|
||||
* length.
|
||||
*
|
||||
* @param length the size of the internal byte buffer to allocate
|
||||
* @throws NegativeArraySizeException if {@code length} is negative
|
||||
*/
|
||||
protected PlainBytes(final int length) {
|
||||
this.buffer = new byte[length];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* {@code PlainBytes} represents the start of a {@link DataContent} chain and
|
||||
* therefore must not have any input. Calling this method with a
|
||||
* non-{@code null} argument will result in an exception.
|
||||
*
|
||||
*
|
||||
* @param input the preceding {@link DataContent}, which must be {@code null}
|
||||
* @throws IllegalArgumentException if {@code input} is not {@code null}
|
||||
*/
|
||||
@Override
|
||||
public void setInput(final DataContent input) {
|
||||
if (input != null) {
|
||||
throw new IllegalArgumentException(
|
||||
getClass().getName() + " must be the first element in a DataContent chain; it cannot accept input");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link InputStream} for reading the byte array.
|
||||
*
|
||||
* @return a new {@link ByteArrayInputStream}
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Override
|
||||
public InputStream getStream() throws IOException {
|
||||
LOG.log(Level.INFO, "opening byte array for read, length={0}", buffer.length);
|
||||
|
||||
return new ByteArrayInputStream(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the internal byte buffer.
|
||||
*
|
||||
* @return a new byte array containing the data from the internal buffer
|
||||
*/
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
return Arrays.copyOf(buffer, buffer.length);
|
||||
}
|
||||
}
|
||||
110
lib/src/main/java/zeroecho/data/processing/PlainFile.java
Normal file
110
lib/src/main/java/zeroecho/data/processing/PlainFile.java
Normal file
@@ -0,0 +1,110 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.data.processing;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import zeroecho.data.DataContent;
|
||||
import zeroecho.data.PlainContent;
|
||||
|
||||
/**
|
||||
* A {@link PlainContent} implementation that reads content from a file-like
|
||||
* URL.
|
||||
*/
|
||||
public final class PlainFile implements PlainContent {
|
||||
/**
|
||||
* Logger instance for the {@code PlainFile} class used for logging debug,
|
||||
* informational, and error messages. Initialized with the class name to provide
|
||||
* context-specific logging output.
|
||||
*/
|
||||
private static final Logger LOG = Logger.getLogger(PlainFile.class.getName());
|
||||
|
||||
/**
|
||||
* URL representing the location of the file associated with this instance.
|
||||
*
|
||||
* This URL may point to a file on the local file system, a remote resource, or
|
||||
* any other location accessible via the {@link java.net.URL} protocol. It is
|
||||
* final and set during construction.
|
||||
*/
|
||||
private final URL location;
|
||||
|
||||
/**
|
||||
* Constructs a PlainFile from the specified URL.
|
||||
*
|
||||
* @param location the file URL; must not be null
|
||||
*/
|
||||
public PlainFile(final URL location) {
|
||||
Objects.requireNonNull(location, "URL must not be null");
|
||||
|
||||
this.location = location;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* {@code PlainFile} represents the start of a {@link DataContent} chain and
|
||||
* therefore must not have any input. Calling this method with a
|
||||
* non-{@code null} argument will result in an exception.
|
||||
*
|
||||
* @param input the preceding {@link DataContent}, which must be {@code null}
|
||||
* @throws IllegalArgumentException if {@code input} is not {@code null}
|
||||
*/
|
||||
@Override
|
||||
public void setInput(final DataContent input) {
|
||||
if (input != null) {
|
||||
throw new IllegalArgumentException(
|
||||
getClass().getName() + " must be the first element in a DataContent chain; it cannot accept input");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link InputStream} for reading from the file.
|
||||
*
|
||||
* @return an {@link InputStream} from the URL
|
||||
* @throws IOException if an I/O error occurs opening the stream
|
||||
*/
|
||||
@Override
|
||||
public InputStream getStream() throws IOException {
|
||||
LOG.log(Level.INFO, "opening {0}", location);
|
||||
|
||||
return location.openStream();
|
||||
}
|
||||
|
||||
}
|
||||
126
lib/src/main/java/zeroecho/data/processing/PlainString.java
Normal file
126
lib/src/main/java/zeroecho/data/processing/PlainString.java
Normal file
@@ -0,0 +1,126 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.data.processing;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import zeroecho.data.DataContent;
|
||||
import zeroecho.data.PlainContent;
|
||||
|
||||
/**
|
||||
* A {@link PlainContent} implementation that wraps a UTF-8 string.
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public class PlainString implements PlainContent {
|
||||
/**
|
||||
* Logger instance for the {@code PlainString} class used to log informational,
|
||||
* debug, or error messages related to the class’s operations.
|
||||
*
|
||||
* The logger is initialized with the name of the {@code PlainString} class,
|
||||
* enabling targeted and organized logging output.
|
||||
*/
|
||||
private static final Logger LOG = Logger.getLogger(PlainString.class.getName());
|
||||
|
||||
/**
|
||||
* The plain text content represented by this instance.
|
||||
*/
|
||||
protected String str;
|
||||
|
||||
/**
|
||||
* Constructs a PlainString with the given string.
|
||||
*
|
||||
* @param str the plain text content; must not be null
|
||||
*/
|
||||
public PlainString(final String str) {
|
||||
Objects.requireNonNull(str, "plain string must not be null");
|
||||
|
||||
this.str = str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the original string content.
|
||||
*
|
||||
* @return the string
|
||||
*/
|
||||
@Override
|
||||
public String toText() {
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* {@code PlainString} represents the start of a {@link DataContent} chain and
|
||||
* therefore must not have any input. Calling this method with a
|
||||
* non-{@code null} argument will result in an exception.
|
||||
*
|
||||
* @param input the preceding {@link DataContent}, which must be {@code null}
|
||||
* @throws IllegalArgumentException if {@code input} is not {@code null}
|
||||
*/
|
||||
@Override
|
||||
public void setInput(final DataContent input) {
|
||||
if (input != null) {
|
||||
throw new IllegalArgumentException(
|
||||
getClass().getName() + " must be the first element in a DataContent chain; it cannot accept input");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link InputStream} of the UTF-8 encoded string.
|
||||
*
|
||||
* @return a {@link ByteArrayInputStream}
|
||||
*/
|
||||
@Override
|
||||
public InputStream getStream() {
|
||||
LOG.log(Level.FINE, "opening \"{0}\"", str);
|
||||
return new ByteArrayInputStream(toBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the UTF-8 encoded byte array of the string.
|
||||
*
|
||||
* @return a byte array representation
|
||||
*/
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
return str.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.data.processing;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import zeroecho.data.DataContent;
|
||||
import zeroecho.data.PlainContent;
|
||||
|
||||
/**
|
||||
* A {@link PlainContent} implementation that reclassifies an existing
|
||||
* {@link DataContent} instance as plain (unencrypted) without modifying the
|
||||
* underlying data stream.
|
||||
*
|
||||
* This is useful in cryptographic pipelines where the actual content remains
|
||||
* unchanged, but its classification as "plain" is semantically important for
|
||||
* subsequent processing.
|
||||
*/
|
||||
@Deprecated
|
||||
public class ReclassifiedPlain implements PlainContent {
|
||||
private DataContent previous;
|
||||
|
||||
/**
|
||||
* Creates a new {@code ReclassifiedPlain} instance without any initial input.
|
||||
*
|
||||
* The input must be later provided using {@link #setInput(DataContent)}.
|
||||
*/
|
||||
public ReclassifiedPlain() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code ReclassifiedPlain} with the specified upstream content.
|
||||
*
|
||||
* @param previous the upstream {@link DataContent} to be reclassified as plain
|
||||
*/
|
||||
public ReclassifiedPlain(final DataContent previous) {
|
||||
super();
|
||||
this.previous = previous;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the upstream {@link DataContent} that this plain content reclassifies.
|
||||
*
|
||||
* @param input the content to treat as plain
|
||||
*/
|
||||
@Override
|
||||
public void setInput(final DataContent input) {
|
||||
previous = input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the input stream from the upstream content without any
|
||||
* transformation.
|
||||
*
|
||||
* @return the raw input stream from the upstream content
|
||||
* @throws IOException if the underlying content fails to provide a stream
|
||||
*/
|
||||
@Override
|
||||
public InputStream getStream() throws IOException {
|
||||
if (previous == null) {
|
||||
throw new IllegalStateException("Input content is not set for ReclassifiedPlain");
|
||||
}
|
||||
return previous.getStream();
|
||||
}
|
||||
}
|
||||
165
lib/src/main/java/zeroecho/data/processing/SecretAesRandom.java
Normal file
165
lib/src/main/java/zeroecho/data/processing/SecretAesRandom.java
Normal file
@@ -0,0 +1,165 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.data.processing;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import conflux.Ctx;
|
||||
import zeroecho.data.DataContent;
|
||||
import zeroecho.util.aes.AesCipherType;
|
||||
import zeroecho.util.aes.AesMode;
|
||||
import zeroecho.util.aes.AesParameters;
|
||||
import zeroecho.util.aes.AesSupport;
|
||||
|
||||
/**
|
||||
* A {@link SecretRandom} implementation that generates a random AES key and IV
|
||||
* using the specified {@link AesMode} and {@link AesCipherType}.
|
||||
*
|
||||
* <p>
|
||||
* The combined key and IV are stored in the internal buffer and propagated to
|
||||
* the shared {@link Ctx} for use by other components that rely on AES
|
||||
* encryption or decryption.
|
||||
*
|
||||
* <p>
|
||||
* When the {@code SECRET} context value is updated, this class listens and
|
||||
* splits the secret value into separate key and IV portions, updating both the
|
||||
* internal buffer and the relevant context entries.
|
||||
*
|
||||
* <p>
|
||||
* Likewise, changes to the {@code KEY} context value will update the buffer
|
||||
* accordingly.
|
||||
*
|
||||
* <p>
|
||||
* This class does not perform encryption or decryption itself. Instead, it
|
||||
* makes the key/IV accessible to other components and exposes the raw input
|
||||
* stream.
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public class SecretAesRandom extends SecretRandom {
|
||||
private static final Logger LOG = Logger.getLogger(SecretAesRandom.class.getName());
|
||||
|
||||
private DataContent source;
|
||||
|
||||
/**
|
||||
* Creates a new {@code SecretAesRandom} instance by generating a random AES
|
||||
* key, IV, and optionally incorporating Additional Authenticated Data (AAD).
|
||||
*
|
||||
* <p>
|
||||
* The generated values are saved to the {@link Ctx} and stored internally in a
|
||||
* buffer. Listeners are registered to propagate updates between the
|
||||
* {@code SECRET}, {@code KEY}, and {@code IV}.
|
||||
* </p>
|
||||
*
|
||||
* @param mode the AES mode determining the key length (e.g., AES-128 or
|
||||
* AES-256)
|
||||
* @param cipherType the AES cipher type (e.g., CBC, GCM)
|
||||
* @param aad optional Additional Authenticated Data (AAD) associated
|
||||
* with the generated parameters; may be {@code null} if
|
||||
* unused
|
||||
*
|
||||
* @throws IllegalArgumentException if key or IV generation fails
|
||||
*
|
||||
* @see AesSupport#generateKeyAndIV(AesMode, AesCipherType, byte[])
|
||||
*/
|
||||
public SecretAesRandom(final AesMode mode, final AesCipherType cipherType, final byte[] aad) {
|
||||
super(mode.getKeyLengthBytes() + cipherType.getIVLengthBytes(), false);
|
||||
|
||||
final AesParameters params;
|
||||
try {
|
||||
params = AesSupport.generateKeyAndIV(mode, cipherType, aad);
|
||||
} catch (IllegalArgumentException | NoSuchAlgorithmException | NoSuchProviderException e) {
|
||||
LOG.logp(Level.WARNING, "SecretAesRandom", "SecretAesRandom", "Exception", e);
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
|
||||
System.arraycopy(params.key().getKey(), 0, buffer, 0, mode.getKeyLengthBytes());
|
||||
System.arraycopy(params.iv(), 0, buffer, mode.getKeyLengthBytes(), cipherType.getIVLengthBytes());
|
||||
|
||||
params.save(Ctx.INSTANCE);
|
||||
|
||||
Ctx.INSTANCE.addListener(SECRET, newValue -> {
|
||||
Ctx.INSTANCE.put(AesCommon.KEY, Arrays.copyOf(newValue, mode.getKeyLengthBytes()));
|
||||
Ctx.INSTANCE.put(AesCommon.IV, Arrays.copyOfRange(newValue, mode.getKeyLengthBytes(),
|
||||
mode.getKeyLengthBytes() + cipherType.getIVLengthBytes()));
|
||||
});
|
||||
|
||||
Ctx.INSTANCE.addListener(AesCommon.KEY, newValue -> {
|
||||
System.arraycopy(newValue, 0, buffer, 0, mode.getKeyLengthBytes());
|
||||
});
|
||||
|
||||
Ctx.INSTANCE.addListener(SECRET, newValue -> {
|
||||
System.arraycopy(newValue, 0, buffer, mode.getKeyLengthBytes(), cipherType.getIVLengthBytes());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the source {@link DataContent} for this instance.
|
||||
* <p>
|
||||
* This class does not modify or encrypt the input data—it only passes through
|
||||
* the stream as-is via {@link #getStream()}.
|
||||
*
|
||||
* @param input the data source to use (must not be null)
|
||||
* @throws NullPointerException if {@code input} is null
|
||||
*/
|
||||
@Override
|
||||
public void setInput(final DataContent input) {
|
||||
Objects.requireNonNull(input, "input must not be null");
|
||||
|
||||
source = input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the raw input stream from the previously set {@link DataContent}.
|
||||
* <p>
|
||||
* This stream is not encrypted or modified. Consumers are expected to use the
|
||||
* AES key and IV made available through the {@link Ctx} for encryption or
|
||||
* decryption operations.
|
||||
*
|
||||
* @return the raw input stream
|
||||
* @throws IOException if the underlying stream cannot be opened
|
||||
*/
|
||||
@Override
|
||||
public InputStream getStream() throws IOException {
|
||||
return source.getStream();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.data.processing;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.SequenceInputStream;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import conflux.Ctx;
|
||||
import zeroecho.data.DataContent;
|
||||
import zeroecho.data.SecretContent;
|
||||
import zeroecho.util.IOUtil;
|
||||
import zeroecho.util.aes.AesCipherType;
|
||||
import zeroecho.util.aes.AesMode;
|
||||
import zeroecho.util.aes.AesSupport;
|
||||
import zeroecho.util.aes.DerivedAesParameters;
|
||||
|
||||
/**
|
||||
* A {@link SecretContent} implementation that uses a password-based key
|
||||
* derivation function (PBKDF) to produce AES encryption parameters (key and
|
||||
* IV).
|
||||
*
|
||||
* <p>
|
||||
* This class supports both encryption and decryption:
|
||||
* <ul>
|
||||
* <li><strong>Encryption mode</strong>:
|
||||
* <ul>
|
||||
* <li>Derives the AES key and IV immediately upon construction.</li>
|
||||
* <li>Stores the derived parameters, salt, and iteration count in a shared
|
||||
* {@link Ctx}.</li>
|
||||
* <li>The output stream prepends salt and iteration count as a header.</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li><strong>Decryption mode</strong>:
|
||||
* <ul>
|
||||
* <li>Expects the input stream to start with a salt and iteration count
|
||||
* header.</li>
|
||||
* <li>Key and IV are re-derived dynamically in {@link #getStream()} using the
|
||||
* extracted values.</li>
|
||||
* <li>Mode and cipher type must be known in advance and are stored in
|
||||
* {@link Ctx} at construction.</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* Derived parameters and configuration values are stored in {@link Ctx} using
|
||||
* standard keys like {@code KEY}, {@code IV}, {@code SALT}, {@code ITERATIONS},
|
||||
* {@code MODE}, {@code CIPHER_TYPE}, and {@code BLOCK_SIZE}.
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public class SecretDerivedAesParameters implements SecretContent {
|
||||
private static final Logger LOG = Logger.getLogger(SecretDerivedAesParameters.class.getName());
|
||||
|
||||
private DataContent source;
|
||||
private final String password;
|
||||
private final boolean encrypt;
|
||||
|
||||
/**
|
||||
* Constructs an AES key derivation context with default cipher type
|
||||
* ({@code CBC}).
|
||||
* <p>
|
||||
* This constructor performs full setup for encryption or preps configuration
|
||||
* for decryption.
|
||||
* </p>
|
||||
*
|
||||
* @param password the password used for PBKDF key derivation (must not be
|
||||
* null)
|
||||
* @param iterations the number of PBKDF iterations
|
||||
* @param mode the AES mode (e.g., {@code AES-128}, {@code AES-256})
|
||||
* @param encrypt true for encryption (derive key now), false for decryption
|
||||
* (key derived later)
|
||||
* @throws IllegalArgumentException if arguments are invalid
|
||||
* @throws InvalidKeySpecException if key derivation fails (only in encryption
|
||||
* mode)
|
||||
*/
|
||||
public SecretDerivedAesParameters(final String password, final int iterations, final AesMode mode,
|
||||
final boolean encrypt) throws InvalidKeySpecException {
|
||||
this(password, iterations, null, mode, AesCipherType.CBC, encrypt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an AES key derivation context with explicit cipher type and
|
||||
* optional Additional Authenticated Data (AAD).
|
||||
*
|
||||
* <p>
|
||||
* In encryption mode, key derivation is performed immediately using PBKDF2, and
|
||||
* the derived parameters (including AAD) are saved in {@link Ctx}. In
|
||||
* decryption mode, the AES mode and cipher type are stored in {@link Ctx}, but
|
||||
* key derivation is deferred until {@link #getStream()} reads the salt and
|
||||
* iteration count from the input stream.
|
||||
* </p>
|
||||
*
|
||||
* @param password the password used for PBKDF2 key derivation; must not be
|
||||
* {@code null}
|
||||
* @param iterations the PBKDF2 iteration count; should be at least 100,000 for
|
||||
* adequate security
|
||||
* @param aad optional Additional Authenticated Data (AAD); may be
|
||||
* {@code null} if unused
|
||||
* @param mode the AES mode (e.g., {@code AES-128}, {@code AES-256}); must
|
||||
* not be {@code null}
|
||||
* @param cipherType the AES cipher type (e.g., {@code CBC}, {@code GCM}); must
|
||||
* not be {@code null}
|
||||
* @param encrypt {@code true} for encryption mode (immediate derivation),
|
||||
* {@code false} for decryption mode
|
||||
*
|
||||
* @throws IllegalArgumentException if any argument is invalid
|
||||
* @throws InvalidKeySpecException if key derivation fails (only in encryption
|
||||
* mode)
|
||||
*
|
||||
* @see AesSupport#deriveKeyAndIv(String, int, AesMode, byte[], AesCipherType)
|
||||
*/
|
||||
public SecretDerivedAesParameters(final String password, final int iterations, final byte[] aad, final AesMode mode,
|
||||
final AesCipherType cipherType, final boolean encrypt) throws InvalidKeySpecException {
|
||||
this.password = password;
|
||||
this.encrypt = encrypt;
|
||||
|
||||
if (encrypt) {
|
||||
final DerivedAesParameters params = AesSupport.deriveKeyAndIv(password, iterations, mode, aad, cipherType);
|
||||
|
||||
params.save(Ctx.INSTANCE);
|
||||
} else {
|
||||
Ctx.INSTANCE.put(AesCommon.CIPHER_TYPE, cipherType);
|
||||
Ctx.INSTANCE.put(AesCommon.MODE, mode);
|
||||
if (aad != null) {
|
||||
Ctx.INSTANCE.put(AesCommon.AAD, aad);
|
||||
}
|
||||
}
|
||||
Ctx.INSTANCE.put(AesCommon.BLOCK_SIZE, AesSupport.BLOCK_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the data source for encryption or decryption.
|
||||
* <p>
|
||||
* The actual processing mode depends on the {@code encrypt} flag provided
|
||||
* during construction:
|
||||
* <ul>
|
||||
* <li><b>Encryption</b>: the stream will prepend salt and iteration header
|
||||
* before actual data.</li>
|
||||
* <li><b>Decryption</b>: the stream will read salt and iteration header to
|
||||
* re-derive the key and IV.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param input the input content to wrap (must not be null)
|
||||
* @throws NullPointerException if {@code input} is null
|
||||
*/
|
||||
@Override
|
||||
public void setInput(final DataContent input) {
|
||||
Objects.requireNonNull(input, "input must not be null");
|
||||
|
||||
source = input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stream that wraps the input content, handling AES encryption or
|
||||
* decryption.
|
||||
*
|
||||
* <ul>
|
||||
* <li><b>Encryption mode</b>:
|
||||
* <ul>
|
||||
* <li>Prepends a header (salt length, salt bytes, and iteration count).</li>
|
||||
* <li>Returns a concatenated stream: header + encrypted content.</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li><b>Decryption mode</b>:
|
||||
* <ul>
|
||||
* <li>Reads the header (salt and iterations) from the input stream.</li>
|
||||
* <li>Re-derives the AES key and IV using stored password, mode, and cipher
|
||||
* type.</li>
|
||||
* <li>Returns the input stream positioned after the header.</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* </ul>
|
||||
*
|
||||
* @return the wrapped input stream with header handling
|
||||
* @throws IOException if stream reading or header parsing fails
|
||||
* @throws IllegalStateException if key derivation fails during decryption
|
||||
*/
|
||||
@Override
|
||||
public InputStream getStream() throws IOException {
|
||||
if (encrypt) {
|
||||
// encryption
|
||||
final byte[] salt = Ctx.INSTANCE.get(DerivedAesParameters.SALT);
|
||||
final int iterations = Ctx.INSTANCE.get(DerivedAesParameters.ITERATIONS);
|
||||
final ByteArrayOutputStream header = new ByteArrayOutputStream();
|
||||
IOUtil.write(header, salt);
|
||||
IOUtil.writePack7I(header, iterations);
|
||||
|
||||
// salt is not a secret, so we can log it
|
||||
if (LOG.isLoggable(Level.INFO)) {
|
||||
LOG.log(Level.INFO, "processing AES (iterations={0} salt={1}",
|
||||
new Object[] { iterations, Arrays.toString(salt) });
|
||||
}
|
||||
|
||||
final InputStream encryptedStream = source.getStream();
|
||||
return new SequenceInputStream(new ByteArrayInputStream(header.toByteArray()), encryptedStream);
|
||||
} else {
|
||||
// decryption
|
||||
// Read salt and iteration count from input stream header
|
||||
final InputStream in = source.getStream();
|
||||
final byte[] salt = IOUtil.read(in, 4 * AesSupport.BLOCK_SIZE);
|
||||
final int iterations = IOUtil.readPack7I(in);
|
||||
|
||||
if (LOG.isLoggable(Level.INFO)) {
|
||||
LOG.log(Level.INFO, "processing AES (iterations={0} salt={1})",
|
||||
new Object[] { iterations, Arrays.toString(salt) });
|
||||
}
|
||||
|
||||
try {
|
||||
final AesMode mode = Ctx.INSTANCE.get(AesCommon.MODE);
|
||||
final AesCipherType cipherType = Ctx.INSTANCE.get(AesCommon.CIPHER_TYPE);
|
||||
final byte[] aad = Ctx.INSTANCE.get(AesCommon.AAD);
|
||||
final DerivedAesParameters params = AesSupport.rederiveKeyAndIv(password, salt, iterations, mode, aad,
|
||||
cipherType);
|
||||
Ctx.INSTANCE.put(AesCommon.KEY, params.key().getKey());
|
||||
Ctx.INSTANCE.put(AesCommon.IV, params.iv());
|
||||
Ctx.INSTANCE.put(DerivedAesParameters.SALT, params.salt());
|
||||
Ctx.INSTANCE.put(DerivedAesParameters.ITERATIONS, iterations);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
LOG.logp(Level.WARNING, "PasswordBasedAesDecryptor", "getStream", "Exception", e);
|
||||
throw new IllegalStateException("Failed to generate key: invalid state", e);
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
/**
|
||||
* 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 zeroecho.data.processing;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.SequenceInputStream;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import conflux.Ctx;
|
||||
import zeroecho.data.DataContent;
|
||||
import zeroecho.data.SecretContent;
|
||||
import zeroecho.util.IOUtil;
|
||||
import zeroecho.util.aes.AesCipherType;
|
||||
import zeroecho.util.aes.AesMode;
|
||||
import zeroecho.util.aes.AesSupport;
|
||||
import zeroecho.util.aes.BasicAesParameters;
|
||||
import zeroecho.util.asymmetric.KEMAsymmetricContext;
|
||||
|
||||
/**
|
||||
* Provides AES encryption and decryption parameter management using a Key
|
||||
* Encapsulation Mechanism (KEM).
|
||||
* <p>
|
||||
* This class integrates a {@link KEMAsymmetricContext} to securely derive AES
|
||||
* keys and initialization vectors, supporting both encryption and decryption
|
||||
* workflows:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li><strong>Encryption mode:</strong> Uses the KEM generator to derive AES
|
||||
* parameters, prepends the encapsulated key and IV to the data stream, and
|
||||
* stores them in {@link Ctx} for downstream processing.</li>
|
||||
* <li><strong>Decryption mode:</strong> Reads the encapsulated key and IV from
|
||||
* the input stream, performs KEM decapsulation to reconstruct the AES key, and
|
||||
* populates {@link Ctx} for subsequent AES operations.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* The class implements {@link SecretContent} and can be chained into
|
||||
* {@code DataContent} pipelines for streaming encryption/decryption.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Usage</h2> <pre>{@code
|
||||
* SecretKEMAesParameters params =
|
||||
* new SecretKEMAesParameters(kemContext, aesMode, cipherType, aad);
|
||||
* params.setInput(dataContent);
|
||||
* InputStream stream = params.getStream();
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* Thread-safety: Instances are not thread-safe. Each encryption or decryption
|
||||
* pipeline must use its own {@code SecretKEMAesParameters}.
|
||||
* </p>
|
||||
*/
|
||||
public class SecretKEMAesParameters implements SecretContent {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(SecretKEMAesParameters.class.getName());
|
||||
|
||||
/** KEM context used for encapsulation/decapsulation. */
|
||||
private final KEMAsymmetricContext kemContext;
|
||||
|
||||
/** Indicates encryption (true) or decryption (false). */
|
||||
private final boolean encrypt;
|
||||
|
||||
/** Data source for processing. */
|
||||
private DataContent source;
|
||||
|
||||
/**
|
||||
* Creates a new KEM-based AES parameter provider.
|
||||
*
|
||||
* <p>
|
||||
* Depending on whether the provided {@link KEMAsymmetricContext} is configured
|
||||
* for encryption or decryption, this constructor initializes the internal
|
||||
* context and populates {@link Ctx} with the required AES parameters.
|
||||
* </p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>In encryption mode, derives AES key material and IV using KEM and stores
|
||||
* them in {@link Ctx}.</li>
|
||||
* <li>In decryption mode, records the cipher type, AES mode, and optional
|
||||
* Additional Authenticated Data (AAD) into {@link Ctx}.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param kemContext the KEM context used for encapsulation/decapsulation; must
|
||||
* not be {@code null}
|
||||
* @param mode the AES mode specifying key length; must not be
|
||||
* {@code null}
|
||||
* @param cipherType the AES cipher type (CBC, GCM, etc.); must not be
|
||||
* {@code null}
|
||||
* @param aad optional Additional Authenticated Data (AAD); may be
|
||||
* {@code null}
|
||||
* @throws NullPointerException if any required parameter is {@code null}
|
||||
*/
|
||||
public SecretKEMAesParameters(final KEMAsymmetricContext kemContext, final AesMode mode,
|
||||
final AesCipherType cipherType, final byte[] aad) {
|
||||
|
||||
this.kemContext = Objects.requireNonNull(kemContext, "kemContext must not be null");
|
||||
this.encrypt = kemContext.extractor() == null;
|
||||
|
||||
if (encrypt) {
|
||||
final BasicAesParameters params = AesSupport.deriveFromKEM(kemContext, mode, cipherType, aad);
|
||||
|
||||
params.save(Ctx.INSTANCE);
|
||||
} else {
|
||||
Ctx.INSTANCE.put(AesCommon.CIPHER_TYPE, Objects.requireNonNull(cipherType, "cipherType must not be null"));
|
||||
Ctx.INSTANCE.put(AesCommon.MODE, Objects.requireNonNull(mode, "mode must not be null"));
|
||||
if (aad != null) {
|
||||
Ctx.INSTANCE.put(AesCommon.AAD, aad);
|
||||
}
|
||||
}
|
||||
Ctx.INSTANCE.put(AesCommon.BLOCK_SIZE, AesSupport.BLOCK_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the input data content for subsequent processing.
|
||||
* <p>
|
||||
* This must be called prior to invoking {@link #getStream()}.
|
||||
* </p>
|
||||
*
|
||||
* @param input the non-null input content to be processed
|
||||
* @throws NullPointerException if {@code input} is {@code null}
|
||||
*/
|
||||
@Override
|
||||
public void setInput(final DataContent input) {
|
||||
this.source = Objects.requireNonNull(input, "input must not be null");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link InputStream} configured for either encryption or
|
||||
* decryption.
|
||||
*
|
||||
* <ul>
|
||||
* <li>In encryption mode, the stream begins with a header containing the
|
||||
* encapsulated KEM data and IV, followed by the original content.</li>
|
||||
* <li>In decryption mode, the method reads the encapsulated KEM data and IV,
|
||||
* derives the AES key, updates {@link Ctx}, and returns the remaining
|
||||
* stream.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return a prepared {@link InputStream} suitable for AES processing
|
||||
* @throws IOException if an error occurs while preparing the stream
|
||||
*/
|
||||
@Override
|
||||
public InputStream getStream() throws IOException {
|
||||
return encrypt ? buildEncryptionStream() : buildDecryptionStream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an encryption stream by generating and prepending a KEM header.
|
||||
* <p>
|
||||
* The header contains:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>Encapsulated KEM data (used for key reconstruction during
|
||||
* decryption).</li>
|
||||
* <li>Initialization Vector (IV) required by the AES cipher.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return an {@link InputStream} combining the header and original data content
|
||||
* @throws IOException if writing to the header fails
|
||||
*/
|
||||
private InputStream buildEncryptionStream() throws IOException {
|
||||
ByteArrayOutputStream header = new ByteArrayOutputStream();
|
||||
|
||||
byte[] encapsulated = kemContext.getEncapsulation();
|
||||
byte[] iv = Ctx.INSTANCE.get(AesCommon.IV);
|
||||
IOUtil.write(header, encapsulated);
|
||||
IOUtil.write(header, iv);
|
||||
|
||||
if (LOG.isLoggable(Level.FINE)) {
|
||||
LOG.fine("Encryption header: encap=" + encapsulated.length + " iv=" + iv.length);
|
||||
}
|
||||
|
||||
// --- Combine header + original stream ---
|
||||
return new SequenceInputStream(new ByteArrayInputStream(header.toByteArray()), source.getStream());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a decryption stream by consuming the KEM header and restoring AES
|
||||
* parameters.
|
||||
*
|
||||
* <p>
|
||||
* Steps performed:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>Reads encapsulated KEM data and the IV from the input stream.</li>
|
||||
* <li>Performs KEM decapsulation to rederive the AES key.</li>
|
||||
* <li>Stores the derived key and IV into {@link Ctx} for downstream AES
|
||||
* operations.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return the remaining {@link InputStream} after header consumption
|
||||
* @throws IOException if reading from the stream fails
|
||||
* @throws IllegalArgumentException if the IV length does not match the expected
|
||||
* size
|
||||
*/
|
||||
private InputStream buildDecryptionStream() throws IOException {
|
||||
InputStream in = source.getStream();
|
||||
AesCipherType cipherType = Ctx.INSTANCE.get(AesCommon.CIPHER_TYPE);
|
||||
|
||||
// --- Read encapsulated & IV ---
|
||||
byte[] encapsulated = IOUtil.read(in, 30_000);
|
||||
byte[] iv = IOUtil.read(in, cipherType.getIVLengthBytes());
|
||||
|
||||
if (iv.length != cipherType.getIVLengthBytes()) {
|
||||
throw new IllegalArgumentException(cipherType + " requires IV length " + cipherType.getIVLengthBytes()
|
||||
+ ", but only IV with " + iv.length + "available");
|
||||
}
|
||||
|
||||
// --- KEM decapsulation ---
|
||||
byte[] key = AesSupport.rederiveFromKEM(kemContext, encapsulated, Ctx.INSTANCE.get(AesCommon.MODE));
|
||||
|
||||
Ctx.INSTANCE.put(AesCommon.KEY, key);
|
||||
Ctx.INSTANCE.put(AesCommon.IV, iv);
|
||||
|
||||
return in;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
/**
|
||||
* 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 zeroecho.data.processing;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.SequenceInputStream;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.security.auth.DestroyFailedException;
|
||||
|
||||
import org.bouncycastle.crypto.DataLengthException;
|
||||
|
||||
import conflux.Ctx;
|
||||
import conflux.Key;
|
||||
import zeroecho.data.DataContent;
|
||||
import zeroecho.data.EncryptedContent;
|
||||
import zeroecho.util.IOUtil;
|
||||
import zeroecho.util.KeySupport;
|
||||
import zeroecho.util.RandomSupport;
|
||||
import zeroecho.util.asymmetric.AsymmetricContext;
|
||||
import zeroecho.util.asymmetric.AsymmetricStreamBuilder;
|
||||
import zeroecho.util.asymmetric.ClassicAsymmetricContext;
|
||||
import zeroecho.util.asymmetric.KEMAsymmetricContext;
|
||||
import zeroecho.util.asymmetric.SignatureAsymmetricContext;
|
||||
|
||||
/**
|
||||
* A cryptographic content wrapper that supports multi-recipient encryption and
|
||||
* single-recipient decryption using asymmetric keys.
|
||||
* <p>
|
||||
* {@code SecretMultiRecipientCryptor} operates on {@link DataContent} instances
|
||||
* and provides an {@link InputStream} that transparently performs encryption or
|
||||
* decryption depending on the configured keys and input content type:
|
||||
*
|
||||
* <ul>
|
||||
* <li>When constructed with public keys, the cryptor operates in <b>encryption
|
||||
* mode</b>: a secret is encrypted once for each recipient and prepended as a
|
||||
* header before the actual content stream.</li>
|
||||
* <li>When constructed with a private key, the cryptor operates in
|
||||
* <b>decryption mode</b>: the encrypted secret is extracted and decrypted from
|
||||
* the header, and made available in the runtime context.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* This class uses a shared key reference ({@code SECRET}) to store the
|
||||
* recovered or generated secret in the global context ({@link Ctx}) for
|
||||
* downstream use.
|
||||
*
|
||||
* <p>
|
||||
* Logging is kept at an operational level and avoids exposing sensitive data.
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public class SecretMultiRecipientCryptor implements EncryptedContent {
|
||||
private static final Logger LOG = Logger.getLogger(SecretMultiRecipientCryptor.class.getName());
|
||||
|
||||
private final PublicKey recipient[];
|
||||
private final PublicKey decoy[];
|
||||
private final PrivateKey privKey;
|
||||
private DataContent source;
|
||||
/**
|
||||
* A typed key used for storing or retrieving secret data as a byte array.
|
||||
* <p>
|
||||
* The key identifier is "secret.data" and it expects values of type
|
||||
* {@code byte[]}.
|
||||
* </p>
|
||||
*/
|
||||
protected static final Key<byte[]> SECRET = Key.of("secret.data", byte[].class);
|
||||
|
||||
private record TaggedPublicKey(boolean decoy, PublicKey publicKey) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code SecretMultiRecipientCryptor} configured for
|
||||
* encryption using the specified recipients' public keys and optional decoy
|
||||
* public keys.
|
||||
* <p>
|
||||
* The recipient public keys are used to encrypt the secret key for legitimate
|
||||
* recipients. The decoy public keys are included to provide plausible
|
||||
* deniability or to obfuscate the true set of recipients, but do not correspond
|
||||
* to actual decryption capabilities.
|
||||
* </p>
|
||||
*
|
||||
* @param recipient an array of {@link PublicKey} objects representing the
|
||||
* legitimate recipients of the encrypted data
|
||||
* @param decoy an array of {@link PublicKey} objects representing decoy
|
||||
* recipients that cannot decrypt the data; may be empty or
|
||||
* {@code null} if no decoys are desired
|
||||
*/
|
||||
public SecretMultiRecipientCryptor(final PublicKey[] recipient, final PublicKey[] decoy) { // NOPMD
|
||||
super();
|
||||
this.recipient = (recipient == null) ? new PublicKey[0] : recipient;
|
||||
this.decoy = (decoy == null) ? new PublicKey[0] : decoy;
|
||||
this.privKey = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code SecretMultiRecipientCryptor} configured for
|
||||
* decryption with the provided private key.
|
||||
*
|
||||
* @param privKey the private key for decrypting the secret
|
||||
*/
|
||||
public SecretMultiRecipientCryptor(final PrivateKey privKey) {
|
||||
super();
|
||||
this.recipient = null;
|
||||
this.decoy = null;
|
||||
this.privKey = privKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the input {@link DataContent} that this cryptor will wrap. This must be
|
||||
* called before invoking {@link #getStream()}.
|
||||
*
|
||||
* @param input the data content to be encrypted or decrypted; must not be
|
||||
* {@code null}
|
||||
* @throws NullPointerException if {@code input} is {@code null}
|
||||
*/
|
||||
@Override
|
||||
public void setInput(final DataContent input) {
|
||||
Objects.requireNonNull(input, "input must not be null");
|
||||
|
||||
source = input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link InputStream} representing either encrypted or decrypted
|
||||
* data based on the configured mode:
|
||||
*
|
||||
* <ul>
|
||||
* <li><b>Decryption Mode</b> (if a private key is set): Attempts to extract and
|
||||
* decrypt the shared secret from the stream header. The decrypted secret is
|
||||
* validated by hash and stored in the context under {@link #SECRET}. The
|
||||
* remaining stream is returned for use.</li>
|
||||
* <li><b>Encryption Mode</b> (if public keys are set): Encrypts the shared
|
||||
* secret separately for each recipient, constructs a header from these
|
||||
* encrypted blocks, appends a zero-length marker, and returns a stream
|
||||
* combining the header and the original content.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return an input stream for the processed content
|
||||
* @throws IOException if the secret cannot be found or decrypted in
|
||||
* decryption mode, or if an I/O error occurs
|
||||
* @throws IllegalStateException if {@link #setInput(DataContent)} was not
|
||||
* called before invocation
|
||||
*/
|
||||
@Override
|
||||
public InputStream getStream() throws IOException {
|
||||
if (privKey != null) {
|
||||
return decrypt();
|
||||
} else {
|
||||
return encrypt();
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream encrypt() throws IOException {
|
||||
final ByteArrayOutputStream pubChunk = new ByteArrayOutputStream();
|
||||
final ByteArrayOutputStream header = new ByteArrayOutputStream();
|
||||
|
||||
// valid secret
|
||||
final ByteArrayOutputStream secretItem = new ByteArrayOutputStream();
|
||||
final byte[] secret = Ctx.INSTANCE.get(SECRET);
|
||||
IOUtil.writePack7I(secretItem, Arrays.hashCode(secret));
|
||||
secretItem.write(secret);
|
||||
final byte hashAndSecret[] = secretItem.toByteArray();
|
||||
final ByteArrayInputStream secretStream = new ByteArrayInputStream(hashAndSecret);
|
||||
|
||||
// false secret - decoy
|
||||
final ByteArrayOutputStream decoyItem = new ByteArrayOutputStream();
|
||||
final byte[] decoysecret = RandomSupport.generateRandom(secret.length);
|
||||
IOUtil.writePack7I(decoyItem, Arrays.hashCode(decoysecret));
|
||||
decoyItem.write(decoysecret);
|
||||
final byte hashAndDecoy[] = decoyItem.toByteArray();
|
||||
final ByteArrayInputStream decoyStream = new ByteArrayInputStream(hashAndDecoy);
|
||||
|
||||
// prepare
|
||||
final List<TaggedPublicKey> items = new ArrayList<>();
|
||||
for (PublicKey item : recipient) {
|
||||
items.add(new TaggedPublicKey(false, item));
|
||||
}
|
||||
for (PublicKey item : decoy) {
|
||||
items.add(new TaggedPublicKey(true, item));
|
||||
}
|
||||
Collections.shuffle(items, RandomSupport.getRandom());
|
||||
|
||||
final byte[] aad = new byte[4];
|
||||
int aadCounter = 0;
|
||||
for (TaggedPublicKey item : items) {
|
||||
aad[0] = (byte) (aadCounter >>> 24);
|
||||
aad[1] = (byte) (aadCounter >>> 16);
|
||||
aad[2] = (byte) (aadCounter >>> 8);
|
||||
aad[3] = (byte) aadCounter;
|
||||
aadCounter++;
|
||||
|
||||
try (AsymmetricContext ctx = KeySupport.fromKey(item.publicKey())) {
|
||||
constructEncryptor(ctx).withInputStream(item.decoy() ? decoyStream : secretStream).withKEMCipherAad(aad)
|
||||
.buildEncryptingStream().transferTo(pubChunk);
|
||||
IOUtil.write(header, pubChunk.toByteArray());
|
||||
pubChunk.reset();
|
||||
if (item.decoy()) {
|
||||
decoyStream.reset();
|
||||
} else {
|
||||
secretStream.reset();
|
||||
}
|
||||
} catch (DestroyFailedException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
IOUtil.writePack7I(header, 0);
|
||||
if (LOG.isLoggable(Level.INFO)) {
|
||||
LOG.log(Level.INFO, "header for {0} recipients, {1} decoys, size {2} bytes",
|
||||
new Object[] { recipient.length, decoy.length, header.size() });
|
||||
}
|
||||
|
||||
final InputStream encryptedStream = source.getStream();
|
||||
return new SequenceInputStream(new ByteArrayInputStream(header.toByteArray()), encryptedStream);
|
||||
}
|
||||
|
||||
private AsymmetricStreamBuilder constructEncryptor(AsymmetricContext context) {
|
||||
return switch (context) {
|
||||
case ClassicAsymmetricContext ctx -> // NOPMD
|
||||
AsymmetricStreamBuilder.newBuilder().withCipherEngine(ctx.cipher()).withKey(ctx.key());
|
||||
case KEMAsymmetricContext ctx -> // NOPMD
|
||||
AsymmetricStreamBuilder.newBuilder().withKEMGenerator(ctx.generator()).withKey(ctx.key());
|
||||
case SignatureAsymmetricContext ctx -> // NOPMD
|
||||
throw new IllegalArgumentException(ctx.toString() + " cannot be used for encryption");
|
||||
};
|
||||
}
|
||||
|
||||
private InputStream decrypt() throws IOException { // NOPMD
|
||||
final InputStream in = source.getStream();
|
||||
try (AsymmetricContext ctx = KeySupport.fromKey(privKey)) {
|
||||
final AsymmetricStreamBuilder asb = constructDecryptor(ctx);
|
||||
final ByteArrayOutputStream decrypted = new ByteArrayOutputStream();
|
||||
|
||||
final byte[] aad = new byte[4];
|
||||
int aadCounter = 0;
|
||||
for (byte pubChunkByte[] = IOUtil.read(in, 20 * 1024); pubChunkByte.length > 0; pubChunkByte = IOUtil
|
||||
.read(in, 20 * 1024)) {
|
||||
if (decrypted.size() == 0) {
|
||||
// the secret was not yet found
|
||||
aad[0] = (byte) (aadCounter >>> 24);
|
||||
aad[1] = (byte) (aadCounter >>> 16);
|
||||
aad[2] = (byte) (aadCounter >>> 8);
|
||||
aad[3] = (byte) aadCounter;
|
||||
aadCounter++;
|
||||
|
||||
try {
|
||||
final InputStream is = asb.withInputStream(new ByteArrayInputStream(pubChunkByte)) // NOPMD
|
||||
.withKEMCipherAad(aad).buildDecryptingStream();
|
||||
final int hash = IOUtil.readPack7I(is);
|
||||
is.transferTo(decrypted);
|
||||
final byte[] candidate = decrypted.toByteArray();
|
||||
if (hash != Arrays.hashCode(candidate)) {
|
||||
decrypted.reset();
|
||||
}
|
||||
} catch (IOException | DataLengthException | DestroyFailedException e) {
|
||||
if (LOG.isLoggable(Level.INFO)) {
|
||||
LOG.info(e.toString());
|
||||
}
|
||||
decrypted.reset();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// thrown by PQ algos when something is wrong
|
||||
if (LOG.isLoggable(Level.INFO)) {
|
||||
LOG.info("Exception from PQ " + e);
|
||||
}
|
||||
decrypted.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (LOG.isLoggable(Level.INFO)) {
|
||||
LOG.log(Level.INFO, "header with {0} recipients, secret size {1} bytes",
|
||||
new Object[] { aadCounter, decrypted.size() });
|
||||
}
|
||||
|
||||
if (decrypted.size() == 0) {
|
||||
throw new IOException("secret was not found in the header");
|
||||
}
|
||||
|
||||
Ctx.INSTANCE.put(SECRET, decrypted.toByteArray());
|
||||
|
||||
return in;
|
||||
}
|
||||
}
|
||||
|
||||
private AsymmetricStreamBuilder constructDecryptor(AsymmetricContext context) {
|
||||
return switch (context) {
|
||||
case ClassicAsymmetricContext ctx -> // NOPMD
|
||||
AsymmetricStreamBuilder.newBuilder().withKey(ctx.key()).withCipherEngine(ctx.cipher());
|
||||
case KEMAsymmetricContext ctx -> // NOPMD
|
||||
AsymmetricStreamBuilder.newBuilder().withKEMExtractor(ctx.extractor()).withKey(ctx.key());
|
||||
case SignatureAsymmetricContext ctx -> // NOPMD
|
||||
throw new IllegalArgumentException(ctx.toString() + " cannot be used for decryption");
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.data.processing;
|
||||
|
||||
import conflux.Ctx;
|
||||
import conflux.Key;
|
||||
import zeroecho.data.SecretContent;
|
||||
import zeroecho.util.Password;
|
||||
|
||||
/**
|
||||
* A {@link SecretContent} implementation that encapsulates a passwordKey
|
||||
* string. This class extends {@link PlainString} and enforces immutability of
|
||||
* the passwordKey after construction.
|
||||
* <p>
|
||||
* Passwords can be generated randomly or provided explicitly. Once set,
|
||||
* attempts to change the passwordKey via parameters will cause an exception.
|
||||
* <p>
|
||||
* This class supports applying parameters from a map and collecting its state
|
||||
* back into a map, using the key {@link #PASSWORD}.
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public class SecretPassword extends PlainString implements SecretContent {
|
||||
private final Key<String> PASSWORD = Key.of("secret.password", String.class);
|
||||
|
||||
/**
|
||||
* Constructs a {@code SecretPassword} with a randomly generated printable
|
||||
* passwordKey of the specified length.
|
||||
*
|
||||
* @param length the length of the generated passwordKey
|
||||
* @throws IllegalArgumentException if {@code length} is less than or equal to
|
||||
* zero
|
||||
*/
|
||||
public SecretPassword(final int length) {
|
||||
super(Password.generatePrintablePassword(length));
|
||||
Ctx.INSTANCE.put(PASSWORD, str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code SecretPassword} wrapping the specified passwordKey
|
||||
* string. The passwordKey may be {@code null}.
|
||||
*
|
||||
* @param password the passwordKey string, or {@code null}
|
||||
*/
|
||||
public SecretPassword(final String password) {
|
||||
super(password);
|
||||
Ctx.INSTANCE.put(PASSWORD, str);
|
||||
}
|
||||
}
|
||||
148
lib/src/main/java/zeroecho/data/processing/SecretRandom.java
Normal file
148
lib/src/main/java/zeroecho/data/processing/SecretRandom.java
Normal file
@@ -0,0 +1,148 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.data.processing;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import conflux.Ctx;
|
||||
import conflux.Key;
|
||||
import zeroecho.data.SecretContent;
|
||||
import zeroecho.util.Password;
|
||||
import zeroecho.util.RandomSupport;
|
||||
|
||||
/**
|
||||
* A secure byte container that holds cryptographically random data, intended
|
||||
* for use in encryption-related operations such as secret generation, key
|
||||
* material storage, or secure content handling.
|
||||
* <p>
|
||||
* {@code SecretRandom} extends {@link PlainBytes} and implements
|
||||
* {@link SecretContent}, offering additional semantics for managing sensitive
|
||||
* data. All instances are stored in a shared context via a predefined secret
|
||||
* key reference to allow later retrieval or secure reuse.
|
||||
* <p>
|
||||
* Multiple constructors are provided to support different secure initialization
|
||||
* modes:
|
||||
* <ul>
|
||||
* <li>Randomly filled buffer of a specified size</li>
|
||||
* <li>Wrapping an existing secure byte array</li>
|
||||
* <li>Seed-based randomization using a secure hash and salt</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Logging is limited to operational messages and avoids revealing any sensitive
|
||||
* content.
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public class SecretRandom extends PlainBytes implements SecretContent {
|
||||
/**
|
||||
* Logger instance scoped to this class, used for operational messages related
|
||||
* to secure random number generation and management.
|
||||
*
|
||||
* Note: Care is taken not to log the sensitive content of the secret bytes.
|
||||
*/
|
||||
private static final Logger LOG = Logger.getLogger(SecretRandom.class.getName());
|
||||
/**
|
||||
* A typed key for secret data as a byte array, delegated from
|
||||
* {@link SecretMultiRecipientCryptor#SECRET}.
|
||||
*/
|
||||
protected static final Key<byte[]> SECRET = SecretMultiRecipientCryptor.SECRET;
|
||||
|
||||
/**
|
||||
* Constructs a {@code SecretRandom} instance with a buffer of the specified
|
||||
* length.
|
||||
* <p>
|
||||
* If {@code fillWithRandom} is {@code true}, the buffer is filled with
|
||||
* cryptographically secure random bytes. The resulting buffer is also stored in
|
||||
* the shared context using a secret key.
|
||||
* </p>
|
||||
*
|
||||
* @param length the length (in bytes) of the buffer to be allocated
|
||||
* @param fillWithRandom if {@code true}, fills the buffer with secure random
|
||||
* data
|
||||
*/
|
||||
public SecretRandom(final int length, final boolean fillWithRandom) {
|
||||
super(length);
|
||||
LOG.log(Level.INFO, "generating random, {0} bytes", length);
|
||||
|
||||
if (fillWithRandom) {
|
||||
RandomSupport.generateRandom(buffer);
|
||||
}
|
||||
Ctx.INSTANCE.put(SECRET, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance by copying a pre-existing cryptographically secure
|
||||
* byte array.
|
||||
*
|
||||
* @param random the secure byte array to use as secret content
|
||||
*/
|
||||
public SecretRandom(final byte[] random) {
|
||||
super(random);
|
||||
Ctx.INSTANCE.put(SECRET, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code SecretRandom} instance with a securely randomized
|
||||
* byte buffer.
|
||||
* <p>
|
||||
* The buffer is initialized with cryptographically strong random data,
|
||||
* generated using a combination of a user-provided {@code seed} string and an
|
||||
* internal random salt. The resulting byte array is non-deterministic and
|
||||
* unique across multiple invocations, even with the same seed.
|
||||
* <p>
|
||||
* The randomness is generated via the
|
||||
* {@link Password#generateRandom(byte[], String)} method, which uses a secure
|
||||
* hash (SHA-256) of the seed and salt to initialize a {@link SecureRandom}
|
||||
* instance. This ensures that the internal buffer is filled with high-entropy,
|
||||
* unpredictable bytes suitable for secret material or key generation purposes.
|
||||
*
|
||||
* @param length the size of the internal byte buffer
|
||||
* @param seed a user-defined seed string that influences the randomness
|
||||
* generation; must not be {@code null}
|
||||
* @throws NoSuchAlgorithmException if the cryptographic algorithm (SHA-256 or
|
||||
* SecureRandom) is not available
|
||||
* @throws NegativeArraySizeException if {@code length} is negative
|
||||
* @throws NullPointerException if {@code seed} is {@code null}
|
||||
*/
|
||||
public SecretRandom(final int length, final String seed) throws NoSuchAlgorithmException {
|
||||
super(length);
|
||||
|
||||
Password.generateRandom(buffer, seed);
|
||||
Ctx.INSTANCE.put(SECRET, buffer);
|
||||
}
|
||||
}
|
||||
79
lib/src/main/java/zeroecho/data/processing/package-info.java
Normal file
79
lib/src/main/java/zeroecho/data/processing/package-info.java
Normal file
@@ -0,0 +1,79 @@
|
||||
/*******************************************************************************
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
/**
|
||||
* Provides a comprehensive set of classes for AES encryption and decryption,
|
||||
* password-based key derivation, and secure content handling.
|
||||
*
|
||||
* <p>
|
||||
* This package contains the core AES cryptographic components:
|
||||
* <ul>
|
||||
* <li>{@link AesCommon} — common AES utilities and base class for encryptors
|
||||
* and decryptors</li>
|
||||
* <li>{@link AesEncryptor} and {@link AesDecryptor} — stream-based AES
|
||||
* encryption and decryption handlers</li>
|
||||
* <li>{@link PasswordBasedAesEncryptor} and {@link PasswordBasedAesDecryptor} —
|
||||
* AES implementations using password-derived keys (via PBKDF2)</li>
|
||||
* <li>{@link SecretRandom} — abstract base class for secure byte sequence
|
||||
* generators</li>
|
||||
* <li>{@link SecretAesRandom} — deterministic AES-based implementation of
|
||||
* {@code SecretRandom}</li>
|
||||
* <li>{@link SecretDerivedAesParameters} — encapsulates derived AES keys and
|
||||
* IVs with contextual integrity checks</li>
|
||||
* <li>{@link SecretMultiRecipientCryptor} — supports encrypting data for
|
||||
* multiple recipients using independent key derivation paths</li>
|
||||
* <li>{@link SecretPassword} — immutable password container designed for secure
|
||||
* handling and comparison</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* The package also offers plain content wrappers for handling unencrypted data
|
||||
* sources:
|
||||
* <ul>
|
||||
* <li>{@link PlainBytes} — wraps raw byte arrays as content sources</li>
|
||||
* <li>{@link PlainString} — wraps UTF-8 strings as content sources</li>
|
||||
* <li>{@link PlainFile} — treats file-based or URL-based content as input
|
||||
* sources</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* This package is designed around streaming APIs and centralized context-based
|
||||
* cryptographic parameter management using the shared {@link conflux.Ctx}
|
||||
* object. It facilitates secure encryption workflows with support for dynamic
|
||||
* derivation, deterministic streams, and multi-party encryption use cases.
|
||||
* </p>
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
package zeroecho.data.processing;
|
||||
51
lib/src/main/java/zeroecho/operations/Decryption.java
Normal file
51
lib/src/main/java/zeroecho/operations/Decryption.java
Normal file
@@ -0,0 +1,51 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.operations;
|
||||
|
||||
import zeroecho.data.DataContent;
|
||||
import zeroecho.data.EncryptedContent;
|
||||
import zeroecho.data.PlainContent;
|
||||
import zeroecho.data.SecretContent;
|
||||
|
||||
/**
|
||||
* Defines an operation that decrypts {@link EncryptedContent} using a
|
||||
* {@link SecretContent} and returns a {@link DataContent}. The result is
|
||||
* typically a {@link PlainContent}, but more complex encryption-decryption
|
||||
* schemes may yield other types of {@link DataContent}, especially in cases
|
||||
* involving multiple encryption layers for added complexity or obfuscation.
|
||||
*/
|
||||
|
||||
public interface Decryption {
|
||||
}
|
||||
56
lib/src/main/java/zeroecho/operations/Deployment.java
Normal file
56
lib/src/main/java/zeroecho/operations/Deployment.java
Normal file
@@ -0,0 +1,56 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.operations;
|
||||
|
||||
import zeroecho.data.EncryptedContent;
|
||||
|
||||
/**
|
||||
* Defines an operation for publishing {@link EncryptedContent} to public or
|
||||
* shared locations.
|
||||
* <p>
|
||||
* The deployment process ensures that sensitive content, once encrypted, can be
|
||||
* safely disseminated in a variety of forms and mediums—without revealing its
|
||||
* meaning or presence. Common deployment strategies include:
|
||||
* <ul>
|
||||
* <li>Saving to files on local or remote systems</li>
|
||||
* <li>Writing to standard output (e.g., console or logs)</li>
|
||||
* <li>Embedding in other data structures or media via steganography</li>
|
||||
* <li>Distributing through network endpoints or URLs</li>
|
||||
* </ul>
|
||||
* In advanced use cases, deployment may also aim to obscure the very existence
|
||||
* of the content, making it resistant to detection or suspicion.
|
||||
*/
|
||||
public interface Deployment {
|
||||
}
|
||||
46
lib/src/main/java/zeroecho/operations/Encryption.java
Normal file
46
lib/src/main/java/zeroecho/operations/Encryption.java
Normal file
@@ -0,0 +1,46 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.operations;
|
||||
|
||||
import zeroecho.data.EncryptedContent;
|
||||
import zeroecho.data.SecretContent;
|
||||
|
||||
/**
|
||||
* Defines an operation that transforms arbitrary content into
|
||||
* {@link EncryptedContent} using a {@link SecretContent} as the encryption key
|
||||
* or secret.
|
||||
*/
|
||||
public interface Encryption {
|
||||
}
|
||||
64
lib/src/main/java/zeroecho/operations/package-info.java
Normal file
64
lib/src/main/java/zeroecho/operations/package-info.java
Normal file
@@ -0,0 +1,64 @@
|
||||
/*******************************************************************************
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
/**
|
||||
* Provides abstractions for securely transforming and publishing sensitive data
|
||||
* in encrypted form across public or shared environments.
|
||||
* <p>
|
||||
* The primary objective of this package is to enable the secure dissemination
|
||||
* of original {@link zeroecho.data.PlainContent} by converting it into
|
||||
* {@link zeroecho.data.EncryptedContent}, which can be safely shared—even in
|
||||
* hostile or public environments—without exposing the underlying message.
|
||||
* <p>
|
||||
* This package defines the following interfaces:
|
||||
* <ul>
|
||||
* <li>{@link Encryption} – Transforms any {@link zeroecho.data.DataContent},
|
||||
* typically a {@link zeroecho.data.PlainContent}, into
|
||||
* {@link zeroecho.data.EncryptedContent} using a
|
||||
* {@link zeroecho.data.SecretContent} (e.g., a passphrase or key).</li>
|
||||
* <li>{@link Decryption} – Uses a {@link zeroecho.data.SecretContent} to
|
||||
* recover the original {@link zeroecho.data.DataContent} from an
|
||||
* {@link zeroecho.data.EncryptedContent}. In most cases, this yields a
|
||||
* {@link zeroecho.data.PlainContent}, though more complex encryption chains are
|
||||
* supported.</li>
|
||||
* <li>{@link Deployment} – Publishes the {@link zeroecho.data.EncryptedContent}
|
||||
* to publicly accessible locations such as files, console output, URLs, or
|
||||
* hidden within other data structures using techniques like steganography,
|
||||
* making the content optionally hard to detect.</li>
|
||||
* </ul>
|
||||
* This framework supports layered encryption, flexible content handling, and
|
||||
* covert deployment strategies for maximum confidentiality and plausible
|
||||
* deniability in public communication or archival.
|
||||
*/
|
||||
package zeroecho.operations;
|
||||
106
lib/src/main/java/zeroecho/util/BouncyCastleActivator.java
Normal file
106
lib/src/main/java/zeroecho/util/BouncyCastleActivator.java
Normal file
@@ -0,0 +1,106 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.util;
|
||||
|
||||
import java.security.Security;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
|
||||
|
||||
/**
|
||||
* Provides initialization support for the Bouncy Castle cryptographic provider.
|
||||
* <p>
|
||||
* This utility class automatically adds the Bouncy Castle provider to the Java
|
||||
* Security framework upon class loading. It also offers an explicit
|
||||
* initialization method for optional use.
|
||||
* <p>
|
||||
* The class is declared as {@code final} and has a private constructor to
|
||||
* prevent instantiation, emphasizing its utility nature.
|
||||
*
|
||||
* <p>
|
||||
* Example usage:
|
||||
*
|
||||
* <pre>{@code
|
||||
* BouncyCastleActivator.init();
|
||||
* }</pre>
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public final class BouncyCastleActivator {
|
||||
/**
|
||||
* Logger instance for the {@code BouncyCastleActivator} class, used to log
|
||||
* messages related to the initialization and management of the Bouncy Castle
|
||||
* security provider.
|
||||
* <p>
|
||||
* Initialized with the class name to ensure logs are specific and traceable to
|
||||
* this component. Useful for debugging issues during cryptographic provider
|
||||
* setup.
|
||||
* </p>
|
||||
*/
|
||||
private static final Logger LOG = Logger.getLogger(BouncyCastleActivator.class.getName());
|
||||
|
||||
/**
|
||||
* Static initializer that registers the Bouncy Castle provider with the Java
|
||||
* Security framework. Logs the initialization process.
|
||||
*/
|
||||
static {
|
||||
LOG.log(Level.INFO, "BouncyCastle provider initialization");
|
||||
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
LOG.log(Level.INFO, "BouncyCastle PQC provider initialization");
|
||||
if (Security.getProvider(BouncyCastlePQCProvider.PROVIDER_NAME) == null) {
|
||||
Security.addProvider(new BouncyCastlePQCProvider());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly logs the activation of the Bouncy Castle provider. This method can
|
||||
* be called to confirm provider activation.
|
||||
*/
|
||||
static public void init() {
|
||||
LOG.log(Level.INFO, "BrouncyCastle activated");
|
||||
}
|
||||
|
||||
/**
|
||||
* Private constructor to prevent instantiation of this utility class.
|
||||
*/
|
||||
private BouncyCastleActivator() {
|
||||
// this is a utility class
|
||||
}
|
||||
}
|
||||
217
lib/src/main/java/zeroecho/util/CryptoAlgorithmsNames.java
Normal file
217
lib/src/main/java/zeroecho/util/CryptoAlgorithmsNames.java
Normal file
@@ -0,0 +1,217 @@
|
||||
/**
|
||||
* 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 zeroecho.util;
|
||||
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
|
||||
|
||||
/**
|
||||
* Enumeration of supported cryptographic algorithm names used in
|
||||
* {@code KeyPairAlgorithm}.
|
||||
*
|
||||
* <p>
|
||||
* Each name includes a human-readable display name that is used for external
|
||||
* representation, such as in logs or UIs. The internal enum name (e.g.,
|
||||
* {@code ML_KEM}) can still be accessed via {@link #name()}, while
|
||||
* {@link #toString()} and {@link #displayName()} return the readable form.
|
||||
*
|
||||
* <p>
|
||||
* This enum can be used in {@code switch-case} structures for logic branching
|
||||
* on algorithm families.
|
||||
*/
|
||||
public enum CryptoAlgorithmsNames {
|
||||
|
||||
/** EdDSA (Edwards-curve Digital Signature Algorithm). */
|
||||
ED25519("Ed25519"),
|
||||
|
||||
/** RSA public-key encryption and signature algorithm. */
|
||||
RSA("RSA"),
|
||||
|
||||
/** DSA (Digital Signature Algorithm). */
|
||||
DSA("DSA"),
|
||||
|
||||
/** EC (Elliptic Curve) public-key cryptography. */
|
||||
EC("EC"),
|
||||
|
||||
/** ElGamal public-key encryption algorithm. */
|
||||
ELGAMAL("ElGamal"),
|
||||
|
||||
/** Naccache–Stern public-key cryptosystem. */
|
||||
/// NACCACHE("NaccacheStern"),
|
||||
|
||||
/** McEliece code-based public-key encryption scheme. */
|
||||
MCELIECE("McEliece"),
|
||||
|
||||
/**
|
||||
* ML-KEM (Kyber), a post-quantum lattice-based KEM (Key Encapsulation
|
||||
* Mechanism).
|
||||
*/
|
||||
KYBER("ML-KEM"),
|
||||
|
||||
/** SPHINCS+, a stateless hash-based digital signature scheme. */
|
||||
SPHINCS_PLUS("SPHINCS+"),
|
||||
|
||||
/** NewHope, a post-quantum lattice-based KEM. */
|
||||
NEWHOPE("NewHope"),
|
||||
|
||||
/**
|
||||
* FrodoKEM, a post-quantum lattice-based KEM based on learning with errors
|
||||
* (LWE).
|
||||
*/
|
||||
FRODO("Frodo");
|
||||
|
||||
private final String displayNameField;
|
||||
|
||||
private static final Map<String, CryptoAlgorithmsNames> BY_NAME = new HashMap<>(); // NOPMD
|
||||
|
||||
static {
|
||||
for (CryptoAlgorithmsNames n : values()) {
|
||||
BY_NAME.put(n.displayNameField.toUpperCase(Locale.ROOT), n); // case-insensitive match
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an algorithm name enum with a human-readable name.
|
||||
*
|
||||
* @param displayName the external string representation of the algorithm name
|
||||
*/
|
||||
CryptoAlgorithmsNames(String displayName) {
|
||||
this.displayNameField = displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the external display name of the algorithm.
|
||||
*
|
||||
* @return the human-readable algorithm name
|
||||
*/
|
||||
public String displayName() {
|
||||
return displayNameField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string representation of the algorithm, same as
|
||||
* {@link #displayName()}.
|
||||
*
|
||||
* @return the human-readable algorithm name
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return displayNameField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a human-readable algorithm name and returns the corresponding
|
||||
* {@code CryptoAlgorithmsNames} enum constant. Matching is case-insensitive.
|
||||
* <p>
|
||||
* The method also normalizes the algorithm name extracted from a public or
|
||||
* private key's format or metadata.
|
||||
* <p>
|
||||
* Some cryptographic providers, like BouncyCastle, use detailed algorithm names
|
||||
* (e.g., "SPHINCS+-SHA2-256S") to specify variant or parameter information.
|
||||
* This method maps such names to more general algorithm identifiers (e.g.,
|
||||
* "SPHINCS+").
|
||||
* <p>
|
||||
* This ensures consistent algorithm identification regardless of the variant or
|
||||
* parameter set.
|
||||
*
|
||||
* @param name the name of the algorithm (e.g., "RSA", "ML-KEM",
|
||||
* "SPHINCS+-SHA2-256S")
|
||||
* @return the corresponding {@code CryptoAlgorithmsNames} enum constant
|
||||
* @throws IllegalArgumentException if the name is unknown
|
||||
*/
|
||||
public static CryptoAlgorithmsNames fromString(String name) { // NOPMD
|
||||
if (name == null) {
|
||||
throw new IllegalArgumentException("Algorithm name must not be null.");
|
||||
}
|
||||
|
||||
if (name.startsWith("ML-KEM")) {
|
||||
name = "ML-KEM"; // NOPMD
|
||||
} else {
|
||||
if (name.startsWith("EdDSA")) {
|
||||
name = "Ed25519";
|
||||
} else {
|
||||
if (name.startsWith("Frodo")) {
|
||||
name = "Frodo";
|
||||
} else {
|
||||
final int i = name.indexOf('-');
|
||||
if (i != -1) {
|
||||
name = name.substring(0, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final CryptoAlgorithmsNames result = BY_NAME.get(name.trim().toUpperCase(Locale.ROOT));
|
||||
if (result == null) {
|
||||
throw new IllegalArgumentException("Unknown algorithm name: " + name);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link KeyFactory} instance for the algorithm represented by this
|
||||
* enum constant.
|
||||
* <p>
|
||||
* This method first attempts to obtain the {@code KeyFactory} from the classic
|
||||
* Bouncy Castle provider ({@link BouncyCastleProvider}). If the algorithm is
|
||||
* not supported there — typically in the case of post-quantum cryptographic
|
||||
* (PQC) algorithms — it falls back to the Bouncy Castle PQC provider
|
||||
* ({@link BouncyCastlePQCProvider}).
|
||||
* </p>
|
||||
*
|
||||
* @return a {@code KeyFactory} for the algorithm associated with this enum
|
||||
* constant
|
||||
* @throws NoSuchAlgorithmException if the algorithm is not available from
|
||||
* either provider
|
||||
* @throws NoSuchProviderException if the required Bouncy Castle provider is
|
||||
* not registered
|
||||
*/
|
||||
public KeyFactory getFactory() throws NoSuchAlgorithmException, NoSuchProviderException {
|
||||
try {
|
||||
// Classic algo ?
|
||||
return KeyFactory.getInstance(displayNameField, BouncyCastleProvider.PROVIDER_NAME);
|
||||
} catch (NoSuchAlgorithmException x) {
|
||||
// PQC algo ?
|
||||
return KeyFactory.getInstance(displayNameField, BouncyCastlePQCProvider.PROVIDER_NAME);
|
||||
}
|
||||
}
|
||||
}
|
||||
286
lib/src/main/java/zeroecho/util/IOUtil.java
Normal file
286
lib/src/main/java/zeroecho/util/IOUtil.java
Normal file
@@ -0,0 +1,286 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (C) 2024, 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 zeroecho.util;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Utility class for performing efficient I/O operations with support for packed
|
||||
* integers. This class provides static methods to write and read data with a
|
||||
* compact variable-length encoding ("Pack7") and custom handling of UUIDs and
|
||||
* UTF-8 strings.
|
||||
* <p>
|
||||
* <b>Design rationale:</b> Using static methods allows developers to perform
|
||||
* encoding and decoding operations directly on existing {@link InputStream} and
|
||||
* {@link OutputStream} instances without the need for additional stream
|
||||
* wrappers such as {@link FilterInputStream} or {@link FilterOutputStream}.
|
||||
* This approach eliminates extra object creation and method call overhead,
|
||||
* potentially improving performance, especially in high-throughput scenarios.
|
||||
* </p>
|
||||
* <p>
|
||||
* <b>Usage:</b> These methods can be used independently with any I/O stream to
|
||||
* achieve custom serialization formats, such as length-prefixed UTF-8 strings,
|
||||
* packed integers, or custom binary structures.
|
||||
* </p>
|
||||
*/
|
||||
public final class IOUtil { // NOPMD by Leo Galambos on 6/1/25, 4:30 PM
|
||||
/**
|
||||
* Private constructor to prevent instantiation of this utility class.
|
||||
*/
|
||||
private IOUtil() {
|
||||
// this is a utility class
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a byte array to the output stream with its length encoded as a packed
|
||||
* 7-bit integer.
|
||||
*
|
||||
* @param out the output stream
|
||||
* @param buf the byte array to write
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public static void write(final OutputStream out, final byte[] buf) throws IOException {
|
||||
writePack7I(out, buf.length);
|
||||
out.write(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a UUID to the output stream as two big-endian long values.
|
||||
*
|
||||
* @param out the output stream
|
||||
* @param uuid the UUID to write
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public static void write(final OutputStream out, final UUID uuid) throws IOException {
|
||||
writeLong(out, uuid.getMostSignificantBits());
|
||||
writeLong(out, uuid.getLeastSignificantBits());
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a UTF-8 encoded string to the output stream with its length as a
|
||||
* packed 7-bit integer.
|
||||
*
|
||||
* @param out the output stream
|
||||
* @param str the string to write
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public static void writeUTF8(final OutputStream out, final String str) throws IOException {
|
||||
final byte[] buf = str.getBytes(StandardCharsets.UTF_8);
|
||||
writePack7I(out, buf.length);
|
||||
out.write(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a long value to the output stream in big-endian order (8 bytes).
|
||||
*
|
||||
* @param out the output stream
|
||||
* @param val the long value to write
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public static void writeLong(final OutputStream out, long val) throws IOException {
|
||||
final byte[] buf = new byte[8];
|
||||
for (int i = buf.length; --i >= 0;) { // NOPMD by Leo Galambos on 6/1/25, 4:32 PM
|
||||
buf[i] = (byte) (val & 0xff);
|
||||
val = val >>> 8; // NOPMD by Leo Galambos on 6/1/25, 4:33 PM
|
||||
}
|
||||
out.write(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a UUID from the input stream, composed of two big-endian long values.
|
||||
*
|
||||
* @param in the input stream
|
||||
* @return the UUID read from the stream
|
||||
* @throws IOException if an I/O error occurs or if the stream ends prematurely
|
||||
*/
|
||||
public static UUID readUUID(final InputStream in) throws IOException {
|
||||
final long msb = readLong(in);
|
||||
final long lsb = readLong(in);
|
||||
|
||||
return new UUID(msb, lsb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a UTF-8 encoded string from the input stream. The string is prefixed
|
||||
* with a packed 7-bit integer indicating its length. To prevent excessive
|
||||
* allocation, the maximum allowable length must be specified.
|
||||
*
|
||||
* @param in the input stream
|
||||
* @param maxLength the maximum allowable length of the string in bytes
|
||||
* @return the string read from the stream
|
||||
* @throws IOException if an I/O error occurs, the length exceeds maxLength, or
|
||||
* if the stream ends prematurely
|
||||
*/
|
||||
public static String readUTF8(final InputStream in, final int maxLength) throws IOException {
|
||||
final int len = readPack7I(in);
|
||||
if (len > maxLength) {
|
||||
throw new IOException("readUTF8 length " + len + " exceeds maximum allowed length " + maxLength);
|
||||
}
|
||||
final byte[] buf = new byte[len];
|
||||
if (in.readNBytes(buf, 0, buf.length) != buf.length) {
|
||||
throw new EOFException("readUTF8 EOF");
|
||||
}
|
||||
return new String(buf, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a long value from the input stream in big-endian order (8 bytes).
|
||||
*
|
||||
* @param in the input stream
|
||||
* @return the long value read
|
||||
* @throws IOException if an I/O error occurs or if the stream ends prematurely
|
||||
*/
|
||||
public static long readLong(final InputStream in) throws IOException {
|
||||
final byte[] buff = new byte[8];
|
||||
if (in.readNBytes(buff, 0, buff.length) != buff.length) {
|
||||
throw new EOFException("readLong EOF");
|
||||
}
|
||||
|
||||
long result = 0;
|
||||
for (final byte b : buff) {
|
||||
result = (result << 8) | (b & 0xffL);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an integer to the output stream using packed 7-bit encoding (variable
|
||||
* length).
|
||||
*
|
||||
* @param out the output stream
|
||||
* @param val the integer value to write
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public static void writePack7I(final OutputStream out, int val) throws IOException {
|
||||
final byte[] buff = new byte[5];
|
||||
int idx = buff.length;
|
||||
while ((val & ~0x7f) != 0) {
|
||||
buff[--idx] = (byte) (val & 0x7f);
|
||||
val = val >>> 7; // NOPMD by Leo Galambos on 6/1/25, 4:35 PM
|
||||
}
|
||||
buff[--idx] = (byte) val;
|
||||
buff[buff.length - 1] |= 0x80;
|
||||
out.write(buff, idx, buff.length - idx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a long value to the output stream using packed 7-bit encoding
|
||||
* (variable length).
|
||||
*
|
||||
* @param out the output stream
|
||||
* @param val the long value to write
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
public static void writePack7L(final OutputStream out, long val) throws IOException {
|
||||
final byte[] buff = new byte[10];
|
||||
int idx = buff.length;
|
||||
while ((val & ~0x7fL) != 0) {
|
||||
buff[--idx] = (byte) (val & 0x7f);
|
||||
val = val >>> 7; // NOPMD by Leo Galambos on 6/1/25, 4:37 PM
|
||||
}
|
||||
buff[--idx] = (byte) val;
|
||||
buff[buff.length - 1] |= 0x80;
|
||||
out.write(buff, idx, buff.length - idx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a byte array from the input stream. The length of the array is
|
||||
* specified as a packed 7-bit integer prefix. To prevent excessive allocation,
|
||||
* the maximum allowable length must be specified.
|
||||
*
|
||||
* @param in the input stream
|
||||
* @param maxLength the maximum allowable length of the byte array
|
||||
* @return the byte array read
|
||||
* @throws IOException if an I/O error occurs, the length exceeds maxLength, or
|
||||
* if the stream ends prematurely
|
||||
*/
|
||||
public static byte[] read(final InputStream in, final int maxLength) throws IOException {
|
||||
final int len = readPack7I(in);
|
||||
if (len > maxLength || len < 0) {
|
||||
throw new IOException("read length " + len + " exceeds maximum allowed length " + maxLength);
|
||||
}
|
||||
final byte[] result = new byte[len];
|
||||
if (in.readNBytes(result, 0, result.length) != result.length) {
|
||||
throw new EOFException("read EOF");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an integer from the input stream using packed 7-bit encoding (variable
|
||||
* length).
|
||||
*
|
||||
* @param in the input stream
|
||||
* @return the integer value read
|
||||
* @throws IOException if an I/O error occurs or if the stream ends prematurely
|
||||
*/
|
||||
public static int readPack7I(final InputStream in) throws IOException {
|
||||
int result = in.read();
|
||||
if (result > 0x7f) { // NOPMD by Leo Galambos on 6/1/25, 4:38 PM
|
||||
return result & 0x7f;
|
||||
}
|
||||
int i;
|
||||
for (i = in.read(); i < 0x80; i = in.read()) {
|
||||
result = (result << 7) | i;
|
||||
}
|
||||
return (result << 7) | (i & 0x7f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a long value from the input stream using packed 7-bit encoding
|
||||
* (variable length).
|
||||
*
|
||||
* @param in the input stream
|
||||
* @return the long value read
|
||||
* @throws IOException if an I/O error occurs or if the stream ends prematurely
|
||||
*/
|
||||
public static long readPack7L(final InputStream in) throws IOException {
|
||||
long result = in.read();
|
||||
if (result > 0x7f) { // NOPMD by Leo Galambos on 6/1/25, 4:38 PM
|
||||
return result & 0x7fL;
|
||||
}
|
||||
int i;
|
||||
for (i = in.read(); i < 0x80; i = in.read()) {
|
||||
result = (result << 7) | i;
|
||||
}
|
||||
return (result << 7) | (i & 0x7f);
|
||||
}
|
||||
}
|
||||
516
lib/src/main/java/zeroecho/util/KeyPairAlgorithm.java
Normal file
516
lib/src/main/java/zeroecho/util/KeyPairAlgorithm.java
Normal file
@@ -0,0 +1,516 @@
|
||||
/**
|
||||
* 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 zeroecho.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.ECGenParameterSpec;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.KeyGenerationParameters;
|
||||
import org.bouncycastle.crypto.generators.ElGamalParametersGenerator;
|
||||
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
|
||||
import org.bouncycastle.crypto.params.ElGamalParameters;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.jce.spec.ElGamalParameterSpec;
|
||||
import org.bouncycastle.pqc.crypto.frodo.FrodoKeyGenerationParameters;
|
||||
import org.bouncycastle.pqc.crypto.frodo.FrodoKeyPairGenerator;
|
||||
import org.bouncycastle.pqc.crypto.frodo.FrodoParameters;
|
||||
import org.bouncycastle.pqc.crypto.frodo.FrodoPrivateKeyParameters;
|
||||
import org.bouncycastle.pqc.crypto.frodo.FrodoPublicKeyParameters;
|
||||
import org.bouncycastle.pqc.crypto.newhope.NHKeyPairGenerator;
|
||||
import org.bouncycastle.pqc.crypto.newhope.NHPrivateKeyParameters;
|
||||
import org.bouncycastle.pqc.crypto.newhope.NHPublicKeyParameters;
|
||||
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
|
||||
import org.bouncycastle.pqc.jcajce.spec.KyberParameterSpec;
|
||||
import org.bouncycastle.pqc.jcajce.spec.SPHINCSPlusParameterSpec;
|
||||
import org.bouncycastle.pqc.legacy.crypto.mceliece.McElieceKeyGenerationParameters;
|
||||
import org.bouncycastle.pqc.legacy.crypto.mceliece.McElieceKeyPairGenerator;
|
||||
import org.bouncycastle.pqc.legacy.crypto.mceliece.McElieceParameters;
|
||||
import org.bouncycastle.pqc.legacy.crypto.mceliece.McEliecePrivateKeyParameters;
|
||||
import org.bouncycastle.pqc.legacy.crypto.mceliece.McEliecePublicKeyParameters;
|
||||
|
||||
import zeroecho.util.bc.FrodoPrivateKey;
|
||||
import zeroecho.util.bc.FrodoPublicKey;
|
||||
import zeroecho.util.bc.McEliecePrivateKey;
|
||||
import zeroecho.util.bc.McEliecePublicKey;
|
||||
import zeroecho.util.bc.NewHopePrivateKey;
|
||||
import zeroecho.util.bc.NewHopePublicKey;
|
||||
|
||||
/**
|
||||
* Enumeration representing supported asymmetric key pair algorithms along with
|
||||
* their key sizes and post-quantum cryptography (PQC) status.
|
||||
*
|
||||
* <p>
|
||||
* This enum provides constants for classical cryptographic algorithms such as
|
||||
* RSA, DSA, EC, ElGamal, and EdDSA, as well as various post-quantum algorithms
|
||||
* like Kyber, SPHINCS+, NewHope, FrodoKEM, and McEliece.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Each enum constant includes the algorithm's name, key size (or security level
|
||||
* for PQC algorithms), and whether it is a post-quantum cryptographic
|
||||
* algorithm.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Functionality includes:
|
||||
* <ul>
|
||||
* <li>Generating key pairs using the Bouncy Castle (classic and PQC)
|
||||
* providers.</li>
|
||||
* <li>Converting Java {@link PublicKey} and {@link PrivateKey} objects to
|
||||
* Bouncy Castle {@link org.bouncycastle.crypto.params.AsymmetricKeyParameter}
|
||||
* instances.</li>
|
||||
* <li>Parsing string representations of algorithms in the format
|
||||
* "ALGORITHM:KEYSIZE" (case-insensitive) to corresponding enum constants.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* Note: The {@link #generateKeyPair()} method automatically adds the necessary
|
||||
* Bouncy Castle providers if not already present.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The {@link #generateKeyPair()} method uses the Bouncy Castle provider to
|
||||
* generate the key pair, and the enum supports parsing from string format
|
||||
* (e.g., {@code "RSA:2048"}) via {@link #fromString(String)}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Example usage: <pre>{@code
|
||||
* KeyPairAlgorithm alg = KeyPairAlgorithm.RSA_2048;
|
||||
* KeyPair keyPair = alg.generateKeyPair();
|
||||
* }</pre>
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public enum KeyPairAlgorithm {
|
||||
/**
|
||||
* EdDSA (Edwards-curve Digital Signature Algorithm) with 255-bit key size.
|
||||
*/
|
||||
ED25519(CryptoAlgorithmsNames.ED25519, 255),
|
||||
|
||||
/**
|
||||
* RSA algorithm with 1024-bit key size.
|
||||
*/
|
||||
RSA_1024(CryptoAlgorithmsNames.RSA, 1024),
|
||||
/**
|
||||
* RSA algorithm with 2048-bit key size.
|
||||
*/
|
||||
RSA_2048(CryptoAlgorithmsNames.RSA, 2048),
|
||||
/**
|
||||
* RSA algorithm with 4096-bit key size.
|
||||
*/
|
||||
RSA_4096(CryptoAlgorithmsNames.RSA, 4096),
|
||||
|
||||
/**
|
||||
* DSA algorithm with 2048-bit key size.
|
||||
*/
|
||||
DSA_2048(CryptoAlgorithmsNames.DSA, 2048),
|
||||
|
||||
/**
|
||||
* EC algorithm with 256-bit key size (e.g., secp256r1).
|
||||
*/
|
||||
EC_P256(CryptoAlgorithmsNames.EC, 256),
|
||||
/**
|
||||
* EC algorithm with 384-bit key size (e.g., secp384r1).
|
||||
*/
|
||||
EC_P384(CryptoAlgorithmsNames.EC, 384),
|
||||
|
||||
/**
|
||||
* ElGamal algorithm with 512-bit key size.
|
||||
*/
|
||||
ELGAMAL_512(CryptoAlgorithmsNames.ELGAMAL, 512),
|
||||
/**
|
||||
* ElGamal algorithm with 1024-bit key size.
|
||||
*/
|
||||
ELGAMAL_1024(CryptoAlgorithmsNames.ELGAMAL, 1024),
|
||||
/**
|
||||
* ElGamal algorithm with 2048-bit key size.
|
||||
*/
|
||||
ELGAMAL_2048(CryptoAlgorithmsNames.ELGAMAL, 2048),
|
||||
|
||||
/// Naccache–Stern algorithm with 2048-bit key size.
|
||||
/// NACCACHE_2048(CryptoAlgorithmsNames.NACCACHE, 2048),
|
||||
|
||||
/**
|
||||
* McEliece algorithm with 256-bit security level.
|
||||
*/
|
||||
MCELIECE_256(CryptoAlgorithmsNames.MCELIECE, 256, true),
|
||||
|
||||
/**
|
||||
* ML-KEM (Kyber) algorithm with 512-bit security level.
|
||||
*/
|
||||
KYBER_512(CryptoAlgorithmsNames.KYBER, 512, true),
|
||||
/**
|
||||
* ML-KEM (Kyber) algorithm with 768-bit security level.
|
||||
*/
|
||||
KYBER_768(CryptoAlgorithmsNames.KYBER, 768, true),
|
||||
/**
|
||||
* ML-KEM (Kyber) algorithm with 1024-bit security level.
|
||||
*/
|
||||
KYBER_1024(CryptoAlgorithmsNames.KYBER, 1024, true),
|
||||
|
||||
/**
|
||||
* SPHINCS+ algorithm with 128-bit security level.
|
||||
*/
|
||||
SPHINCS_PLUS_128S(CryptoAlgorithmsNames.SPHINCS_PLUS, 128, true),
|
||||
/**
|
||||
* SPHINCS+ algorithm with 192-bit security level.
|
||||
*/
|
||||
SPHINCS_PLUS_192S(CryptoAlgorithmsNames.SPHINCS_PLUS, 192, true),
|
||||
/**
|
||||
* SPHINCS+ algorithm with 256-bit security level.
|
||||
*/
|
||||
SPHINCS_PLUS_256S(CryptoAlgorithmsNames.SPHINCS_PLUS, 256, true),
|
||||
|
||||
/**
|
||||
* NewHope algorithm with 512-bit parameter set.
|
||||
*/
|
||||
NEWHOPE_512(CryptoAlgorithmsNames.NEWHOPE, 512, true),
|
||||
/**
|
||||
* NewHope algorithm with 1024-bit parameter set.
|
||||
*/
|
||||
NEWHOPE_1024(CryptoAlgorithmsNames.NEWHOPE, 1024, true),
|
||||
|
||||
/**
|
||||
* FrodoKEM algorithm with 640-bit parameter set.
|
||||
*/
|
||||
FRODOKEM_640(CryptoAlgorithmsNames.FRODO, 640, true),
|
||||
/**
|
||||
* FrodoKEM algorithm with 976-bit parameter set.
|
||||
*/
|
||||
FRODOKEM_976(CryptoAlgorithmsNames.FRODO, 976, true),
|
||||
/**
|
||||
* FrodoKEM algorithm with 1344-bit parameter set.
|
||||
*/
|
||||
FRODOKEM_1344(CryptoAlgorithmsNames.FRODO, 1344, true);
|
||||
|
||||
/**
|
||||
* Algorithms for which key pairs are serializable (i.e., keys return non-null
|
||||
* from {@code getEncoded()}).
|
||||
*/
|
||||
public static final KeyPairAlgorithm[] SERIALIZABLE_ALGORITHMS = {
|
||||
/// EdDSA (Edwards-curve Digital Signature Algorithm) with 255-bit key size.
|
||||
ED25519,
|
||||
/// RSA with 1024-bit key size.
|
||||
RSA_1024,
|
||||
/// RSA with 2048-bit key size.
|
||||
RSA_2048,
|
||||
/// RSA with 4096-bit key size.
|
||||
RSA_4096,
|
||||
/// DSA with 2048-bit key size.
|
||||
DSA_2048,
|
||||
/// Elliptic Curve with 256-bit key size (e.g., secp256r1).
|
||||
EC_P256,
|
||||
/// Elliptic Curve with 384-bit key size (e.g., secp384r1).
|
||||
EC_P384,
|
||||
/// ElGamal with 512-bit key size.
|
||||
ELGAMAL_512,
|
||||
/// ElGamal with 1024-bit key size.
|
||||
ELGAMAL_1024,
|
||||
/// ElGamal with 2048-bit key size.
|
||||
ELGAMAL_2048,
|
||||
/// ML-KEM / Kyber with 512-bit parameter.
|
||||
KYBER_512,
|
||||
/// ML-KEM / Kyber with 768-bit parameter.
|
||||
KYBER_768,
|
||||
/// ML-KEM / Kyber with 1024-bit parameter.
|
||||
KYBER_1024,
|
||||
/// SPHINCS+ with 128-bit security level (SHAKE-256).
|
||||
SPHINCS_PLUS_128S,
|
||||
/// SPHINCS+ with 192-bit security level (SHAKE-256).
|
||||
SPHINCS_PLUS_192S,
|
||||
/// SPHINCS+ with 256-bit security level (SHAKE-256).
|
||||
SPHINCS_PLUS_256S,
|
||||
/// FrodoKEM algorithm with 640-bit parameter set.
|
||||
FRODOKEM_640,
|
||||
/// FrodoKEM algorithm with 976-bit parameter set.
|
||||
FRODOKEM_976,
|
||||
/// FrodoKEM algorithm with 1344-bit parameter set.
|
||||
FRODOKEM_1344 };
|
||||
|
||||
private static final Map<String, KeyPairAlgorithm> BY_STRING = new HashMap<>(); // NOPMD
|
||||
|
||||
static {
|
||||
for (KeyPairAlgorithm alg : values()) {
|
||||
BY_STRING.put(alg.toString().toUpperCase(Locale.ROOT), alg);
|
||||
}
|
||||
}
|
||||
|
||||
private final CryptoAlgorithmsNames algorithmName;
|
||||
private final int keySize;
|
||||
private final boolean pqc;
|
||||
|
||||
KeyPairAlgorithm(CryptoAlgorithmsNames algorithmName, int keySize) {
|
||||
this(algorithmName, keySize, false);
|
||||
}
|
||||
|
||||
KeyPairAlgorithm(CryptoAlgorithmsNames algorithmName, int keySize, boolean pqc) {
|
||||
this.algorithmName = algorithmName;
|
||||
this.keySize = keySize;
|
||||
this.pqc = pqc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a given {@link PublicKey} into the corresponding Bouncy Castle
|
||||
* {@link org.bouncycastle.crypto.params.AsymmetricKeyParameter}.
|
||||
*
|
||||
* @param key the public key to convert
|
||||
* @return the corresponding BC asymmetric key parameter
|
||||
* @throws IOException if the key encoding is invalid or conversion fails
|
||||
*/
|
||||
public AsymmetricKeyParameter getKeyParameter(PublicKey key) throws IOException {
|
||||
return pqc ? org.bouncycastle.pqc.crypto.util.PublicKeyFactory.createKey(key.getEncoded())
|
||||
: org.bouncycastle.crypto.util.PublicKeyFactory.createKey(key.getEncoded());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a given {@link PrivateKey} into the corresponding Bouncy Castle
|
||||
* {@link org.bouncycastle.crypto.params.AsymmetricKeyParameter}.
|
||||
*
|
||||
* @param key the private key to convert
|
||||
* @return the corresponding BC asymmetric key parameter
|
||||
* @throws IOException if the key encoding is invalid or conversion fails
|
||||
*/
|
||||
public AsymmetricKeyParameter getKeyParameter(PrivateKey key) throws IOException {
|
||||
return pqc ? org.bouncycastle.pqc.crypto.util.PrivateKeyFactory.createKey(key.getEncoded())
|
||||
: org.bouncycastle.crypto.util.PrivateKeyFactory.createKey(key.getEncoded());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the standard cryptographic algorithm name (e.g., "RSA", "EC", "DSA")
|
||||
* associated with this enum constant.
|
||||
*
|
||||
* @return the algorithm name as a {@code String}
|
||||
*/
|
||||
public String getAlgorithmName() {
|
||||
return algorithmName.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key size or security level in bits for this algorithm.
|
||||
*
|
||||
* <p>
|
||||
* For classical algorithms, this corresponds to the actual key size (e.g., 2048
|
||||
* bits for RSA). For post-quantum algorithms, this corresponds to the security
|
||||
* level or parameter set size.
|
||||
* </p>
|
||||
*
|
||||
* @return the key size in bits, or a negative number if undefined
|
||||
*/
|
||||
public int getKeySize() {
|
||||
return keySize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of this algorithm in the format
|
||||
* {@code "ALGORITHM:KEYSIZE"}, e.g., {@code "RSA:2048"} or
|
||||
* {@code "McEliece:256"}. This format is consistent for parsing and display.
|
||||
*
|
||||
* @return the formatted string representation of the algorithm and key size
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return algorithmName + ":" + keySize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new {@link KeyPair} for this algorithm using the Bouncy Castle
|
||||
* providers.
|
||||
*
|
||||
* <p>
|
||||
* This method automatically adds and configures the Bouncy Castle and Bouncy
|
||||
* Castle PQC providers if not already present in the Java Security environment.
|
||||
* </p>
|
||||
*
|
||||
* @return a newly generated {@link KeyPair} instance for this algorithm
|
||||
* @throws NoSuchAlgorithmException if the algorithm is not supported
|
||||
* @throws NoSuchProviderException if the required Bouncy Castle
|
||||
* provider is not registered or
|
||||
* available
|
||||
* @throws InvalidAlgorithmParameterException if algorithm parameters are
|
||||
* invalid
|
||||
*/
|
||||
public KeyPair generateKeyPair() // NOPMD
|
||||
throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
|
||||
switch (this) {
|
||||
case ED25519: {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithmName.displayName());
|
||||
keyPairGenerator.initialize(255, new SecureRandom());
|
||||
|
||||
return keyPairGenerator.generateKeyPair();
|
||||
}
|
||||
case RSA_1024:
|
||||
case RSA_2048:
|
||||
case RSA_4096:
|
||||
case DSA_2048: {
|
||||
KeyPairGenerator gen = KeyPairGenerator.getInstance(algorithmName.displayName(),
|
||||
BouncyCastleProvider.PROVIDER_NAME);
|
||||
gen.initialize(keySize);
|
||||
return gen.generateKeyPair();
|
||||
}
|
||||
case EC_P256:
|
||||
case EC_P384: {
|
||||
KeyPairGenerator gen = KeyPairGenerator.getInstance(algorithmName.displayName(),
|
||||
BouncyCastleProvider.PROVIDER_NAME);
|
||||
gen.initialize(new ECGenParameterSpec("secp" + keySize + "r1"));
|
||||
return gen.generateKeyPair();
|
||||
}
|
||||
case ELGAMAL_512:
|
||||
case ELGAMAL_1024:
|
||||
case ELGAMAL_2048: {
|
||||
ElGamalParametersGenerator paramGen = new ElGamalParametersGenerator();
|
||||
paramGen.init(keySize, 20, RandomSupport.getRandom());
|
||||
ElGamalParameters params = paramGen.generateParameters();
|
||||
ElGamalParameterSpec spec = new ElGamalParameterSpec(params.getP(), params.getG());
|
||||
KeyPairGenerator gen = KeyPairGenerator.getInstance(algorithmName.displayName(),
|
||||
BouncyCastleProvider.PROVIDER_NAME);
|
||||
gen.initialize(spec);
|
||||
return gen.generateKeyPair();
|
||||
}
|
||||
case MCELIECE_256: {
|
||||
McElieceKeyPairGenerator generator = new McElieceKeyPairGenerator();
|
||||
McElieceKeyGenerationParameters params = new McElieceKeyGenerationParameters(RandomSupport.getRandom(),
|
||||
new McElieceParameters());
|
||||
generator.init(params);
|
||||
AsymmetricCipherKeyPair kp = generator.generateKeyPair();
|
||||
|
||||
McEliecePublicKeyParameters pubParams = (McEliecePublicKeyParameters) kp.getPublic();
|
||||
McEliecePrivateKeyParameters privParams = (McEliecePrivateKeyParameters) kp.getPrivate();
|
||||
|
||||
PublicKey pub = new McEliecePublicKey(pubParams);
|
||||
PrivateKey priv = new McEliecePrivateKey(privParams);
|
||||
|
||||
return new KeyPair(pub, priv);
|
||||
}
|
||||
case KYBER_512:
|
||||
case KYBER_768:
|
||||
case KYBER_1024: {
|
||||
KeyPairGenerator gen = KeyPairGenerator.getInstance("Kyber", BouncyCastlePQCProvider.PROVIDER_NAME);
|
||||
KyberParameterSpec paramSpec = switch (this) {
|
||||
case KYBER_512 -> KyberParameterSpec.kyber512;
|
||||
case KYBER_768 -> KyberParameterSpec.kyber768;
|
||||
case KYBER_1024 -> KyberParameterSpec.kyber1024;
|
||||
default -> throw new IllegalStateException("Unexpected Kyber variant");
|
||||
};
|
||||
gen.initialize(paramSpec, RandomSupport.getRandom());
|
||||
return gen.generateKeyPair();
|
||||
}
|
||||
case SPHINCS_PLUS_128S:
|
||||
case SPHINCS_PLUS_192S:
|
||||
case SPHINCS_PLUS_256S: {
|
||||
SPHINCSPlusParameterSpec params = switch (this) {
|
||||
case SPHINCS_PLUS_128S -> SPHINCSPlusParameterSpec.shake_128s;
|
||||
case SPHINCS_PLUS_192S -> SPHINCSPlusParameterSpec.shake_192s;
|
||||
case SPHINCS_PLUS_256S -> SPHINCSPlusParameterSpec.shake_256s;
|
||||
default -> throw new IllegalStateException("Unexpected SPHINCS variant");
|
||||
};
|
||||
KeyPairGenerator gen = KeyPairGenerator.getInstance("SPHINCSPlus",
|
||||
BouncyCastlePQCProvider.PROVIDER_NAME);
|
||||
gen.initialize(params);
|
||||
return gen.generateKeyPair();
|
||||
}
|
||||
case NEWHOPE_512:
|
||||
case NEWHOPE_1024: {
|
||||
NHKeyPairGenerator generator = new NHKeyPairGenerator();
|
||||
generator.init(new KeyGenerationParameters(RandomSupport.getRandom(), keySize));
|
||||
AsymmetricCipherKeyPair kp = generator.generateKeyPair();
|
||||
|
||||
NHPublicKeyParameters pubParams = (NHPublicKeyParameters) kp.getPublic();
|
||||
NHPrivateKeyParameters privParams = (NHPrivateKeyParameters) kp.getPrivate();
|
||||
|
||||
PublicKey pub = new NewHopePublicKey(pubParams);
|
||||
PrivateKey priv = new NewHopePrivateKey(privParams);
|
||||
|
||||
return new KeyPair(pub, priv);
|
||||
}
|
||||
case FRODOKEM_640:
|
||||
case FRODOKEM_976:
|
||||
case FRODOKEM_1344: {
|
||||
FrodoParameters params = switch (this) {
|
||||
case FRODOKEM_640 -> FrodoParameters.frodokem640aes;
|
||||
case FRODOKEM_976 -> FrodoParameters.frodokem976aes;
|
||||
case FRODOKEM_1344 -> FrodoParameters.frodokem1344aes;
|
||||
default -> throw new IllegalStateException("Unexpected Frodo variant");
|
||||
};
|
||||
|
||||
FrodoKeyPairGenerator generator = new FrodoKeyPairGenerator();
|
||||
generator.init(new FrodoKeyGenerationParameters(RandomSupport.getRandom(), params));
|
||||
AsymmetricCipherKeyPair kp = generator.generateKeyPair();
|
||||
|
||||
FrodoPublicKeyParameters pubParams = (FrodoPublicKeyParameters) kp.getPublic();
|
||||
FrodoPrivateKeyParameters privParams = (FrodoPrivateKeyParameters) kp.getPrivate();
|
||||
|
||||
PublicKey pub = new FrodoPublicKey(pubParams);
|
||||
PrivateKey priv = new FrodoPrivateKey(privParams);
|
||||
|
||||
return new KeyPair(pub, priv);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string representation of an algorithm in the format
|
||||
* {@code "ALGORITHM:KEYSIZE"} (case-insensitive) and returns the matching
|
||||
* {@link KeyPairAlgorithm} enum constant.
|
||||
*
|
||||
* @param value the string to parse, e.g., "RSA:2048"
|
||||
* @return the corresponding {@code KeyPairAlgorithm} constant
|
||||
* @throws IllegalArgumentException if the input is null, malformed, or not
|
||||
* recognized
|
||||
*/
|
||||
public static KeyPairAlgorithm fromString(String value) {
|
||||
if (value == null || !value.contains(":")) {
|
||||
throw new IllegalArgumentException("Expected format: ALGORITHM:KEYSIZE (e.g., RSA:2048)");
|
||||
}
|
||||
final KeyPairAlgorithm alg = BY_STRING.get(value.trim().toUpperCase(Locale.ROOT));
|
||||
if (alg == null) {
|
||||
throw new IllegalArgumentException("Unsupported key pair algorithm: " + value);
|
||||
}
|
||||
return alg;
|
||||
}
|
||||
}
|
||||
425
lib/src/main/java/zeroecho/util/KeySupport.java
Normal file
425
lib/src/main/java/zeroecho/util/KeySupport.java
Normal file
@@ -0,0 +1,425 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.util; // NOPMD
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.interfaces.DSAKey;
|
||||
import java.security.interfaces.DSAParams;
|
||||
import java.security.interfaces.DSAPrivateKey;
|
||||
import java.security.interfaces.DSAPublicKey;
|
||||
import java.security.interfaces.ECKey;
|
||||
import java.security.interfaces.ECPrivateKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.security.interfaces.RSAKey;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
|
||||
import javax.crypto.interfaces.DHKey;
|
||||
import javax.crypto.interfaces.DHPrivateKey;
|
||||
import javax.crypto.interfaces.DHPublicKey;
|
||||
import javax.crypto.spec.DHParameterSpec;
|
||||
|
||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||
import org.bouncycastle.crypto.digests.SHA256Digest;
|
||||
import org.bouncycastle.crypto.engines.ElGamalEngine;
|
||||
import org.bouncycastle.crypto.engines.RSAEngine;
|
||||
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
|
||||
import org.bouncycastle.crypto.params.DSAParameters;
|
||||
import org.bouncycastle.crypto.params.DSAPrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.DSAPublicKeyParameters;
|
||||
import org.bouncycastle.crypto.params.ECDomainParameters;
|
||||
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
|
||||
import org.bouncycastle.crypto.params.ElGamalParameters;
|
||||
import org.bouncycastle.crypto.params.ElGamalPrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.ElGamalPublicKeyParameters;
|
||||
import org.bouncycastle.crypto.params.RSAKeyParameters;
|
||||
import org.bouncycastle.jce.ECNamedCurveTable;
|
||||
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
|
||||
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
|
||||
import org.bouncycastle.math.ec.ECPoint;
|
||||
import org.bouncycastle.pqc.crypto.frodo.FrodoKEMExtractor;
|
||||
import org.bouncycastle.pqc.crypto.frodo.FrodoKEMGenerator;
|
||||
import org.bouncycastle.pqc.crypto.frodo.FrodoPrivateKeyParameters;
|
||||
import org.bouncycastle.pqc.crypto.frodo.FrodoPublicKeyParameters;
|
||||
import org.bouncycastle.pqc.crypto.mlkem.MLKEMExtractor;
|
||||
import org.bouncycastle.pqc.crypto.mlkem.MLKEMGenerator;
|
||||
import org.bouncycastle.pqc.crypto.mlkem.MLKEMPrivateKeyParameters;
|
||||
import org.bouncycastle.pqc.crypto.mlkem.MLKEMPublicKeyParameters;
|
||||
import org.bouncycastle.pqc.crypto.util.PrivateKeyFactory;
|
||||
import org.bouncycastle.pqc.crypto.util.PublicKeyFactory;
|
||||
import org.bouncycastle.pqc.jcajce.interfaces.SPHINCSPlusPrivateKey;
|
||||
import org.bouncycastle.pqc.jcajce.interfaces.SPHINCSPlusPublicKey;
|
||||
|
||||
import zeroecho.util.asymmetric.AsymmetricContext;
|
||||
import zeroecho.util.asymmetric.ClassicAsymmetricContext;
|
||||
import zeroecho.util.asymmetric.KEMAsymmetricContext;
|
||||
import zeroecho.util.asymmetric.SignatureAsymmetricContext;
|
||||
import zeroecho.util.bc.FrodoPrivateKey;
|
||||
import zeroecho.util.bc.FrodoPublicKey;
|
||||
|
||||
/**
|
||||
* Utility class for converting between standard Java {@link Key} objects and
|
||||
* BouncyCastle {@link AsymmetricKeyParameter} with optional cipher context.
|
||||
* <p>
|
||||
* Supports serialization and deserialization of public and private keys to/from
|
||||
* a standardized string format, as well as conversion to
|
||||
* {@link AsymmetricContext} wrappers compatible with BouncyCastle.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Supported Algorithms</h2>
|
||||
* <ul>
|
||||
* <li>RSA</li>
|
||||
* <li>DSA</li>
|
||||
* <li>EC</li>
|
||||
* <li>ElGamal</li>
|
||||
* <li>SPHINCS+</li>
|
||||
* <li>KEM-based (e.g., Kyber, Frodo)</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>String Format</h2> <pre>{@code
|
||||
* ALGORITHM:BIT_LENGTH:BASE64_ENCODED_KEY
|
||||
* Example: RSA:2048:MIIBIjANBgkqhkiG9...
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* Note that algorithms which do not support encryption (e.g., DSA, EC) will
|
||||
* have a <code>null</code> cipher in their {@link AsymmetricContext}.
|
||||
* </p>
|
||||
*/
|
||||
public final class KeySupport {
|
||||
|
||||
private KeySupport() {
|
||||
// Utility class; do not instantiate
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a standard Java {@link PublicKey} into an {@link AsymmetricContext},
|
||||
* which contains the corresponding BouncyCastle key parameter and an optional
|
||||
* encryption engine if applicable.
|
||||
*
|
||||
* @param pubKey the public key to convert
|
||||
* @return an {@link AsymmetricContext} wrapping the key and optional cipher
|
||||
* @throws IOException if key decoding fails
|
||||
* @throws IllegalArgumentException if the algorithm is unsupported
|
||||
*/
|
||||
public static AsymmetricContext fromKey(final PublicKey pubKey) throws IOException { // NOPMD
|
||||
return switch (CryptoAlgorithmsNames.fromString(pubKey.getAlgorithm())) {
|
||||
case CryptoAlgorithmsNames.ED25519 -> {
|
||||
byte[] keyBytes = pubKey.getEncoded(); // full SubjectPublicKeyInfo DER-encoded
|
||||
SubjectPublicKeyInfo spki = SubjectPublicKeyInfo.getInstance(keyBytes);
|
||||
byte[] keyData = spki.getPublicKeyData().getBytes();
|
||||
Ed25519PublicKeyParameters keyParam = new Ed25519PublicKeyParameters(keyData, 0);
|
||||
yield new SignatureAsymmetricContext(keyParam); // , null);
|
||||
}
|
||||
case CryptoAlgorithmsNames.RSA -> {
|
||||
final RSAPublicKey rsa = (RSAPublicKey) pubKey;
|
||||
final RSAKeyParameters keyParam = new RSAKeyParameters(false, rsa.getModulus(),
|
||||
rsa.getPublicExponent());
|
||||
yield new ClassicAsymmetricContext(keyParam, new org.bouncycastle.crypto.encodings.OAEPEncoding(
|
||||
new RSAEngine(), new SHA256Digest(), new byte[0]));
|
||||
}
|
||||
case CryptoAlgorithmsNames.DSA -> {
|
||||
final DSAPublicKey dsa = (DSAPublicKey) pubKey;
|
||||
final DSAParams p = dsa.getParams();
|
||||
final DSAParameters dsaParams = new DSAParameters(p.getP(), p.getQ(), p.getG());
|
||||
final DSAPublicKeyParameters keyParam = new DSAPublicKeyParameters(dsa.getY(), dsaParams);
|
||||
yield new SignatureAsymmetricContext(keyParam); // , null); // Signing only
|
||||
}
|
||||
case CryptoAlgorithmsNames.EC -> {
|
||||
final ECPublicKey ec = (ECPublicKey) pubKey;
|
||||
final ECNamedCurveSpec ecSpec = (ECNamedCurveSpec) ec.getParams();
|
||||
final ECNamedCurveParameterSpec bcSpec = ECNamedCurveTable.getParameterSpec(ecSpec.getName());
|
||||
final ECPoint point = bcSpec.getCurve().createPoint(ec.getW().getAffineX(), ec.getW().getAffineY());
|
||||
final ECDomainParameters domainParams = new ECDomainParameters(bcSpec.getCurve(), bcSpec.getG(),
|
||||
bcSpec.getN(), bcSpec.getH(), bcSpec.getSeed());
|
||||
final ECPublicKeyParameters keyParam = new ECPublicKeyParameters(point, domainParams);
|
||||
yield new SignatureAsymmetricContext(keyParam); // , null); // Signing only
|
||||
}
|
||||
case CryptoAlgorithmsNames.ELGAMAL -> {
|
||||
final DHPublicKey elg = (DHPublicKey) pubKey;
|
||||
final DHParameterSpec elParams = elg.getParams();
|
||||
final ElGamalParameters params = new ElGamalParameters(elParams.getP(), elParams.getG());
|
||||
final ElGamalPublicKeyParameters keyParam = new ElGamalPublicKeyParameters(elg.getY(), params);
|
||||
yield new ClassicAsymmetricContext(keyParam, new ElGamalEngine());
|
||||
}
|
||||
case CryptoAlgorithmsNames.SPHINCS_PLUS -> {
|
||||
final SPHINCSPlusPublicKey sphincs = (SPHINCSPlusPublicKey) pubKey;
|
||||
final AsymmetricKeyParameter keyParam = PublicKeyFactory.createKey(sphincs.getEncoded());
|
||||
yield new SignatureAsymmetricContext(keyParam); // , (EncapsulatedSecretGenerator) null);
|
||||
}
|
||||
case CryptoAlgorithmsNames.KYBER -> {
|
||||
// Kyber
|
||||
final MLKEMPublicKeyParameters keyParam = (MLKEMPublicKeyParameters) PublicKeyFactory
|
||||
.createKey(pubKey.getEncoded());
|
||||
yield new KEMAsymmetricContext(keyParam, new MLKEMGenerator(RandomSupport.getRandom()));
|
||||
}
|
||||
case CryptoAlgorithmsNames.FRODO -> {
|
||||
// Frodo
|
||||
final FrodoPublicKeyParameters keyParam = ((FrodoPublicKey) pubKey).getKeyParameters();
|
||||
yield new KEMAsymmetricContext(keyParam, new FrodoKEMGenerator(RandomSupport.getRandom()));
|
||||
}
|
||||
default -> {
|
||||
throw new IllegalArgumentException("Unsupported algorithm: " + pubKey.getAlgorithm());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a standard Java {@link PrivateKey} into an
|
||||
* {@link AsymmetricContext}, which contains the corresponding BouncyCastle key
|
||||
* parameter and an optional encryption engine if applicable.
|
||||
*
|
||||
* @param privKey the private key to convert
|
||||
* @return an {@link AsymmetricContext} wrapping the key and optional cipher
|
||||
* @throws IOException if key decoding fails
|
||||
* @throws IllegalArgumentException if the algorithm is unsupported
|
||||
*/
|
||||
public static AsymmetricContext fromKey(final PrivateKey privKey) throws IOException { // NOPMD
|
||||
return switch (CryptoAlgorithmsNames.fromString(privKey.getAlgorithm())) {
|
||||
case CryptoAlgorithmsNames.ED25519 -> {
|
||||
byte[] keyBytes = privKey.getEncoded();
|
||||
PrivateKeyInfo pki = PrivateKeyInfo.getInstance(keyBytes);
|
||||
byte[] keyData = pki.parsePrivateKey().toASN1Primitive().getEncoded();
|
||||
Ed25519PrivateKeyParameters keyParam = new Ed25519PrivateKeyParameters(keyData, 0);
|
||||
yield new SignatureAsymmetricContext(keyParam); // , null);
|
||||
}
|
||||
case CryptoAlgorithmsNames.RSA -> {
|
||||
final RSAPrivateKey rsa = (RSAPrivateKey) privKey;
|
||||
final RSAKeyParameters keyParam = new RSAKeyParameters(true, rsa.getModulus(),
|
||||
rsa.getPrivateExponent());
|
||||
yield new ClassicAsymmetricContext(keyParam, new org.bouncycastle.crypto.encodings.OAEPEncoding(
|
||||
new RSAEngine(), new SHA256Digest(), new byte[0]));
|
||||
}
|
||||
case CryptoAlgorithmsNames.DSA -> {
|
||||
final DSAPrivateKey dsa = (DSAPrivateKey) privKey;
|
||||
final DSAParams p = dsa.getParams();
|
||||
final DSAParameters dsaParams = new DSAParameters(p.getP(), p.getQ(), p.getG());
|
||||
final DSAPrivateKeyParameters keyParam = new DSAPrivateKeyParameters(dsa.getX(), dsaParams);
|
||||
yield new SignatureAsymmetricContext(keyParam); // , null); // Signing only
|
||||
}
|
||||
case CryptoAlgorithmsNames.EC -> {
|
||||
final ECPrivateKey ecPrivateKey = (ECPrivateKey) privKey;
|
||||
final ECNamedCurveSpec ecNamedSpec = (ECNamedCurveSpec) ecPrivateKey.getParams();
|
||||
final ECNamedCurveParameterSpec bcSpec = ECNamedCurveTable.getParameterSpec(ecNamedSpec.getName());
|
||||
final ECDomainParameters domainParams = new ECDomainParameters(bcSpec.getCurve(), bcSpec.getG(),
|
||||
bcSpec.getN(), bcSpec.getH(), bcSpec.getSeed());
|
||||
final ECPrivateKeyParameters keyParam = new ECPrivateKeyParameters(ecPrivateKey.getS(), domainParams);
|
||||
yield new SignatureAsymmetricContext(keyParam); // , null); // Signing only
|
||||
}
|
||||
case CryptoAlgorithmsNames.ELGAMAL -> {
|
||||
final DHPrivateKey elg = (DHPrivateKey) privKey;
|
||||
final DHParameterSpec elParams = elg.getParams();
|
||||
final ElGamalParameters params = new ElGamalParameters(elParams.getP(), elParams.getG());
|
||||
final ElGamalPrivateKeyParameters keyParam = new ElGamalPrivateKeyParameters(elg.getX(), params);
|
||||
yield new ClassicAsymmetricContext(keyParam, new ElGamalEngine());
|
||||
}
|
||||
case CryptoAlgorithmsNames.SPHINCS_PLUS -> {
|
||||
final SPHINCSPlusPrivateKey sphincs = (SPHINCSPlusPrivateKey) privKey;
|
||||
final AsymmetricKeyParameter keyParam = PrivateKeyFactory.createKey(sphincs.getEncoded());
|
||||
yield new SignatureAsymmetricContext(keyParam); // , (EncapsulatedSecretExtractor) null);
|
||||
}
|
||||
case CryptoAlgorithmsNames.KYBER -> {
|
||||
final MLKEMPrivateKeyParameters keyParam = (MLKEMPrivateKeyParameters) PrivateKeyFactory
|
||||
.createKey(privKey.getEncoded());
|
||||
yield new KEMAsymmetricContext(keyParam, new MLKEMExtractor(keyParam));
|
||||
}
|
||||
case CryptoAlgorithmsNames.FRODO -> {
|
||||
// Frodo
|
||||
final FrodoPrivateKeyParameters keyParam = ((FrodoPrivateKey) privKey).getKeyParameters();
|
||||
yield new KEMAsymmetricContext(keyParam, new FrodoKEMExtractor(keyParam));
|
||||
}
|
||||
default -> {
|
||||
throw new IllegalArgumentException("Unsupported algorithm: " + privKey.getAlgorithm());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a {@link Key} into a string format for storage or transmission.
|
||||
* <p>
|
||||
* Format: <code>ALGORITHM:BIT_LENGTH:BASE64_ENCODED_KEY</code>
|
||||
* </p>
|
||||
*
|
||||
* @param key the key to serialize
|
||||
* @return serialized string representation of the key
|
||||
*/
|
||||
public static String serializeKey(final Key key) {
|
||||
final CryptoAlgorithmsNames algorithm = CryptoAlgorithmsNames.fromString(key.getAlgorithm());
|
||||
final int keySize;
|
||||
|
||||
switch (algorithm) {
|
||||
case CryptoAlgorithmsNames.RSA -> keySize = ((RSAKey) key).getModulus().bitLength();
|
||||
case CryptoAlgorithmsNames.DSA -> keySize = ((DSAKey) key).getParams().getP().bitLength();
|
||||
case CryptoAlgorithmsNames.EC -> keySize = ((ECKey) key).getParams().getCurve().getField().getFieldSize();
|
||||
case CryptoAlgorithmsNames.ELGAMAL -> keySize = ((DHKey) key).getParams().getP().bitLength(); // ElGamal
|
||||
// keys often
|
||||
// use DH
|
||||
// interface
|
||||
case CryptoAlgorithmsNames.SPHINCS_PLUS -> {
|
||||
// Approximate bit length from encoded length (not precisely meaningful for PQC)
|
||||
keySize = key.getEncoded().length * 8;
|
||||
}
|
||||
case CryptoAlgorithmsNames.KYBER -> {
|
||||
// Kyber: ML-KEM-1024 => KYBER.displayName() + "-" + {keySize}
|
||||
keySize = Integer
|
||||
.parseInt(key.getAlgorithm().substring(CryptoAlgorithmsNames.KYBER.displayName().length() + 1));
|
||||
}
|
||||
default -> {
|
||||
// fallback
|
||||
keySize = key.getEncoded().length * 8; // fallback
|
||||
}
|
||||
}
|
||||
|
||||
final String base64 = Base64.getEncoder().encodeToString(key.getEncoded());
|
||||
return algorithm + ":" + keySize + ":" + base64;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a public key from a string previously generated using a matching
|
||||
* serialization format (e.g., by {@link #serializeKey(Key)}).
|
||||
*
|
||||
* <p>
|
||||
* The expected format of the input string is:
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
* ALGORITHM:BIT_LENGTH:BASE64_ENCODED_KEY
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* The {@code BIT_LENGTH} component is ignored during parsing and is only used
|
||||
* for informational purposes.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This method reconstructs a {@link PublicKey} instance using the
|
||||
* {@link java.security.spec.X509EncodedKeySpec} and the {@link KeyFactory} for
|
||||
* the specified algorithm. The BouncyCastle security provider is required to be
|
||||
* registered beforehand.
|
||||
* </p>
|
||||
*
|
||||
* @param serialized the serialized string representation of the public key
|
||||
* @return the deserialized {@link PublicKey} object
|
||||
* @throws IllegalArgumentException if the serialized string is malformed or
|
||||
* does not contain exactly three parts
|
||||
* @throws NoSuchAlgorithmException if the algorithm specified in the serialized
|
||||
* string is not supported
|
||||
* @throws NoSuchProviderException if the BouncyCastle provider is not
|
||||
* available
|
||||
* @throws InvalidKeySpecException if the key specification is invalid or
|
||||
* incompatible with the algorithm
|
||||
*/
|
||||
public static PublicKey deserializePublicKey(final String serialized)
|
||||
throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
|
||||
final int PARTS = 3;
|
||||
|
||||
final String[] parts = serialized.split(":", PARTS);
|
||||
if (parts.length != PARTS) {
|
||||
throw new IllegalArgumentException("Invalid serialized key format");
|
||||
}
|
||||
final CryptoAlgorithmsNames algorithm = CryptoAlgorithmsNames.fromString(parts[0]);
|
||||
final KeyFactory keyFactory = algorithm.getFactory();
|
||||
final byte[] encoded = Base64.getDecoder().decode(parts[2]);
|
||||
final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
|
||||
|
||||
return keyFactory.generatePublic(keySpec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a private key from a string representation.
|
||||
*
|
||||
* <p>
|
||||
* The expected format of the input string is:
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
* ALGORITHM:BIT_LENGTH:BASE64_ENCODED_PKCS8_KEY
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* Example:
|
||||
* </p>
|
||||
*
|
||||
* <pre>{@code
|
||||
* RSA:2048:MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBK...
|
||||
* }</pre>
|
||||
*
|
||||
* <p>
|
||||
* This method supports keys for standard algorithms such as RSA, DSA, EC, and
|
||||
* ElGamal, provided they are properly encoded in PKCS#8 format and recognized
|
||||
* by the BouncyCastle provider.
|
||||
* </p>
|
||||
* <p>
|
||||
* The bit length field is ignored during deserialization, as the key strength
|
||||
* is derived from the actual key material.
|
||||
* </p>
|
||||
*
|
||||
* @param serialized the serialized private key string in the format
|
||||
* {@code ALGORITHM:BIT_LENGTH:BASE64}
|
||||
* @return the reconstructed {@link PrivateKey} instance
|
||||
* @throws IllegalArgumentException if the serialized format is invalid
|
||||
* @throws NoSuchAlgorithmException if the algorithm is not supported
|
||||
* @throws NoSuchProviderException if the BouncyCastle provider is not
|
||||
* available
|
||||
* @throws InvalidKeySpecException if the key specification is invalid or the
|
||||
* key cannot be reconstructed
|
||||
*/
|
||||
public static PrivateKey deserializePrivateKey(final String serialized)
|
||||
throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
|
||||
final int PARTS = 3;
|
||||
|
||||
final String[] parts = serialized.split(":", PARTS);
|
||||
if (parts.length != PARTS) {
|
||||
throw new IllegalArgumentException("Invalid serialized key format");
|
||||
}
|
||||
final CryptoAlgorithmsNames algorithm = CryptoAlgorithmsNames.fromString(parts[0]);
|
||||
final KeyFactory keyFactory = algorithm.getFactory();
|
||||
final byte[] encoded = Base64.getDecoder().decode(parts[2]);
|
||||
return keyFactory.generatePrivate(new java.security.spec.PKCS8EncodedKeySpec(encoded));
|
||||
}
|
||||
}
|
||||
182
lib/src/main/java/zeroecho/util/OutputToInputStreamAdapter.java
Normal file
182
lib/src/main/java/zeroecho/util/OutputToInputStreamAdapter.java
Normal file
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* 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 zeroecho.util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* An abstract adapter that converts an {@link OutputStream}-based
|
||||
* transformation (such as encryption or compression) into an
|
||||
* {@link InputStream}-based one. This class reads data from a given
|
||||
* {@link InputStream} (the {@code previousInput}), processes it through a
|
||||
* transformation {@link OutputStream} (e.g., encryption, compression), and
|
||||
* exposes the transformed data via the standard {@link InputStream} API.
|
||||
* <p>
|
||||
* Subclasses must initialize the {@code transformationOut} field with a proper
|
||||
* {@link OutputStream} that writes transformed data into the internal buffer
|
||||
* ({@code baos}). This initialization must be done <strong>after construction
|
||||
* and before any read operation</strong>, via a subclass- specific method such
|
||||
* as an <code>initialize(...)</code> method.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Subclass Contract</h2>
|
||||
* <ul>
|
||||
* <li>Set {@code transformationOut} to a valid {@link OutputStream} that writes
|
||||
* to {@code baos}.</li>
|
||||
* <li>Do <em>not</em> call virtual methods like initialization from
|
||||
* constructors.</li>
|
||||
* <li>Call the initialization method before performing any reads.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class OutputToInputStreamAdapter extends InputStream {
|
||||
/**
|
||||
* Default buffer size in bytes. Typically set to 8 KB (8 * 1024 bytes).
|
||||
*/
|
||||
protected static final int DEFAULT_BUF_SIZE = 8 * 1024;
|
||||
/**
|
||||
* Buffer size in bytes used by this instance. Initialized during object
|
||||
* construction and remains constant.
|
||||
*/
|
||||
protected final int BUF_SIZE;
|
||||
|
||||
/**
|
||||
* The original input stream containing untransformed data.
|
||||
*/
|
||||
protected final InputStream previousInput;
|
||||
|
||||
/**
|
||||
* A buffer holding transformed data.
|
||||
*/
|
||||
protected ByteArrayOutputStream baos;
|
||||
|
||||
/**
|
||||
* The output stream performing transformation and writing to {@code baos}. Must
|
||||
* be initialized by subclasses before use.
|
||||
*/
|
||||
protected OutputStream transformationOut;
|
||||
/**
|
||||
* Input stream initialized to a no-op {@code InputStream} that contains no
|
||||
* data.
|
||||
* <p>
|
||||
* This stream is set to {@link InputStream#nullInputStream()}, which is a
|
||||
* convenient way to avoid {@code null} values while providing a valid,
|
||||
* non-functional stream. Useful as a default placeholder until a real stream is
|
||||
* assigned.
|
||||
* </p>
|
||||
*/
|
||||
private InputStream bais = nullInputStream();
|
||||
|
||||
/**
|
||||
* Constructs a new adapter wrapping the specified input stream with a given
|
||||
* buffer size for transformation output.
|
||||
*
|
||||
* @param previousInput The input stream to read untransformed data from.
|
||||
* @param bufSize The buffer size for the transformation output buffer.
|
||||
*/
|
||||
public OutputToInputStreamAdapter(final InputStream previousInput, final int bufSize) {
|
||||
super();
|
||||
|
||||
Objects.requireNonNull(previousInput, "input stream must not be null");
|
||||
|
||||
this.previousInput = previousInput;
|
||||
this.BUF_SIZE = bufSize;
|
||||
baos = new ByteArrayOutputStream(BUF_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new adapter wrapping the specified input stream using the
|
||||
* default buffer size.
|
||||
*
|
||||
* @param previousInput The input stream to read untransformed data from.
|
||||
*/
|
||||
public OutputToInputStreamAdapter(final InputStream previousInput) {
|
||||
this(previousInput, DEFAULT_BUF_SIZE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
ensureData();
|
||||
return bais.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(final byte[] b, final int off, final int len) throws IOException {
|
||||
ensureData();
|
||||
return bais.read(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
previousInput.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads untransformed data from {@code previousInput}, writes it to
|
||||
* {@code transformationOut} for processing, and buffers the transformed data in
|
||||
* {@code baos} for reading.
|
||||
*
|
||||
* @throws IOException If an I/O error occurs during reading or transformation.
|
||||
*/
|
||||
private void ensureData() throws IOException {
|
||||
if (bais.available() > 0 || baos == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
bais = null; // NOPMD by Leo Galambos on 6/1/25, 4:09 PM
|
||||
|
||||
final byte[] chunk = previousInput.readNBytes(BUF_SIZE);
|
||||
if (chunk.length == 0) {
|
||||
transformationOut.close();
|
||||
final byte[] finalBytes = baos.toByteArray();
|
||||
bais = new ByteArrayInputStream(finalBytes);
|
||||
baos = null; // NOPMD
|
||||
return;
|
||||
} else {
|
||||
transformationOut.write(chunk);
|
||||
}
|
||||
|
||||
final byte[] chunkBytes = baos.toByteArray();
|
||||
bais = new ByteArrayInputStream(chunkBytes);
|
||||
baos.reset();
|
||||
}
|
||||
}
|
||||
147
lib/src/main/java/zeroecho/util/Password.java
Normal file
147
lib/src/main/java/zeroecho/util/Password.java
Normal file
@@ -0,0 +1,147 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.util;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Utility class for generating random passwords and secure random byte arrays.
|
||||
* <p>
|
||||
* This class provides methods to:
|
||||
* <ul>
|
||||
* <li>Generate cryptographically secure random byte arrays of specified
|
||||
* length.</li>
|
||||
* <li>Generate random passwords composed of characters derived from random
|
||||
* bytes.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* A single instance of {@link SecureRandom} is used unless
|
||||
* {@code UNSAFE_SINGLE_SECURE} is set to {@code false}, in which case a new
|
||||
* {@link SecureRandom} instance is created for each operation.
|
||||
* <p>
|
||||
* This class is thread-safe through use of a {@link ReentrantLock}.
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public final class Password {
|
||||
/**
|
||||
* Private constructor to prevent instantiation of this utility class.
|
||||
*/
|
||||
private Password() {
|
||||
// this is a utility class
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random password by filling the provided byte array with
|
||||
* cryptographically strong random bytes. The randomness is influenced by a
|
||||
* combination of a user-supplied seed string and a randomly generated salt,
|
||||
* ensuring that the result is both secure and non-deterministic across multiple
|
||||
* invocations with the same input.
|
||||
* <p>
|
||||
* Internally, the method uses the SHA-256 digest of the concatenation of the
|
||||
* seed and a 16-byte random salt to derive a seed for a {@link SecureRandom}
|
||||
* instance. This seed is mixed into the internal state of the
|
||||
* {@code SecureRandom} generator to produce a high-entropy, unpredictable byte
|
||||
* sequence. As a result, the output differs each time the method is called,
|
||||
* even with the same seed and password buffer.
|
||||
* <p>
|
||||
* Note: The generated salt is not returned or stored. If you require
|
||||
* reproducible output or need to verify the result later, you must persist the
|
||||
* salt separately.
|
||||
*
|
||||
* @param password the byte array to be filled with random password bytes; must
|
||||
* not be {@code null}
|
||||
* @param seed a user-supplied string used to influence the randomness
|
||||
* generation; must not be {@code null}
|
||||
* @return the same {@code password} byte array, now filled with
|
||||
* cryptographically strong random data
|
||||
* @throws NoSuchAlgorithmException if the SHA-256 digest or strong
|
||||
* {@code SecureRandom} implementation is not
|
||||
* available
|
||||
* @throws NullPointerException if {@code password} or {@code seed} is
|
||||
* {@code null}
|
||||
*/
|
||||
public static byte[] generateRandom(final byte[] password, final String seed) throws NoSuchAlgorithmException {
|
||||
final byte[] salt = new byte[16];
|
||||
RandomSupport.getRandom().nextBytes(salt);
|
||||
|
||||
// Combine input + salt
|
||||
final byte[] seedBytes = seed.getBytes(StandardCharsets.UTF_8);
|
||||
final byte[] combined = new byte[seedBytes.length + salt.length];
|
||||
System.arraycopy(seedBytes, 0, combined, 0, seedBytes.length);
|
||||
System.arraycopy(salt, 0, combined, seedBytes.length, salt.length);
|
||||
|
||||
// Derive seed using SHA-256
|
||||
final MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
final byte[] rndSeed = digest.digest(combined);
|
||||
|
||||
// Seed SecureRandom (deterministic per run, but uses a fresh salt)
|
||||
final SecureRandom seededRandom = SecureRandom.getInstanceStrong();
|
||||
seededRandom.setSeed(rndSeed); // mixes with internal state
|
||||
|
||||
// Generate random output
|
||||
seededRandom.nextBytes(password);
|
||||
|
||||
return password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a cryptographically secure random password consisting of printable
|
||||
* ASCII characters.
|
||||
*
|
||||
* @param length the desired length of the password; must be positive
|
||||
* @return a random password string using printable characters in the ASCII
|
||||
* range 33–126
|
||||
* @throws IllegalArgumentException if {@code length} is less than or equal to
|
||||
* zero
|
||||
*/
|
||||
public static String generatePrintablePassword(final int length) {
|
||||
if (length <= 0) {
|
||||
throw new IllegalArgumentException("Password length must be greater than zero");
|
||||
}
|
||||
|
||||
final StringBuilder password = new StringBuilder(length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
final int ascii = RandomSupport.getRandom().nextInt('~' - '!' + 1) + '!';
|
||||
password.append((char) ascii);
|
||||
}
|
||||
|
||||
return password.toString();
|
||||
}
|
||||
}
|
||||
131
lib/src/main/java/zeroecho/util/RandomSupport.java
Normal file
131
lib/src/main/java/zeroecho/util/RandomSupport.java
Normal 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 zeroecho.util;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* Utility class providing support for secure random number generation.
|
||||
* <p>
|
||||
* This class encapsulates logic for generating cryptographically secure random
|
||||
* data using Java's {@link SecureRandom}. It optionally supports a singleton
|
||||
* instance pattern for the {@code SecureRandom} generator, controlled by the
|
||||
* {@code UNSAFE_SINGLE_SECURE} flag.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <strong>Note:</strong> Reusing a single {@code SecureRandom} instance can
|
||||
* improve performance, but may reduce randomness guarantees in some
|
||||
* multi-threaded or long-lived contexts. Always assess your threat model and
|
||||
* performance needs when toggling {@code UNSAFE_SINGLE_SECURE}.
|
||||
* </p>
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public final class RandomSupport {
|
||||
private static final Logger LOG = Logger.getLogger(RandomSupport.class.getName());
|
||||
|
||||
/** Flag indicating whether a single {@link SecureRandom} instance is reused. */
|
||||
private final static boolean UNSAFE_SINGLE_SECURE = true;
|
||||
/** Lock for thread-safe access to the {@link SecureRandom} instance. */
|
||||
private static Lock instanceLock = new ReentrantLock();
|
||||
/** Shared {@link SecureRandom} instance. */
|
||||
private static SecureRandom RANDOM;
|
||||
|
||||
static {
|
||||
try {
|
||||
RANDOM = SecureRandom.getInstanceStrong();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
LOG.logp(Level.WARNING, "Password", "", "NoSuchAlgorithmException", e);
|
||||
RANDOM = new SecureRandom();
|
||||
}
|
||||
}
|
||||
|
||||
private RandomSupport() {
|
||||
// this is a utility class
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a {@link SecureRandom} instance.
|
||||
* <p>
|
||||
* If {@code UNSAFE_SINGLE_SECURE} is true, returns a shared instance;
|
||||
* otherwise, creates a new {@link SecureRandom} instance.
|
||||
*
|
||||
* @return A {@link SecureRandom} instance for random number generation.
|
||||
*/
|
||||
public static SecureRandom getRandom() {
|
||||
instanceLock.lock();
|
||||
try {
|
||||
if (UNSAFE_SINGLE_SECURE) {
|
||||
return RANDOM;
|
||||
} else {
|
||||
LOG.log(Level.INFO, "creating a new SecureRandom");
|
||||
return new SecureRandom();
|
||||
}
|
||||
} finally {
|
||||
instanceLock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a secure random byte array of the specified size.
|
||||
*
|
||||
* @param size The size of the byte array to generate.
|
||||
* @return A byte array filled with cryptographically secure random bytes.
|
||||
*/
|
||||
public static byte[] generateRandom(final int size) {
|
||||
return generateRandom(new byte[size]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the given byte array with random bytes and returns it.
|
||||
* <p>
|
||||
* This method uses a thread-safe {@code SecureRandom} instance to generate
|
||||
* cryptographically strong random values.
|
||||
*
|
||||
* @param buffer the byte array to fill with random bytes
|
||||
* @return the same byte array, now containing random data
|
||||
* @throws NullPointerException if {@code buffer} is {@code null}
|
||||
*/
|
||||
public static byte[] generateRandom(final byte[] buffer) {
|
||||
getRandom().nextBytes(buffer);
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
523
lib/src/main/java/zeroecho/util/SymmetricStreamBuilder.java
Normal file
523
lib/src/main/java/zeroecho/util/SymmetricStreamBuilder.java
Normal file
@@ -0,0 +1,523 @@
|
||||
/**
|
||||
* 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 zeroecho.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.InvalidParameterException;
|
||||
|
||||
import org.bouncycastle.crypto.BufferedBlockCipher;
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
import org.bouncycastle.crypto.DataLengthException;
|
||||
import org.bouncycastle.crypto.InvalidCipherTextException;
|
||||
import org.bouncycastle.crypto.StreamCipher;
|
||||
import org.bouncycastle.crypto.modes.AEADBlockCipher;
|
||||
|
||||
import zeroecho.util.aes.AesCipherType;
|
||||
import zeroecho.util.aes.AesParameters;
|
||||
import zeroecho.util.aes.AesSupport;
|
||||
|
||||
/**
|
||||
* A builder for creating {@link InputStream}s that apply symmetric encryption
|
||||
* or decryption using a variety of cipher types (block, stream, or AEAD).
|
||||
* <p>
|
||||
* Supports Bouncy Castle's {@link BufferedBlockCipher}, {@link StreamCipher},
|
||||
* and {@link AEADBlockCipher} implementations. This builder validates
|
||||
* configuration and initializes the cipher before wrapping the input stream in
|
||||
* a cipher-specific processing stream.
|
||||
*/
|
||||
public final class SymmetricStreamBuilder {
|
||||
/** Internal buffer size used during stream processing. */
|
||||
public static final int BUF_SIZE = 4096;
|
||||
|
||||
private InputStream in;
|
||||
private Object cipher;
|
||||
private CipherParameters params;
|
||||
|
||||
private SymmetricStreamBuilder() {
|
||||
// private constructor
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@code SymmetricStreamBuilder}.
|
||||
*
|
||||
* @return a new builder instance
|
||||
*/
|
||||
public static SymmetricStreamBuilder newBuilder() {
|
||||
return new SymmetricStreamBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the input stream that will be encrypted or decrypted.
|
||||
*
|
||||
* @param in the source {@link InputStream}
|
||||
* @return this builder instance
|
||||
*/
|
||||
public SymmetricStreamBuilder withInputStream(final InputStream in) {
|
||||
this.in = in;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the cipher to be used for symmetric encryption or decryption.
|
||||
*
|
||||
* This method accepts either a predefined {@link AesCipherType}, or a concrete
|
||||
* cipher instance such as:
|
||||
* <ul>
|
||||
* <li>{@link org.bouncycastle.crypto.StreamCipher}</li>
|
||||
* <li>{@link org.bouncycastle.crypto.BufferedBlockCipher}</li>
|
||||
* <li>{@link org.bouncycastle.crypto.modes.AEADBlockCipher}</li>
|
||||
* </ul>
|
||||
* If an {@link AesCipherType} is provided, it is internally converted into the
|
||||
* appropriate cipher instance via its {@code createCipher()} method.
|
||||
*
|
||||
* Any unsupported cipher type will result in an
|
||||
* {@link InvalidParameterException}.
|
||||
*
|
||||
* @param cipher the cipher type or instance to use for stream processing; must
|
||||
* be an instance of {@code AesCipherType}, {@code StreamCipher},
|
||||
* {@code BufferedBlockCipher}, or {@code AEADBlockCipher}
|
||||
*
|
||||
* @return this {@code SymmetricStreamBuilder} instance for fluent chaining
|
||||
*
|
||||
* @throws InvalidParameterException if the provided cipher is of an unsupported
|
||||
* type
|
||||
*
|
||||
* @see AesCipherType
|
||||
* @see org.bouncycastle.crypto.StreamCipher
|
||||
* @see org.bouncycastle.crypto.BufferedBlockCipher
|
||||
* @see org.bouncycastle.crypto.modes.AEADBlockCipher
|
||||
*/
|
||||
public SymmetricStreamBuilder withCipher(final Object cipher) {
|
||||
this.cipher = switch (cipher) {
|
||||
case AesCipherType aes -> aes.createCipher();
|
||||
case BufferedBlockCipher buff -> buff;
|
||||
case StreamCipher stream -> stream;
|
||||
case AEADBlockCipher aead -> aead;
|
||||
default -> throw new InvalidParameterException("Unsupported cipher type: " + cipher.getClass().getName());
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the cipher parameters (e.g., key, IV, nonce).
|
||||
*
|
||||
* @param params the cipher parameters
|
||||
* @return this builder instance
|
||||
*/
|
||||
public SymmetricStreamBuilder withParameters(final CipherParameters params) {
|
||||
this.params = params;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures this {@link SymmetricStreamBuilder} with both the AES cipher
|
||||
* instance and its associated parameters, derived from the specified
|
||||
* {@link AesParameters}.
|
||||
*
|
||||
* <p>
|
||||
* This method performs two key operations:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>Initializes the cipher by invoking {@link AesCipherType#createCipher()}
|
||||
* on {@code params.cipherType()}.</li>
|
||||
* <li>Generates the corresponding {@link CipherParameters} using the provided
|
||||
* secret key, initialization vector (IV), and optional Additional Authenticated
|
||||
* Data (AAD).</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* If the selected cipher type supports AEAD (e.g., AES-GCM), the
|
||||
* {@code params.aad()} value will be incorporated into the parameter set. For
|
||||
* non-AEAD modes, any provided AAD is ignored.
|
||||
* </p>
|
||||
*
|
||||
* @param params the {@link AesParameters} object containing the cipher type,
|
||||
* secret key, IV, and optional AAD; must not be {@code null}
|
||||
* @return this {@code SymmetricStreamBuilder} instance for method chaining
|
||||
* @throws NullPointerException if {@code params} is {@code null}
|
||||
*/
|
||||
public SymmetricStreamBuilder withCipherAndParameters(final AesParameters params) {
|
||||
withCipher(params.cipherType().createCipher());
|
||||
this.params = AesSupport.getParameters(params.cipherType(), params.key(), params.iv(), params.aad());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a stream that encrypts data from the configured input stream.
|
||||
*
|
||||
* @return an {@link InputStream} that provides encrypted output
|
||||
* @throws IllegalStateException if the builder is not fully configured
|
||||
*/
|
||||
public InputStream buildEncryptingStream() {
|
||||
return buildStream(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a stream that decrypts data from the configured input stream.
|
||||
*
|
||||
* @return an {@link InputStream} that provides decrypted output
|
||||
* @throws IllegalStateException if the builder is not fully configured
|
||||
*/
|
||||
public InputStream buildDecryptingStream() {
|
||||
return buildStream(false);
|
||||
}
|
||||
|
||||
private InputStream buildStream(final boolean forEncryption) {
|
||||
if (cipher == null || in == null || params == null) {
|
||||
throw new IllegalStateException("InputStream, cipher, and parameters must be provided.");
|
||||
}
|
||||
|
||||
return switch (cipher) {
|
||||
case BufferedBlockCipher bbc -> {
|
||||
bbc.init(forEncryption, params);
|
||||
yield new BufferedBlockCipherInputStream(in, bbc);
|
||||
}
|
||||
case AEADBlockCipher aead -> {
|
||||
aead.init(forEncryption, params);
|
||||
yield new AEADBlockCipherInputStream(in, aead);
|
||||
}
|
||||
case StreamCipher sc -> {
|
||||
sc.init(forEncryption, params);
|
||||
yield new StreamCipherInputStream(in, sc);
|
||||
}
|
||||
default -> throw new IllegalStateException("Unsupported cipher type: " + cipher.getClass().getName());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract base class for cipher-based {@link InputStream} implementations that
|
||||
* process encrypted or decrypted data using a symmetric cipher.
|
||||
*
|
||||
* Manages buffered input/output handling, stream reading, and stream
|
||||
* finalization logic. Concrete subclasses implement cipher-specific processing
|
||||
* through the {@link #processCipher} method.
|
||||
*/
|
||||
abstract class SymmetricCipherInputStreamBase extends InputStream {
|
||||
/**
|
||||
* The underlying input stream supplying raw (encrypted or plaintext) data.
|
||||
*/
|
||||
protected final InputStream in;
|
||||
/**
|
||||
* Internal buffer used to read data from the underlying input stream.
|
||||
*/
|
||||
protected final byte[] inputBuffer = new byte[SymmetricStreamBuilder.BUF_SIZE];
|
||||
/**
|
||||
* Buffer holding the output from cipher processing.
|
||||
*/
|
||||
protected final byte[] outputBuffer;
|
||||
/**
|
||||
* Current read position within the output buffer.
|
||||
*/
|
||||
protected int outputPos;
|
||||
/**
|
||||
* Total number of valid bytes currently in the output buffer.
|
||||
*/
|
||||
protected int outputLen;
|
||||
/**
|
||||
* Indicates whether the cipher has been finalized (no more data to process).
|
||||
*/
|
||||
protected boolean finalProcessed;
|
||||
|
||||
/**
|
||||
* Constructs a cipher input stream with the specified underlying input and
|
||||
* output buffer size.
|
||||
*
|
||||
* @param in the underlying input stream
|
||||
* @param outputBufferSize the size of the buffer to hold processed output
|
||||
*/
|
||||
protected SymmetricCipherInputStreamBase(final InputStream in, final int outputBufferSize) {
|
||||
super();
|
||||
|
||||
this.in = in;
|
||||
this.outputBuffer = new byte[outputBufferSize];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a single byte from the encrypted or decrypted stream.
|
||||
*
|
||||
* @return the byte read, or {@code -1} if end of stream
|
||||
* @throws IOException if an I/O or cipher processing error occurs
|
||||
*/
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (outputPos >= outputLen && !fillBuffer()) {
|
||||
return -1;
|
||||
}
|
||||
return outputBuffer[outputPos++] & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads up to {@code len} bytes into the given buffer, starting at {@code off}.
|
||||
*
|
||||
* @param b the destination buffer
|
||||
* @param off the start offset
|
||||
* @param len the maximum number of bytes to read
|
||||
* @return the number of bytes read, or {@code -1} if end of stream
|
||||
* @throws IOException if an I/O or cipher processing error occurs
|
||||
*/
|
||||
@Override
|
||||
public int read(final byte[] b, final int off, final int len) throws IOException {
|
||||
if (outputPos >= outputLen && !fillBuffer()) {
|
||||
return -1;
|
||||
}
|
||||
final int toCopy = Math.min(len, outputLen - outputPos);
|
||||
System.arraycopy(outputBuffer, outputPos, b, off, toCopy);
|
||||
outputPos += toCopy;
|
||||
return toCopy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to refill the output buffer by reading from the input stream and
|
||||
* processing the data with the cipher.
|
||||
* <p>
|
||||
* If the end of input is reached, this method finalizes the cipher (if
|
||||
* applicable) and marks the stream as complete.
|
||||
*
|
||||
* @return {@code true} if new output was generated and is available to read,
|
||||
* {@code false} if end of stream was reached and no more output is
|
||||
* available
|
||||
* @throws IOException if cipher processing fails
|
||||
*/
|
||||
protected boolean fillBuffer() throws IOException {
|
||||
if (finalProcessed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int read;
|
||||
try {
|
||||
do {
|
||||
read = in.readNBytes(inputBuffer, 0, inputBuffer.length);
|
||||
outputLen = processCipher(inputBuffer, read, outputBuffer);
|
||||
} while (outputLen == 0 && read > 0);
|
||||
finalProcessed = read == 0;
|
||||
outputPos = 0;
|
||||
return outputLen > 0;
|
||||
} catch (DataLengthException | IllegalStateException | InvalidCipherTextException e) {
|
||||
throw new IOException("Cipher processing failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a chunk of data using the configured cipher.
|
||||
* <p>
|
||||
* Implementations must detect {@code inputLen == 0} to perform finalization if
|
||||
* the cipher supports it (e.g., for block or AEAD ciphers).
|
||||
* <p>
|
||||
* For stream ciphers, this typically means returning {@code 0} as no
|
||||
* finalization is needed.
|
||||
*
|
||||
* @param input the buffer containing input data; contents are undefined if
|
||||
* {@code inputLen == 0}
|
||||
* @param inputLen the number of bytes to process, or {@code 0} to finalize the
|
||||
* cipher
|
||||
* @param output the buffer into which processed output is written
|
||||
* @return the number of bytes written to the output buffer
|
||||
*
|
||||
* @throws DataLengthException if the input data is too large for the
|
||||
* cipher
|
||||
* @throws IllegalStateException if the cipher has not been properly
|
||||
* initialized
|
||||
* @throws InvalidCipherTextException if finalization fails due to invalid
|
||||
* padding or corrupted ciphertext
|
||||
* @throws Exception if any other unexpected error occurs
|
||||
* during processing
|
||||
*/
|
||||
protected abstract int processCipher(byte[] input, int inputLen, byte[] output) throws InvalidCipherTextException;
|
||||
|
||||
/**
|
||||
* Closes the underlying input stream and releases any associated resources.
|
||||
*
|
||||
* @throws IOException if an I/O error occurs during closing
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link InputStream} that applies a {@link BufferedBlockCipher} to
|
||||
* transform the data during reading.
|
||||
* <p>
|
||||
* This stream supports both encryption and decryption depending on how the
|
||||
* cipher is initialized.
|
||||
*/
|
||||
class BufferedBlockCipherInputStream extends SymmetricCipherInputStreamBase {
|
||||
private final BufferedBlockCipher cipher;
|
||||
|
||||
/**
|
||||
* Creates a new stream that wraps the given input and processes it using a
|
||||
* {@link BufferedBlockCipher}.
|
||||
*
|
||||
* @param in the input stream to wrap
|
||||
* @param cipher the initialized {@link BufferedBlockCipher}
|
||||
*/
|
||||
public BufferedBlockCipherInputStream(final InputStream in, final BufferedBlockCipher cipher) {
|
||||
super(in, cipher.getOutputSize(SymmetricStreamBuilder.BUF_SIZE));
|
||||
this.cipher = cipher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a block of input using the {@link BufferedBlockCipher}.
|
||||
* <p>
|
||||
* If {@code inputLen == 0}, the cipher is finalized via
|
||||
* {@link BufferedBlockCipher#doFinal(byte[], int)}, completing any remaining
|
||||
* buffered input and writing final output (including padding, if applicable).
|
||||
* Otherwise, the method delegates to
|
||||
* {@link BufferedBlockCipher#processBytes(byte[], int, int, byte[], int)}.
|
||||
*
|
||||
* @param input the input buffer containing plaintext or ciphertext bytes;
|
||||
* ignored when {@code inputLen == 0}
|
||||
* @param inputLen number of bytes to process from the input buffer, or
|
||||
* {@code 0} to trigger finalization
|
||||
* @param output the output buffer to write processed data into
|
||||
* @return the number of bytes written to the output buffer
|
||||
*
|
||||
* @throws DataLengthException if the input data is too large for the
|
||||
* cipher
|
||||
* @throws IllegalStateException if the cipher has not been properly
|
||||
* initialized
|
||||
* @throws InvalidCipherTextException if finalization fails due to invalid
|
||||
* padding or corrupted ciphertext
|
||||
*/
|
||||
@Override
|
||||
protected int processCipher(final byte[] input, final int inputLen, final byte[] output)
|
||||
throws InvalidCipherTextException {
|
||||
if (inputLen == 0) {
|
||||
return cipher.doFinal(output, 0);
|
||||
}
|
||||
return cipher.processBytes(input, 0, inputLen, output, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link InputStream} that applies an {@link AEADBlockCipher} to transform
|
||||
* the data during reading.
|
||||
* <p>
|
||||
* AEAD (Authenticated Encryption with Associated Data) ciphers require
|
||||
* finalization to validate authentication tags.
|
||||
*/
|
||||
class AEADBlockCipherInputStream extends SymmetricCipherInputStreamBase {
|
||||
private final AEADBlockCipher cipher;
|
||||
|
||||
/**
|
||||
* Creates a new stream that wraps the given input and processes it using an
|
||||
* {@link AEADBlockCipher}.
|
||||
*
|
||||
* @param in the input stream to wrap
|
||||
* @param cipher the initialized {@link AEADBlockCipher}
|
||||
*/
|
||||
public AEADBlockCipherInputStream(final InputStream in, final AEADBlockCipher cipher) {
|
||||
super(in, cipher.getOutputSize(SymmetricStreamBuilder.BUF_SIZE) + AesSupport.BLOCK_SIZE);
|
||||
this.cipher = cipher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a block of input using the {@link AEADBlockCipher}.
|
||||
* <p>
|
||||
* If {@code inputLen == 0}, the cipher is finalized via
|
||||
* {@link AEADBlockCipher#doFinal(byte[], int)}, completing encryption or
|
||||
* decryption and verifying the authentication tag. If authentication fails, an
|
||||
* {@link InvalidCipherTextException} is thrown.
|
||||
* <p>
|
||||
* For normal processing, the method delegates to
|
||||
* {@link AEADBlockCipher#processBytes(byte[], int, int, byte[], int)} to
|
||||
* transform the input data into ciphertext or plaintext.
|
||||
*
|
||||
* @param input the input buffer containing plaintext or ciphertext data;
|
||||
* ignored when {@code inputLen == 0}
|
||||
* @param inputLen number of bytes to process, or {@code 0} to finalize and
|
||||
* verify authentication
|
||||
* @param output the output buffer to receive processed data
|
||||
* @return the number of bytes written to the output buffer
|
||||
*
|
||||
* @throws IllegalStateException if the cipher is not properly initialized
|
||||
* @throws InvalidCipherTextException if finalization fails due to
|
||||
* authentication tag mismatch
|
||||
*/
|
||||
@Override
|
||||
protected int processCipher(final byte[] input, final int inputLen, final byte[] output)
|
||||
throws InvalidCipherTextException {
|
||||
if (inputLen == 0) {
|
||||
return cipher.doFinal(output, 0);
|
||||
}
|
||||
|
||||
return cipher.processBytes(input, 0, inputLen, output, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link InputStream} that applies a {@link StreamCipher} to transform the
|
||||
* data during reading.
|
||||
* <p>
|
||||
* Stream ciphers operate byte-by-byte and do not require finalization.
|
||||
*/
|
||||
class StreamCipherInputStream extends SymmetricCipherInputStreamBase {
|
||||
private final StreamCipher cipher;
|
||||
|
||||
/**
|
||||
* Creates a new stream that wraps the given input and processes it using a
|
||||
* {@link StreamCipher}.
|
||||
*
|
||||
* @param in the input stream to wrap
|
||||
* @param cipher the initialized {@link StreamCipher}
|
||||
*/
|
||||
public StreamCipherInputStream(final InputStream in, final StreamCipher cipher) {
|
||||
super(in, SymmetricStreamBuilder.BUF_SIZE);
|
||||
this.cipher = cipher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a block of input using the stream cipher. Finalization is a no-op
|
||||
* for stream ciphers (returns 0 bytes).
|
||||
*
|
||||
* @param input input buffer
|
||||
* @param inputLen number of bytes to process, or {@code 0} to signal end of
|
||||
* stream
|
||||
* @param output output buffer
|
||||
* @return number of bytes written to output
|
||||
*/
|
||||
@Override
|
||||
protected int processCipher(final byte[] input, final int inputLen, final byte[] output) {
|
||||
if (inputLen == 0) {
|
||||
// No finalization for StreamCipher, just indicate end of stream
|
||||
return 0;
|
||||
}
|
||||
cipher.processBytes(input, 0, inputLen, output, 0);
|
||||
return inputLen;
|
||||
}
|
||||
}
|
||||
265
lib/src/main/java/zeroecho/util/UniversalKeyStoreFile.java
Normal file
265
lib/src/main/java/zeroecho/util/UniversalKeyStoreFile.java
Normal file
@@ -0,0 +1,265 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* UniversalKeyStoreFile provides a simple, secure mechanism for storing and
|
||||
* retrieving cryptographic public and private keys using a plain text file. It
|
||||
* supports various key algorithms, including asymmetric and post-quantum, and
|
||||
* ensures minimal file permissions for security.
|
||||
* <p>
|
||||
* The keys are stored as key-value pairs where the key is a symbolic name with
|
||||
* suffix "_pub" or "_priv", and the value is formatted as:
|
||||
*
|
||||
* <pre>
|
||||
* Algorithm:Length:Base64EncodedKey
|
||||
* </pre>
|
||||
* <p>
|
||||
* File permissions are restricted to read and write for the owner (chmod 600).
|
||||
*/
|
||||
public class UniversalKeyStoreFile {
|
||||
/**
|
||||
* Logger instance for the {@code UniversalKeyStoreFile} class, used to log
|
||||
* informational, debug, and error messages related to keystore file operations.
|
||||
* <p>
|
||||
* Initialized with the class name to enable clear and contextual logging.
|
||||
* </p>
|
||||
*/
|
||||
private static final Logger LOG = Logger.getLogger(UniversalKeyStoreFile.class.getName());
|
||||
/**
|
||||
* File system path pointing to the keystore file managed by this instance.
|
||||
* <p>
|
||||
* This path is immutable after construction and represents the physical
|
||||
* location of the keystore file, which may be used for reading or writing
|
||||
* keystore data.
|
||||
* </p>
|
||||
*/
|
||||
private final Path filePath;
|
||||
|
||||
/**
|
||||
* Constructs a UniversalKeyStoreFile with the given path. If the file does not
|
||||
* exist, it is created with secure permissions (rw-------).
|
||||
*
|
||||
* @param filePath The path of the file to use for storing keys.
|
||||
*/
|
||||
public UniversalKeyStoreFile(final Path filePath) {
|
||||
this.filePath = filePath;
|
||||
|
||||
try {
|
||||
if (!Files.exists(filePath)) {
|
||||
Files.createFile(filePath);
|
||||
}
|
||||
setSecurePermissions(filePath);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Error creating or securing key store file", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a UniversalKeyStoreFile with the given file name. If the file does
|
||||
* not exist, it is created with secure permissions (rw-------).
|
||||
*
|
||||
* @param fileName The name of the file to use for storing keys.
|
||||
*/
|
||||
public UniversalKeyStoreFile(final String fileName) {
|
||||
this(Path.of(fileName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the file permissions to rw------- (owner read/write only).
|
||||
*
|
||||
* @param path The path of the file to secure.
|
||||
* @throws IOException If an I/O error occurs.
|
||||
*/
|
||||
private void setSecurePermissions(final Path path) throws IOException {
|
||||
try {
|
||||
// Set POSIX permissions: rw------- (owner read/write)
|
||||
final Set<PosixFilePermission> perms = Set.of(PosixFilePermission.OWNER_READ,
|
||||
PosixFilePermission.OWNER_WRITE);
|
||||
Files.setPosixFilePermissions(path, perms);
|
||||
} catch (UnsupportedOperationException e) {
|
||||
LOG.log(Level.WARNING, "POSIX file permissions not supported on this platform.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a public key to the underlying storage for the specified owner.
|
||||
* <p>
|
||||
* The key must support standard encoding, i.e., {@link PublicKey#getEncoded()}
|
||||
* must return a non-null value. If the key's format is {@code null}, indicating
|
||||
* it is not serializable (e.g., some post-quantum keys), an
|
||||
* {@link IllegalArgumentException} will be thrown.
|
||||
*
|
||||
* @param owner the identifier for the key owner; must not be {@code null}
|
||||
* @param pubKey the public key to store; must support encoding
|
||||
* @throws IllegalArgumentException if the key does not support encoding (i.e.,
|
||||
* {@code getFormat() == null})
|
||||
* @throws NullPointerException if either {@code owner} or {@code pubKey} is
|
||||
* {@code null}
|
||||
*/
|
||||
public void addPubKey(final String owner, final PublicKey pubKey) {
|
||||
if (pubKey.getEncoded() == null) {
|
||||
throw new IllegalArgumentException(pubKey.toString() + " is not serializable");
|
||||
}
|
||||
|
||||
writeEntry(owner + "_pub", KeySupport.serializeKey(pubKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a private key to the underlying storage for the specified owner.
|
||||
* <p>
|
||||
* The key must be serializable, i.e., it must support encoding via
|
||||
* {@link PrivateKey#getEncoded()}. If the key's format is {@code null},
|
||||
* indicating it does not support standard encoding (e.g., some post-quantum
|
||||
* keys), an {@link IllegalArgumentException} will be thrown.
|
||||
*
|
||||
* @param owner the identifier for the key owner; must not be {@code null}
|
||||
* @param privKey the private key to store; must support encoding
|
||||
* @throws IllegalArgumentException if the key does not support encoding (i.e.,
|
||||
* {@code getFormat() == null})
|
||||
* @throws NullPointerException if either {@code owner} or {@code privKey}
|
||||
* is {@code null}
|
||||
*/
|
||||
public void addPrivKey(final String owner, final PrivateKey privKey) {
|
||||
if (privKey.getEncoded() == null) {
|
||||
throw new IllegalArgumentException(privKey.toString() + " is not serializable");
|
||||
}
|
||||
|
||||
writeEntry(owner + "_priv", KeySupport.serializeKey(privKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an entry to the key store file in the format key=value.
|
||||
*
|
||||
* @param key The key for the entry (e.g., owner_pub).
|
||||
* @param value The value for the entry, including metadata and Base64 key.
|
||||
*/
|
||||
private void writeEntry(final String key, final String value) {
|
||||
final String entry = key + "=" + value + System.lineSeparator();
|
||||
try {
|
||||
Files.writeString(filePath, entry, StandardOpenOption.APPEND);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Error writing to key store file", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a public key for the specified owner from the key store.
|
||||
* <p>
|
||||
* This method retrieves a line from the key store matching the pattern
|
||||
* {@code owner_pub}, parses it to extract the key algorithm and Base64-encoded
|
||||
* key data, and reconstructs the {@link PublicKey} using the {@code BC} (Bouncy
|
||||
* Castle) provider.
|
||||
* </p>
|
||||
*
|
||||
* @param owner the symbolic name of the key owner (must not be {@code null})
|
||||
* @return the reconstructed {@link PublicKey} instance
|
||||
* @throws NoSuchProviderException if the Bouncy Castle provider is not
|
||||
* available
|
||||
* @throws NoSuchAlgorithmException if the algorithm is not supported
|
||||
* @throws InvalidKeySpecException if the key specification is invalid
|
||||
* @throws NoSuchElementException if the key for the given owner is not found
|
||||
* @throws IOException if an I/O error occurs while accessing the
|
||||
* key store
|
||||
*/
|
||||
public PublicKey loadPublicKey(final String owner)
|
||||
throws IOException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
|
||||
final String line = findLine(owner + "_pub");
|
||||
if (line == null) {
|
||||
throw new NoSuchElementException("Public key for " + owner + " not found.");
|
||||
}
|
||||
final String[] parts = line.split("=", 2);
|
||||
return KeySupport.deserializePublicKey(parts[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a private key for the specified owner from the key store.
|
||||
*
|
||||
* @param owner The symbolic name of the key owner.
|
||||
* @return The reconstructed private key.
|
||||
* @throws NoSuchElementException If the key is not found.
|
||||
* @throws IOException If the key cannot be reconstructed.
|
||||
* @throws NoSuchProviderException If the key cannot be reconstructed.
|
||||
* @throws NoSuchAlgorithmException If the key cannot be reconstructed.
|
||||
* @throws InvalidKeySpecException If the key cannot be reconstructed.
|
||||
*/
|
||||
public PrivateKey loadPrivateKey(final String owner)
|
||||
throws IOException, NoSuchAlgorithmException, NoSuchProviderException, InvalidKeySpecException {
|
||||
final String line = findLine(owner + "_priv");
|
||||
if (line == null) {
|
||||
throw new NoSuchElementException("Private key for " + owner + " not found.");
|
||||
}
|
||||
final String[] parts = line.split("=", 2);
|
||||
return KeySupport.deserializePrivateKey(parts[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a line in the key store file corresponding to the specified key.
|
||||
*
|
||||
* @param key The key to search for.
|
||||
* @return The line containing the key, or null if not found.
|
||||
* @throws IOException If an I/O error occurs.
|
||||
*/
|
||||
private String findLine(final String key) throws IOException {
|
||||
return Files.lines(filePath).filter(line -> line.startsWith(key + "=")).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all entries from the key store file.
|
||||
*/
|
||||
public void clearStore() {
|
||||
try {
|
||||
Files.writeString(filePath, "", StandardOpenOption.TRUNCATE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Error clearing key store file", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
719
lib/src/main/java/zeroecho/util/X509CertificationAuthority.java
Normal file
719
lib/src/main/java/zeroecho/util/X509CertificationAuthority.java
Normal file
@@ -0,0 +1,719 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (C) 2024, 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 zeroecho.util;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.LineNumberReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.nio.file.attribute.PosixFilePermissions;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
|
||||
import org.bouncycastle.asn1.x509.BasicConstraints;
|
||||
import org.bouncycastle.asn1.x509.CRLReason;
|
||||
import org.bouncycastle.asn1.x509.Extension;
|
||||
import org.bouncycastle.asn1.x509.ExtensionsGenerator;
|
||||
import org.bouncycastle.asn1.x509.KeyUsage;
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||
import org.bouncycastle.cert.X509CRLHolder;
|
||||
import org.bouncycastle.cert.X509CertificateHolder;
|
||||
import org.bouncycastle.cert.X509v2CRLBuilder;
|
||||
import org.bouncycastle.cert.X509v3CertificateBuilder;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509v2CRLBuilder;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
|
||||
import org.bouncycastle.crypto.util.PrivateKeyFactory;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.openssl.PEMKeyPair;
|
||||
import org.bouncycastle.openssl.PEMParser;
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
|
||||
import org.bouncycastle.operator.ContentSigner;
|
||||
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
|
||||
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
|
||||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
|
||||
import org.bouncycastle.util.encoders.Base64;
|
||||
|
||||
/**
|
||||
* A simple X.509 Certification Authority (CA) implementation for issuing,
|
||||
* managing, and revoking certificates.
|
||||
* <p>
|
||||
* This class manages a CA for an organization, storing keys and certificates in
|
||||
* files, handling a Certificate Revocation List (CRL), and issuing new
|
||||
* certificates based on CSRs or cross-certificates. It also supports
|
||||
* certificate revocation and ensures minimal file access permissions.
|
||||
* <p>
|
||||
* Key features:
|
||||
* <ul>
|
||||
* <li>Initializes CA with a self-signed root certificate</li>
|
||||
* <li>Issues certificates based on PKCS#10 requests or existing
|
||||
* certificates</li>
|
||||
* <li>Manages a CRL and supports revoking certificates</li>
|
||||
* <li>Persists data to disk with strong access permissions</li>
|
||||
* <li>Uses BouncyCastle for cryptography</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public class X509CertificationAuthority {
|
||||
/**
|
||||
* Logger for logging internal operations and warnings.
|
||||
*/
|
||||
private static final Logger LOG = Logger.getLogger(X509CertificationAuthority.class.getName());
|
||||
/**
|
||||
* Default key length for the CA's key pair.
|
||||
*/
|
||||
private static final int CA_KEYPAIR_KEY_LENGTH = 2048;
|
||||
/**
|
||||
* Algorithm used for the CA key pair (e.g., "RSA").
|
||||
*/
|
||||
private static final String CA_KEYPAIR_ALG = "RSA";
|
||||
/**
|
||||
* Signature algorithm used for CA operations (e.g., "SHA256withRSA").
|
||||
*/
|
||||
protected static final String CA_SIGNATURE_ALG = "SHA256withRSA"; // SHA3-512WITHRSA, ED25519
|
||||
/**
|
||||
* CA certificate validity period in years.
|
||||
*/
|
||||
private static final int CA_VALIDITY_YEARS = 10;
|
||||
/**
|
||||
* Issued end-entity certificate validity period in years.
|
||||
*/
|
||||
private static final int VALIDITY_YEARS = 1;
|
||||
|
||||
/**
|
||||
* X500Name representing the CA's subject information.
|
||||
*/
|
||||
private final X500Name caSubject;
|
||||
/**
|
||||
* The organization name associated with the CA.
|
||||
*/
|
||||
final private String organizationName;
|
||||
/**
|
||||
* The directory path where the CA's files and data are stored.
|
||||
*/
|
||||
final private Path dirPath;
|
||||
/**
|
||||
* Path to the CA's database file.
|
||||
*/
|
||||
final private Path dbPath;
|
||||
/**
|
||||
* Path to the Certificate Revocation List (CRL) file.
|
||||
*/
|
||||
final private Path crlPath;
|
||||
/**
|
||||
* Path to the keystore file storing CA keys and certificates.
|
||||
*/
|
||||
final protected Path keyStorePath;
|
||||
/**
|
||||
* Current serial number used for issuing certificates.
|
||||
*/
|
||||
protected int serialNum;
|
||||
/**
|
||||
* The key pair (private and public keys) of the root CA.
|
||||
*/
|
||||
protected KeyPair rootPair;
|
||||
/**
|
||||
* The root CA's X509 certificate.
|
||||
*/
|
||||
protected X509Certificate rootCert;
|
||||
/**
|
||||
* Holder for the current Certificate Revocation List (CRL).
|
||||
*/
|
||||
private X509CRLHolder crlHolder;
|
||||
/**
|
||||
* The universal keystore managing keys and certificates for the CA.
|
||||
*/
|
||||
private UniversalKeyStoreFile keyStore;
|
||||
/**
|
||||
* An empty array constant for certificates, to avoid repeated allocations.
|
||||
*/
|
||||
private static final Certificate[] EMPTY_CERTIFICATES_ARRAY = new Certificate[0];
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the CA for a given directory and organization.
|
||||
*
|
||||
* @param directory the base directory to store CA data
|
||||
* @param organizationName the name of the organization (alphanumeric only)
|
||||
*/
|
||||
public X509CertificationAuthority(final String directory, final String organizationName) {
|
||||
if (!organizationName.matches("^[a-zA-Z0-9]+$")) {
|
||||
throw new IllegalArgumentException("organizationName must include alphanum(s) only");
|
||||
}
|
||||
dirPath = Paths.get(directory);
|
||||
dbPath = dirPath.resolve(organizationName + ".db");
|
||||
crlPath = dirPath.resolve(organizationName + ".crl");
|
||||
keyStorePath = dirPath.resolve(organizationName + ".keystore");
|
||||
serialNum = 0;
|
||||
|
||||
this.organizationName = organizationName;
|
||||
this.caSubject = new X500Name("CN=zeroecho,OU=HQ,O=" + organizationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the Certificate Authority (CA) by creating necessary directories,
|
||||
* loading or storing keys and Certificate Revocation Lists (CRLs). If the CA is
|
||||
* not yet initialized, this method creates a new one with a self-signed
|
||||
* certificate.
|
||||
*
|
||||
* @return {@code true} if the CA was already initialized; {@code false} if it
|
||||
* was newly created
|
||||
* @throws IOException if an I/O error occurs during directory
|
||||
* creation or CRL handling
|
||||
* @throws NumberFormatException if number parsing fails during
|
||||
* initialization
|
||||
* @throws GeneralSecurityException if a security-related exception occurs
|
||||
* during initialization
|
||||
*/
|
||||
public boolean initialize() throws IOException, GeneralSecurityException {
|
||||
Files.createDirectories(dirPath,
|
||||
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwx------")));
|
||||
|
||||
keyStore = new UniversalKeyStoreFile(keyStorePath);
|
||||
|
||||
final boolean alreadyInitialized;
|
||||
|
||||
try {
|
||||
alreadyInitialized = initializeCA();
|
||||
|
||||
if (Files.notExists(crlPath)) {
|
||||
createCrl();
|
||||
}
|
||||
} catch (OperatorCreationException e) {
|
||||
LOG.logp(Level.WARNING, "X509CertificationAuthority", "initialize", "OperatorCreationException", e);
|
||||
throw new GeneralSecurityException("Failed to create cryptographic operator", e);
|
||||
}
|
||||
|
||||
if (Files.isReadable(crlPath)) {
|
||||
loadCrl();
|
||||
} else {
|
||||
throw new IOException("CRL cannot be read: " + crlPath.toAbsolutePath());
|
||||
}
|
||||
|
||||
return alreadyInitialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Issues a certificate for a given username based on a Base64-encoded PKCS#10
|
||||
* CSR.
|
||||
*
|
||||
* @param username the user for whom the certificate is issued
|
||||
* @param csrBase64String the CSR in Base64 encoding
|
||||
* @return the issued X.509 certificate, or null if failed
|
||||
*/
|
||||
public Certificate issueFromCSR(final String username, final String csrBase64String) {
|
||||
try {
|
||||
final PKCS10CertificationRequest csr = new PKCS10CertificationRequest(Base64.decode(csrBase64String));
|
||||
|
||||
return processCSR(username, csr);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "certificate issue", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Issues an X.509 certificate based on a PKCS#10 Certification Request (CSR)
|
||||
* loaded from a PEM file.
|
||||
* <p>
|
||||
* This method loads the CSR from the specified file, validates it, and
|
||||
* delegates to the internal {@code issue(String, PKCS10CertificationRequest)}
|
||||
* method to generate the certificate.
|
||||
* <p>
|
||||
* If the CSR file is invalid or an I/O error occurs, the method logs the error
|
||||
* and returns {@code null}.
|
||||
*
|
||||
* @param username the username or identifier for whom the certificate is being
|
||||
* issued
|
||||
* @param csrFile the file path to the PEM-encoded CSR file
|
||||
* @return the issued {@link Certificate}, or {@code null} if an error occurred
|
||||
* or the CSR is invalid
|
||||
*/
|
||||
public Certificate issueFromCSRFile(final String username, final String csrFile) {
|
||||
try {
|
||||
final PKCS10CertificationRequest csr = X509Support.loadCSR(csrFile);
|
||||
if (csr == null) {
|
||||
LOG.log(Level.SEVERE, "Failed to load CSR from file: {0}", csrFile);
|
||||
return null;
|
||||
}
|
||||
|
||||
return processCSR(username, csr);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "certificate issue", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A container for a newly issued certificate and its associated key pair.
|
||||
*
|
||||
* @param cert the issued {@link Certificate}
|
||||
* @param key the {@link KeyPair} associated with the certificate
|
||||
*/
|
||||
public record NewCertificate(Certificate cert, KeyPair key) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Issues a new certificate for the given user and subject.
|
||||
* <p>
|
||||
* Generates a new {@link KeyPair}, constructs the subject public key info, and
|
||||
* creates a certificate for the provided username and subject.
|
||||
*
|
||||
* @param username the identifier of the user for whom the certificate is issued
|
||||
* @param subject the X.500 distinguished name for the certificate subject
|
||||
* @return a {@code NewCertificate} record containing the issued certificate and
|
||||
* key pair
|
||||
* @throws NoSuchAlgorithmException if the algorithm used for key
|
||||
* generation is not available
|
||||
* @throws NoSuchProviderException if the cryptographic provider is
|
||||
* not available
|
||||
* @throws InvalidAlgorithmParameterException if the specified key size of
|
||||
* EC-algorithms is invalid
|
||||
*/
|
||||
public NewCertificate issue(final String username, final String subject)
|
||||
throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
|
||||
final KeyPair keyPair = KeyPairAlgorithm.RSA_2048.generateKeyPair();
|
||||
final SubjectPublicKeyInfo spki = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
|
||||
|
||||
return new NewCertificate(issue(username, new X500Name(subject), spki), keyPair);
|
||||
}
|
||||
|
||||
/**
|
||||
* Issues a new X.509 certificate for the specified user based on the provided
|
||||
* Certification Signing Request (CSR).
|
||||
*
|
||||
* <p>
|
||||
* This method extracts the subject and public key information from the CSR and
|
||||
* delegates to the underlying certificate issuance method.
|
||||
* </p>
|
||||
*
|
||||
* @param username the name of the user to whom the certificate will be issued
|
||||
* @param csr the PKCS#10 Certification Signing Request containing the
|
||||
* subject and public key
|
||||
* @return the issued {@link Certificate} instance
|
||||
*/
|
||||
private Certificate processCSR(final String username, final PKCS10CertificationRequest csr) {
|
||||
return issue(username, csr.getSubject(), csr.getSubjectPublicKeyInfo());
|
||||
}
|
||||
|
||||
/**
|
||||
* Issues a cross-certificate for an existing certificate.
|
||||
*
|
||||
* @param username the user name
|
||||
* @param certificate the certificate to cross-sign
|
||||
* @return the cross-signed certificate, or null if failed
|
||||
*/
|
||||
public Certificate issueCrossCertificate(final String username, final Certificate certificate) {
|
||||
try {
|
||||
final X509CertificateHolder cHolder = new X509CertificateHolder(certificate.getEncoded());
|
||||
return issue(username, cHolder.getSubject(), cHolder.getSubjectPublicKeyInfo());
|
||||
} catch (CertificateEncodingException | IOException e) {
|
||||
LOG.log(Level.WARNING, "x-certificate issue", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all certificates issued for a user, excluding revoked ones.
|
||||
*
|
||||
* @param username the username
|
||||
* @return an array of X509Certificates, or empty array if none
|
||||
*/
|
||||
public Certificate[] getAll(final String username) { // NOPMD
|
||||
if (Files.isReadable(dbPath)) {
|
||||
|
||||
final List<Certificate> result = new ArrayList<>();
|
||||
|
||||
try (LineNumberReader usrLine = new LineNumberReader(
|
||||
new InputStreamReader(Files.newInputStream(dbPath, StandardOpenOption.READ)))) {
|
||||
for (String line = usrLine.readLine(); line != null; line = usrLine.readLine()) {
|
||||
final String[] attr = line.split("\\p{Blank}+", 3); // cert_id username
|
||||
// certificate_with_pubkey/base64
|
||||
final int id = Integer.parseInt(attr[0]);
|
||||
if (id > serialNum) {
|
||||
serialNum = id;
|
||||
}
|
||||
|
||||
if (username.compareTo(attr[1]) == 0) {
|
||||
final X509CertificateHolder ch = new X509CertificateHolder(Base64.decode(attr[2])); // NOPMD by
|
||||
// Leo
|
||||
// Galambos
|
||||
// on
|
||||
// 6/1/25,
|
||||
// 12:50 PM
|
||||
if (crlHolder.getRevokedCertificate(ch.getSerialNumber()) == null) {
|
||||
final X509Certificate cert = new JcaX509CertificateConverter() // NOPMD by Leo Galambos on
|
||||
// 6/25/25, 6:55 PM
|
||||
.setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(ch);
|
||||
result.add(cert);
|
||||
} else {
|
||||
if (LOG.isLoggable(Level.INFO)) {
|
||||
LOG.log(Level.INFO, "Certificate #{0} ({1}) is revoked",
|
||||
new Object[] { ch.getSerialNumber().toString(), ch.getSubject() }); // NOPMD by
|
||||
// Leo
|
||||
// Galambos
|
||||
// on
|
||||
// 6/1/25,
|
||||
// 12:50 PM
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!result.isEmpty()) {
|
||||
final Certificate[] resultArray = new Certificate[result.size()];
|
||||
return result.toArray(resultArray);
|
||||
}
|
||||
} catch (IOException | CertificateException x) {
|
||||
LOG.log(Level.WARNING, "getAll", x);
|
||||
}
|
||||
}
|
||||
|
||||
return EMPTY_CERTIFICATES_ARRAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revokes a given certificate for a user and updates the CRL.
|
||||
*
|
||||
* @param username the username
|
||||
* @param certificate the certificate to revoke
|
||||
* @return true if successfully revoked, false otherwise
|
||||
*/
|
||||
public boolean revoke(final String username, final Certificate certificate) {
|
||||
final Certificate[] candidates = getAll(username);
|
||||
if (candidates.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (crlHolder.getNextUpdate().before(new Date())) {
|
||||
LOG.info("Reloading CRL - we might have an old copy");
|
||||
loadCrl();
|
||||
}
|
||||
|
||||
for (final Certificate c : candidates) {
|
||||
if (!(c instanceof X509Certificate x)) {
|
||||
throw new IllegalStateException(
|
||||
"X509 CA includes a certificate which is not X509: " + c.toString());
|
||||
}
|
||||
// if it is the same certificate, or it has the same public key
|
||||
if (c.equals(certificate) || c.getPublicKey().equals(certificate.getPublicKey())) {
|
||||
// found!
|
||||
final X509v2CRLBuilder crlGen = new X509v2CRLBuilder(crlHolder); // NOPMD by Leo Galambos on 6/1/25,
|
||||
// 12:55 PM
|
||||
crlGen.setNextUpdate(new Date(System.currentTimeMillis() + 1000 * 24 * 3600)); // NOPMD by Leo
|
||||
// Galambos on
|
||||
// 6/1/25, 12:55 PM
|
||||
|
||||
final ExtensionsGenerator extGen = new ExtensionsGenerator(); // NOPMD by Leo Galambos on 6/1/25,
|
||||
// 12:56 PM
|
||||
final CRLReason crlReason = CRLReason.lookup(CRLReason.privilegeWithdrawn);
|
||||
extGen.addExtension(Extension.reasonCode, false, crlReason);
|
||||
|
||||
crlGen.addCRLEntry(x.getSerialNumber(), new Date(), extGen.generate()); // NOPMD by Leo Galambos on
|
||||
// 6/1/25, 12:56 PM
|
||||
|
||||
final ContentSigner signer = new JcaContentSignerBuilder(CA_SIGNATURE_ALG) // NOPMD by Leo Galambos
|
||||
// on 6/25/25, 6:55 PM
|
||||
.setProvider(BouncyCastleProvider.PROVIDER_NAME).build(rootPair.getPrivate());
|
||||
crlHolder = crlGen.build(signer);
|
||||
writeCrl();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (IOException | OperatorCreationException x) {
|
||||
LOG.log(Level.WARNING, "revoke", x);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes or loads the CA, generating key/cert if needed.
|
||||
*
|
||||
* @return true if CA was already initialized, false otherwise
|
||||
* @throws GeneralSecurityException, OperatorCreationException, IOException
|
||||
*/
|
||||
private boolean initializeCA() throws GeneralSecurityException, OperatorCreationException, IOException {
|
||||
final Path certPath = dirPath.resolve(organizationName + "-ca.crt");
|
||||
final Path keyPath = dirPath.resolve(organizationName + "-ca.key");
|
||||
|
||||
final boolean certExists = Files.isReadable(certPath);
|
||||
if (certExists ^ Files.isReadable(keyPath)) {
|
||||
// one of the files is missing
|
||||
throw new IllegalStateException("one of the cert or key files are not available");
|
||||
}
|
||||
|
||||
if (certExists) {
|
||||
// load our CA: cert and key
|
||||
try (PEMParser pemParser = new PEMParser(Files.newBufferedReader(certPath))) {
|
||||
final X509CertificateHolder rootCertHolder = (X509CertificateHolder) pemParser.readObject();
|
||||
rootCert = new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||
.getCertificate(rootCertHolder);
|
||||
}
|
||||
|
||||
try (PEMParser pemParser = new PEMParser(Files.newBufferedReader(keyPath))) {
|
||||
final PEMKeyPair kp = (PEMKeyPair) pemParser.readObject();
|
||||
rootPair = new KeyPair(rootCert.getPublicKey(), new JcaPEMKeyConverter()
|
||||
.setProvider(BouncyCastleProvider.PROVIDER_NAME).getPrivateKey(kp.getPrivateKeyInfo()));
|
||||
}
|
||||
|
||||
serialNum = 1;
|
||||
|
||||
try (LineNumberReader usrLine = new LineNumberReader(
|
||||
new InputStreamReader(Files.newInputStream(dbPath, StandardOpenOption.READ)))) {
|
||||
for (String line = usrLine.readLine(); line != null; line = usrLine.readLine()) {
|
||||
final String[] attr = line.split("\\p{Blank}+", 3); // cert_id username
|
||||
// certificate_with_pubkey/base64
|
||||
final int id = Integer.parseInt(attr[0]);
|
||||
if (id > serialNum) {
|
||||
serialNum = id;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (NoSuchFileException e) {
|
||||
// it might not yet exist, it is OK
|
||||
LOG.log(Level.INFO, "{0} does not exist, will create a new one", dbPath);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Certificates cannot be fully read", e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// we are starting a new CA
|
||||
|
||||
final Calendar cal = Calendar.getInstance();
|
||||
final Date startDate = cal.getTime();
|
||||
cal.add(Calendar.YEAR, CA_VALIDITY_YEARS);
|
||||
final Date endDate = cal.getTime();
|
||||
|
||||
final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(CA_KEYPAIR_ALG);
|
||||
keyPairGenerator.initialize(CA_KEYPAIR_KEY_LENGTH, new SecureRandom());
|
||||
rootPair = keyPairGenerator.generateKeyPair();
|
||||
|
||||
final X509v3CertificateBuilder rootCertBuilder = new JcaX509v3CertificateBuilder(/* issuer */ caSubject,
|
||||
BigInteger.valueOf(1), startDate, endDate, caSubject, rootPair.getPublic());
|
||||
|
||||
// BasicContraint - mark cert as CA cert
|
||||
final JcaX509ExtensionUtils rootCertExtUtils = new JcaX509ExtensionUtils();
|
||||
rootCertBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(true));
|
||||
rootCertBuilder.addExtension(Extension.authorityKeyIdentifier, false,
|
||||
rootCertExtUtils.createAuthorityKeyIdentifier(rootPair.getPublic()));
|
||||
rootCertBuilder.addExtension(Extension.subjectKeyIdentifier, false,
|
||||
rootCertExtUtils.createSubjectKeyIdentifier(rootPair.getPublic()));
|
||||
rootCertBuilder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign));
|
||||
|
||||
final ContentSigner rootCertContentSigner = new JcaContentSignerBuilder(CA_SIGNATURE_ALG)
|
||||
.setProvider(BouncyCastleProvider.PROVIDER_NAME).build(rootPair.getPrivate());
|
||||
final X509CertificateHolder rootCertHolder = rootCertBuilder.build(rootCertContentSigner);
|
||||
rootCert = new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME)
|
||||
.getCertificate(rootCertHolder);
|
||||
|
||||
try (PrintWriter p = new PrintWriter(
|
||||
new BufferedOutputStream(Files.newOutputStream(certPath, StandardOpenOption.CREATE_NEW)))) {
|
||||
try (JcaPEMWriter jpw = new JcaPEMWriter(p)) {
|
||||
jpw.writeObject(rootCert);
|
||||
}
|
||||
}
|
||||
|
||||
try (PrintWriter p = new PrintWriter(
|
||||
new BufferedOutputStream(Files.newOutputStream(keyPath, StandardOpenOption.CREATE_NEW)))) {
|
||||
try (JcaPEMWriter jpw = new JcaPEMWriter(p)) {
|
||||
jpw.writeObject(rootPair.getPrivate());
|
||||
}
|
||||
}
|
||||
|
||||
serialNum = 1;
|
||||
|
||||
keyStore.addPubKey("*CA*", rootPair.getPublic());
|
||||
keyStore.addPrivKey("*CA*", rootPair.getPrivate());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Certificate Revocation List (CRL) and saves it.
|
||||
*
|
||||
* @throws NoSuchAlgorithmException, CertificateEncodingException,
|
||||
* OperatorCreationException, IOException
|
||||
*/
|
||||
private void createCrl()
|
||||
throws NoSuchAlgorithmException, CertificateEncodingException, OperatorCreationException, IOException {
|
||||
final X509v2CRLBuilder crlBase = new JcaX509v2CRLBuilder(rootCert.getSubjectX500Principal(), new Date());
|
||||
crlBase.setNextUpdate(new Date(System.currentTimeMillis() + 1000 * 24 * 3600));
|
||||
|
||||
// add extensions to CRL
|
||||
final JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
|
||||
crlBase.addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(rootCert));
|
||||
|
||||
final ContentSigner signer = new JcaContentSignerBuilder(CA_SIGNATURE_ALG)
|
||||
.setProvider(BouncyCastleProvider.PROVIDER_NAME).build(rootPair.getPrivate());
|
||||
|
||||
crlHolder = crlBase.build(signer);
|
||||
|
||||
writeCrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the CRL from file if newer than current in-memory CRL.
|
||||
*
|
||||
* @throws IOException if CRL cannot be loaded
|
||||
*/
|
||||
private void loadCrl() throws IOException {
|
||||
try (PEMParser pemParser = new PEMParser(Files.newBufferedReader(crlPath))) {
|
||||
final X509CRLHolder newerCrl = (X509CRLHolder) pemParser.readObject();
|
||||
if (crlHolder == null || crlHolder.getThisUpdate().before(newerCrl.getThisUpdate())) {
|
||||
crlHolder = newerCrl;
|
||||
} else {
|
||||
LOG.log(Level.INFO, "CRL was not updated, file {0}", crlPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the CRL to file.
|
||||
*
|
||||
* @throws IOException if CRL cannot be saved
|
||||
*/
|
||||
private void writeCrl() throws IOException {
|
||||
try (PrintWriter p = new PrintWriter(new BufferedOutputStream(
|
||||
Files.newOutputStream(crlPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE)))) {
|
||||
try (JcaPEMWriter jpw = new JcaPEMWriter(p)) {
|
||||
jpw.writeObject(crlHolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Issues a certificate using the subject name and public key.
|
||||
*
|
||||
* @param username the username
|
||||
* @param subject X500 subject name
|
||||
* @param publicKeyInfo public key info from CSR or certificate
|
||||
* @return the issued certificate, or null if failed
|
||||
*/
|
||||
public Certificate issue(final String username, final String subject, final SubjectPublicKeyInfo publicKeyInfo) {
|
||||
return issue(username, new X500Name(subject), publicKeyInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Issues a certificate using the subject name and public key.
|
||||
*
|
||||
* @param username the username
|
||||
* @param subject X500 subject name
|
||||
* @param publicKeyInfo public key info from CSR or certificate
|
||||
* @return the issued certificate, or null if failed
|
||||
*/
|
||||
public Certificate issue(final String username, final X500Name subject, final SubjectPublicKeyInfo publicKeyInfo) {
|
||||
try {
|
||||
final int serialNumCert = serialNum + 1;
|
||||
|
||||
final Calendar cal = Calendar.getInstance();
|
||||
final Date startDate = cal.getTime();
|
||||
cal.add(Calendar.YEAR, VALIDITY_YEARS);
|
||||
final Date endDate = cal.getTime();
|
||||
|
||||
final X509v3CertificateBuilder myCertificateGenerator = new X509v3CertificateBuilder(caSubject,
|
||||
BigInteger.valueOf(serialNumCert), startDate, endDate, subject, publicKeyInfo);
|
||||
|
||||
final JcaX509ExtensionUtils certExtUtils = new JcaX509ExtensionUtils();
|
||||
myCertificateGenerator.addExtension(Extension.authorityKeyIdentifier, false,
|
||||
certExtUtils.createAuthorityKeyIdentifier(rootPair.getPublic()));
|
||||
myCertificateGenerator.addExtension(Extension.subjectKeyIdentifier, false,
|
||||
certExtUtils.createSubjectKeyIdentifier(publicKeyInfo));
|
||||
myCertificateGenerator.addExtension(Extension.keyUsage, true,
|
||||
new KeyUsage(KeyUsage.digitalSignature | KeyUsage.dataEncipherment));
|
||||
|
||||
final AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find(CA_SIGNATURE_ALG);
|
||||
final AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
|
||||
final ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId)
|
||||
.build(PrivateKeyFactory.createKey(rootPair.getPrivate().getEncoded()));
|
||||
|
||||
final X509CertificateHolder holder = myCertificateGenerator.build(sigGen);
|
||||
final org.bouncycastle.asn1.x509.Certificate eeX509CertStruct = holder.toASN1Structure();
|
||||
|
||||
final java.security.cert.CertificateFactory factory = java.security.cert.CertificateFactory
|
||||
.getInstance("X.509", BouncyCastleProvider.PROVIDER_NAME);
|
||||
final X509Certificate cert = (X509Certificate) factory
|
||||
.generateCertificate(new ByteArrayInputStream(eeX509CertStruct.getEncoded()));
|
||||
|
||||
try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(dbPath, StandardCharsets.UTF_8,
|
||||
StandardOpenOption.APPEND, StandardOpenOption.CREATE))) {
|
||||
writer.printf("%d %s %s%n", serialNumCert, username, Base64.toBase64String(cert.getEncoded()));
|
||||
}
|
||||
serialNum = serialNumCert;
|
||||
|
||||
keyStore.addPubKey(username, cert.getPublicKey());
|
||||
|
||||
return cert;
|
||||
} catch (IOException | CertificateException | OperatorCreationException | NoSuchProviderException
|
||||
| NoSuchAlgorithmException e) {
|
||||
LOG.log(Level.WARNING, "certificate issue", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
175
lib/src/main/java/zeroecho/util/X509Support.java
Normal file
175
lib/src/main/java/zeroecho/util/X509Support.java
Normal file
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* 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 zeroecho.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.openssl.PEMParser;
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
|
||||
|
||||
/**
|
||||
* Utility class providing support for working with X.509 certificates, private
|
||||
* keys, and certification requests using PEM-encoded files and the Bouncy
|
||||
* Castle library.
|
||||
* <p>
|
||||
* This class includes static helper methods to:
|
||||
* <ul>
|
||||
* <li>Load an X.509 certificate from a PEM file</li>
|
||||
* <li>Load a PKCS#10 certification request (CSR) from a PEM file</li>
|
||||
* <li>Print a {@link java.security.PrivateKey} in PEM format</li>
|
||||
* <li>Print a {@link java.security.cert.Certificate}, including its PEM-encoded
|
||||
* form</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* All methods are static and the class cannot be instantiated.
|
||||
*
|
||||
* <p>
|
||||
* <b>Note:</b> This class relies on the Bouncy Castle library and assumes the
|
||||
* Bouncy Castle provider is correctly configured.
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public final class X509Support {
|
||||
private static final Logger LOG = Logger.getLogger(X509Support.class.getName());
|
||||
|
||||
/**
|
||||
* Private constructor to prevent instantiation of this utility class.
|
||||
*/
|
||||
private X509Support() {
|
||||
// this is a utility class
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an X509Certificate from a PEM file.
|
||||
*
|
||||
* @param path the file path to the certificate PEM file
|
||||
* @return the loaded {@link X509Certificate} or null if invalid
|
||||
* @throws IOException if an I/O error occurs reading the file
|
||||
*/
|
||||
public static Certificate loadCertificate(final String path) throws IOException {
|
||||
try (PEMParser pemParser = new PEMParser(Files.newBufferedReader(Paths.get(path), StandardCharsets.UTF_8))) {
|
||||
final Object obj = pemParser.readObject();
|
||||
if (obj instanceof org.bouncycastle.cert.X509CertificateHolder) {
|
||||
final org.bouncycastle.cert.X509CertificateHolder holder = (org.bouncycastle.cert.X509CertificateHolder) obj;
|
||||
return new org.bouncycastle.cert.jcajce.JcaX509CertificateConverter()
|
||||
.setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(holder);
|
||||
} else {
|
||||
System.err.println("File does not contain a valid X509 certificate.");
|
||||
return null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.SEVERE, "Error reading certificate file: {0}", path);
|
||||
throw e;
|
||||
} catch (Exception e) { // NOPMD by Leo Galambos on 6/1/25, 1:14 PM
|
||||
LOG.log(Level.SEVERE, "Error reading certificate file: {0}", path);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to print a {@link PrivateKey} in PEM format to standard
|
||||
* output.
|
||||
* <p>
|
||||
* This method writes the private key using a {@link JcaPEMWriter} and prints it
|
||||
* to the console in PEM (Base64-encoded) format. If an error occurs during the
|
||||
* writing process, it logs a warning.
|
||||
*
|
||||
* @param privKey the {@link PrivateKey} to print
|
||||
*/
|
||||
public static void printPrivateKey(final PrivateKey privKey) {
|
||||
try (StringWriter sw = new StringWriter(); JcaPEMWriter pemWriter = new JcaPEMWriter(sw)) {
|
||||
pemWriter.writeObject(privKey);
|
||||
pemWriter.flush();
|
||||
System.out.println("Private Key (PEM):\n" + sw.toString());
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to print private key", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the certificate details to standard output, including its PEM encoded
|
||||
* representation.
|
||||
*
|
||||
* @param cert the {@link Certificate} to print
|
||||
*/
|
||||
public static void printCertificate(final Certificate cert) {
|
||||
try {
|
||||
System.out.println("Certificate:");
|
||||
System.out.println(cert.toString());
|
||||
|
||||
// Print PEM encoded certificate
|
||||
try (StringWriter sw = new StringWriter(); JcaPEMWriter pemWriter = new JcaPEMWriter(sw)) {
|
||||
pemWriter.writeObject(cert);
|
||||
pemWriter.flush();
|
||||
System.out.println(sw.toString());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to print certificate", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a PKCS#10 Certification Request (CSR) from a PEM file.
|
||||
*
|
||||
* @param path the file path to the CSR PEM file
|
||||
* @return the loaded {@link PKCS10CertificationRequest} or null if invalid
|
||||
* @throws IOException if an I/O error occurs reading the file
|
||||
*/
|
||||
public static PKCS10CertificationRequest loadCSR(final String path) throws IOException {
|
||||
try (PEMParser pemParser = new PEMParser(Files.newBufferedReader(Paths.get(path), StandardCharsets.UTF_8))) {
|
||||
final Object obj = pemParser.readObject();
|
||||
if (obj instanceof PKCS10CertificationRequest) {
|
||||
return (PKCS10CertificationRequest) obj;
|
||||
} else {
|
||||
System.err.println("File does not contain a valid PKCS#10 CSR.");
|
||||
return null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.SEVERE, "Error reading CSR file: {0}", path);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
227
lib/src/main/java/zeroecho/util/aes/AesCipherType.java
Normal file
227
lib/src/main/java/zeroecho/util/aes/AesCipherType.java
Normal file
@@ -0,0 +1,227 @@
|
||||
/**
|
||||
* 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 zeroecho.util.aes;
|
||||
|
||||
import org.bouncycastle.crypto.BufferedBlockCipher;
|
||||
import org.bouncycastle.crypto.StreamCipher;
|
||||
import org.bouncycastle.crypto.engines.AESEngine;
|
||||
import org.bouncycastle.crypto.modes.AEADBlockCipher;
|
||||
import org.bouncycastle.crypto.modes.CBCBlockCipher;
|
||||
import org.bouncycastle.crypto.modes.GCMBlockCipher;
|
||||
import org.bouncycastle.crypto.modes.SICBlockCipher;
|
||||
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
||||
|
||||
import zeroecho.util.RandomSupport;
|
||||
|
||||
/**
|
||||
* Enum representing various AES cipher modes along with their padding schemes,
|
||||
* specifically using BouncyCastle cryptographic implementations.
|
||||
* <p>
|
||||
* Each enum constant defines a cipher configuration via a string identifier
|
||||
* (e.g., {@code "AES/CBC/PKCS7Padding"}) and provides a factory method to
|
||||
* create the corresponding cipher instance.
|
||||
* </p>
|
||||
* <p>
|
||||
* The {@link #createCipher()} method utilizes a modern switch expression to
|
||||
* return a new instance of the appropriate cipher implementation:
|
||||
* <ul>
|
||||
* <li>{@link org.bouncycastle.crypto.BufferedBlockCipher} for CBC mode</li>
|
||||
* <li>{@link org.bouncycastle.crypto.modes.AEADBlockCipher} for GCM mode</li>
|
||||
* <li>{@link org.bouncycastle.crypto.StreamCipher} for CTR mode</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* The enum also provides utility methods to retrieve the required IV length and
|
||||
* generate random IVs for each mode.
|
||||
* </p>
|
||||
*
|
||||
* @see org.bouncycastle.crypto.BufferedBlockCipher
|
||||
* @see org.bouncycastle.crypto.modes.AEADBlockCipher
|
||||
* @see org.bouncycastle.crypto.StreamCipher
|
||||
*/
|
||||
public enum AesCipherType {
|
||||
/**
|
||||
* AES encryption in CBC (Cipher Block Chaining) mode with PKCS7 padding.
|
||||
* Suitable for general-purpose encryption where authenticated encryption is not
|
||||
* required.
|
||||
*/
|
||||
CBC("AES/CBC/PKCS7Padding"),
|
||||
|
||||
/**
|
||||
* AES encryption in GCM (Galois/Counter Mode) with no padding. Provides
|
||||
* authenticated encryption with associated data (AEAD).
|
||||
*/
|
||||
GCM("AES/GCM/NoPadding"),
|
||||
|
||||
/**
|
||||
* AES encryption in CTR (Counter) mode with no padding. Acts as a stream
|
||||
* cipher; suitable for environments requiring parallelizable
|
||||
* encryption/decryption.
|
||||
*/
|
||||
CTR("AES/CTR/NoPadding");
|
||||
|
||||
private final String id;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code AesCipherType} with the given cipher identifier.
|
||||
*
|
||||
* @param id the full cipher transformation string, e.g.,
|
||||
* {@code "AES/CTR/NoPadding"}
|
||||
*/
|
||||
AesCipherType(final String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string identifier of the AES cipher mode and padding scheme.
|
||||
*
|
||||
* @return the cipher identifier string, e.g. "AES/CBC/PKCS7Padding"
|
||||
*/
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string representation of the AES cipher type.
|
||||
*
|
||||
* @return the cipher identifier string
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the given string to an {@code AesCipherType} enum constant.
|
||||
* <p>
|
||||
* Performs a case-insensitive match against the transformation string (e.g.,
|
||||
* {@code "AES/GCM/NoPadding"}).
|
||||
* </p>
|
||||
*
|
||||
* @param name the transformation string to resolve
|
||||
* @return the corresponding {@code AesCipherType}
|
||||
* @throws IllegalArgumentException if no matching enum constant is found
|
||||
*/
|
||||
public static AesCipherType fromString(final String name) {
|
||||
if (name == null) {
|
||||
throw new IllegalArgumentException("Cipher type name must not be null");
|
||||
}
|
||||
for (final AesCipherType type : values()) {
|
||||
if (type.id.equalsIgnoreCase(name)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid AES cipher type: " + name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of the BouncyCastle cipher implementation
|
||||
* corresponding to this {@code AesCipherType}.
|
||||
*
|
||||
* The returned object is one of the following types depending on the cipher:
|
||||
* <ul>
|
||||
* <li>{@link org.bouncycastle.crypto.BufferedBlockCipher} for CBC mode</li>
|
||||
* <li>{@link org.bouncycastle.crypto.modes.AEADBlockCipher} for GCM mode</li>
|
||||
* <li>{@link org.bouncycastle.crypto.StreamCipher} for CTR mode</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* The caller is responsible for initializing the cipher (e.g., with key and IV)
|
||||
* and setting encryption/decryption mode.
|
||||
* </p>
|
||||
*
|
||||
* @return a new instance of the cipher implementation for this AES mode
|
||||
*/
|
||||
public Object createCipher() {
|
||||
return switch (this) {
|
||||
case CBC -> createCbcCipher();
|
||||
case GCM -> createGcmCipher();
|
||||
case CTR -> createCtrCipher();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random initialization vector (IV) suitable for this AES cipher
|
||||
* mode.
|
||||
*
|
||||
* @return a randomly generated IV with the appropriate byte length
|
||||
*/
|
||||
public byte[] generateRandomIV() {
|
||||
return RandomSupport.generateRandom(getIVLengthBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the required IV length in bytes for this AES cipher mode.
|
||||
* <ul>
|
||||
* <li>CBC and CTR modes require a 16-byte IV</li>
|
||||
* <li>GCM mode uses a 12-byte IV by convention for optimal security</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return the IV length in bytes
|
||||
*/
|
||||
public int getIVLengthBytes() {
|
||||
return switch (this) {
|
||||
case CBC -> 16;
|
||||
case CTR -> 16;
|
||||
case GCM -> 12;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new AES cipher configured for CBC mode with PKCS7 padding.
|
||||
*
|
||||
* @return a {@link BufferedBlockCipher} configured for AES/CBC/PKCS7Padding
|
||||
*/
|
||||
private static BufferedBlockCipher createCbcCipher() {
|
||||
return new PaddedBufferedBlockCipher(CBCBlockCipher.newInstance(AESEngine.newInstance()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new AES cipher configured for GCM mode with no padding (AEAD).
|
||||
*
|
||||
* @return an {@link AEADBlockCipher} configured for AES/GCM/NoPadding
|
||||
*/
|
||||
private static AEADBlockCipher createGcmCipher() {
|
||||
return GCMBlockCipher.newInstance(AESEngine.newInstance());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new AES cipher configured for CTR mode (stream cipher) with no
|
||||
* padding.
|
||||
*
|
||||
* @return a {@link StreamCipher} configured for AES/CTR/NoPadding
|
||||
*/
|
||||
private static StreamCipher createCtrCipher() {
|
||||
return SICBlockCipher.newInstance(AESEngine.newInstance());
|
||||
}
|
||||
}
|
||||
114
lib/src/main/java/zeroecho/util/aes/AesInputStreamAdapter.java
Normal file
114
lib/src/main/java/zeroecho/util/aes/AesInputStreamAdapter.java
Normal file
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* 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 zeroecho.util.aes;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
|
||||
import zeroecho.util.OutputToInputStreamAdapter;
|
||||
|
||||
/**
|
||||
* An {@link InputStream} adapter that encrypts data read from a plaintext input
|
||||
* stream using AES encryption with a specified Initialization Vector (IV).
|
||||
* <p>
|
||||
* The encrypted data is produced as the output of this stream.
|
||||
* </p>
|
||||
* <p>
|
||||
* <strong>Important:</strong> AES encryption must be initialized by calling
|
||||
* {@link #initialize(KeyParameter, byte[], AesCipherType, byte[])} <em>after
|
||||
* construction and before any read operations</em>.
|
||||
* </p>
|
||||
*
|
||||
* @deprecated This class is deprecated and may be removed in future releases.
|
||||
* Consider using updated AES encryption stream utilities.
|
||||
*/
|
||||
@Deprecated
|
||||
public class AesInputStreamAdapter extends OutputToInputStreamAdapter {
|
||||
|
||||
/**
|
||||
* Constructs an AES input stream adapter wrapping the specified input stream,
|
||||
* using the default buffer size.
|
||||
*
|
||||
* @param previousInput the input stream containing plaintext data to be
|
||||
* encrypted
|
||||
*/
|
||||
public AesInputStreamAdapter(final InputStream previousInput) {
|
||||
super(previousInput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an AES input stream adapter wrapping the specified input stream,
|
||||
* using the specified buffer size for internal buffering.
|
||||
*
|
||||
* @param previousInput the input stream containing plaintext data to be
|
||||
* encrypted
|
||||
* @param bufSize the size of the buffer used for transformation output
|
||||
*/
|
||||
public AesInputStreamAdapter(final InputStream previousInput, final int bufSize) {
|
||||
super(previousInput, bufSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the AES encryption transformation using the provided secret key,
|
||||
* initialization vector (IV), cipher type, and optional AAD (Additional
|
||||
* Authenticated Data).
|
||||
* <p>
|
||||
* This method <strong>must</strong> be called before any read operations on
|
||||
* this stream; otherwise, the stream will not function correctly.
|
||||
* </p>
|
||||
*
|
||||
* @param key the AES encryption key parameter
|
||||
* @param iv the initialization vector (IV) for AES encryption
|
||||
* @param cipherType the AES cipher mode/type to use (e.g., CBC, GCM)
|
||||
* @param aad additional authenticated data for AEAD modes (e.g., GCM);
|
||||
* may be null or empty if not required. For non-AEAD modes,
|
||||
* this parameter is ignored.
|
||||
* @throws IOException if the underlying transformation output
|
||||
* stream cannot be initialized
|
||||
* @throws IllegalArgumentException if the key or IV parameters are invalid
|
||||
* @throws InvalidKeySpecException if the encryption setup fails due to invalid
|
||||
* key specification
|
||||
*/
|
||||
public void initialize(final KeyParameter key, final byte[] iv, final AesCipherType cipherType, final byte[] aad)
|
||||
throws IOException, InvalidKeySpecException {
|
||||
this.transformationOut = AesSupport.encrypt(key, baos, iv, cipherType, aad);
|
||||
if (this.transformationOut == null) {
|
||||
throw new IllegalStateException("AES transformation OutputStream cannot be null");
|
||||
}
|
||||
}
|
||||
}
|
||||
125
lib/src/main/java/zeroecho/util/aes/AesMode.java
Normal file
125
lib/src/main/java/zeroecho/util/aes/AesMode.java
Normal file
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* 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 zeroecho.util.aes;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Enumerates the available AES encryption modes distinguished by key length:
|
||||
* 128, 192, or 256 bits.
|
||||
* <p>
|
||||
* Each constant represents a specific AES key size and provides utility methods
|
||||
* to retrieve the key length in bits and bytes, convert to a formatted string,
|
||||
* and parse from a string representation.
|
||||
* </p>
|
||||
*/
|
||||
public enum AesMode {
|
||||
/**
|
||||
* AES mode with a 128-bit key (16 bytes).
|
||||
*/
|
||||
AES_128(128),
|
||||
/**
|
||||
* AES mode with a 192-bit key (24 bytes).
|
||||
*/
|
||||
AES_192(192),
|
||||
/**
|
||||
* AES mode with a 256-bit key (32 bytes).
|
||||
*/
|
||||
AES_256(256);
|
||||
|
||||
private final int keyLengthBits;
|
||||
|
||||
/**
|
||||
* Constructs an {@code AesMode} with the given key length in bits.
|
||||
*
|
||||
* @param keyLengthBits the AES key length in bits (128, 192, or 256)
|
||||
*/
|
||||
AesMode(final int keyLengthBits) {
|
||||
this.keyLengthBits = keyLengthBits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the AES key length in bits.
|
||||
*
|
||||
* @return the key length in bits (e.g. 128, 192, 256)
|
||||
*/
|
||||
public int getKeyLengthBits() {
|
||||
return keyLengthBits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the AES key length in bytes.
|
||||
*
|
||||
* @return the key length in bytes (e.g. 16, 24, 32)
|
||||
*/
|
||||
public int getKeyLengthBytes() {
|
||||
return keyLengthBits / 8;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a human-readable string representation of the AES mode in the format
|
||||
* {@code "AES-<bits>"}, for example, {@code "AES-256"}.
|
||||
*
|
||||
* @return the formatted AES mode string
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AES-" + keyLengthBits;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string representation of the AES mode (e.g., {@code "AES-128"}) and
|
||||
* returns the corresponding {@code AesMode} constant.
|
||||
*
|
||||
* @param value the string to parse; must not be null or empty and should match
|
||||
* one of {@code "AES-128"}, {@code "AES-192"}, or
|
||||
* {@code "AES-256"}
|
||||
* @return the corresponding {@code AesMode} constant
|
||||
* @throws IllegalArgumentException if the input string is null, empty, or does
|
||||
* not correspond to a valid AES mode
|
||||
*/
|
||||
public static AesMode fromString(final String value) {
|
||||
if (value == null || value.isBlank()) {
|
||||
throw new IllegalArgumentException("AesMode string cannot be null or empty");
|
||||
}
|
||||
|
||||
return switch (value.toUpperCase(Locale.ROOT)) {
|
||||
case "AES-128" -> AES_128;
|
||||
case "AES-192" -> AES_192;
|
||||
case "AES-256" -> AES_256;
|
||||
default -> throw new IllegalArgumentException("Unsupported AesMode: " + value);
|
||||
};
|
||||
}
|
||||
}
|
||||
124
lib/src/main/java/zeroecho/util/aes/AesParameters.java
Normal file
124
lib/src/main/java/zeroecho/util/aes/AesParameters.java
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* 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 zeroecho.util.aes;
|
||||
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
|
||||
import conflux.Ctx;
|
||||
import zeroecho.data.processing.AesCommon;
|
||||
|
||||
/**
|
||||
* Defines the essential parameters required for AES cryptographic operations,
|
||||
* including encryption and decryption. This interface encapsulates the AES mode
|
||||
* (which determines the key size), the secret key, the initialization vector
|
||||
* (IV), the cipher type, and optional Additional Authenticated Data (AAD) for
|
||||
* AEAD-capable modes such as GCM.
|
||||
* <p>
|
||||
* Implementations provide the necessary inputs to configure AES-based
|
||||
* encryption/decryption algorithms securely and flexibly.
|
||||
* </p>
|
||||
*/
|
||||
public interface AesParameters {
|
||||
|
||||
/**
|
||||
* Retrieves the AES mode indicating the key size used for cryptographic
|
||||
* operations. Typical modes correspond to key lengths of 128, 192, or 256 bits.
|
||||
*
|
||||
* @return the AES mode specifying the key size
|
||||
*/
|
||||
AesMode mode();
|
||||
|
||||
/**
|
||||
* Returns the secret key wrapped in a {@link KeyParameter} object. This key is
|
||||
* used for AES encryption or decryption.
|
||||
*
|
||||
* @return the AES secret key parameter
|
||||
*/
|
||||
KeyParameter key();
|
||||
|
||||
/**
|
||||
* Provides the initialization vector (IV) required by the AES algorithm. The IV
|
||||
* length must be consistent with the AES block size, typically 16 bytes.
|
||||
* Implementations should ensure the returned array is immutable or a defensive
|
||||
* copy to prevent unintended modifications.
|
||||
*
|
||||
* @return a byte array representing the IV
|
||||
*/
|
||||
byte[] iv(); // NOPMD by Leo Galambos on 6/8/25, 5:31 PM
|
||||
|
||||
/**
|
||||
* Returns the specific cipher type to be used with AES encryption or
|
||||
* decryption. Examples include CBC, GCM, or stream cipher variants.
|
||||
* <p>
|
||||
* Implementations may define a default cipher type if not explicitly specified.
|
||||
* </p>
|
||||
*
|
||||
* @return the AES cipher type
|
||||
*/
|
||||
AesCipherType cipherType();
|
||||
|
||||
/**
|
||||
* Returns the Additional Authenticated Data (AAD) to be used in AEAD modes such
|
||||
* as GCM. The same AAD must be provided for both encryption and decryption;
|
||||
* otherwise, decryption will fail with an authentication error.
|
||||
* <p>
|
||||
* For non-AEAD modes, this value may be {@code null} or an empty byte array.
|
||||
* </p>
|
||||
*
|
||||
* @return a byte array representing the AAD, or {@code null} if unused
|
||||
*/
|
||||
byte[] aad();
|
||||
|
||||
/**
|
||||
* Persists the AES parameters into the provided {@code context}.
|
||||
* <p>
|
||||
* This method stores the AES mode, the raw secret key bytes, the initialization
|
||||
* vector (IV), the cipher type, and, if present, the AAD into the context using
|
||||
* predefined keys, facilitating secure parameter management.
|
||||
* </p>
|
||||
*
|
||||
* @param context the non-null context in which to save the AES parameters
|
||||
* @throws NullPointerException if {@code context} is null
|
||||
*/
|
||||
default void save(final Ctx context) {
|
||||
context.put(AesCommon.MODE, mode());
|
||||
context.put(AesCommon.KEY, key().getKey());
|
||||
context.put(AesCommon.IV, iv());
|
||||
context.put(AesCommon.CIPHER_TYPE, cipherType());
|
||||
if (aad() != null) {
|
||||
context.put(AesCommon.AAD, aad());
|
||||
}
|
||||
}
|
||||
}
|
||||
1003
lib/src/main/java/zeroecho/util/aes/AesSupport.java
Normal file
1003
lib/src/main/java/zeroecho/util/aes/AesSupport.java
Normal file
File diff suppressed because it is too large
Load Diff
141
lib/src/main/java/zeroecho/util/aes/BasicAesParameters.java
Normal file
141
lib/src/main/java/zeroecho/util/aes/BasicAesParameters.java
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* 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 zeroecho.util.aes;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
|
||||
/**
|
||||
* A simple, immutable implementation of {@link AesParameters} that encapsulates
|
||||
* the core cryptographic values required for AES encryption or decryption:
|
||||
* <ul>
|
||||
* <li>{@link AesMode} — defines the key size and block handling behavior</li>
|
||||
* <li>{@link KeyParameter} — the AES key material</li>
|
||||
* <li>Initialization Vector (IV) — must match the expected length for the
|
||||
* cipher type</li>
|
||||
* <li>{@link AesCipherType} — the AES cipher mode (e.g., CBC, GCM)</li>
|
||||
* <li>Optional Additional Authenticated Data (AAD) — used in AEAD modes such as
|
||||
* GCM</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* This implementation is typically used when the key and IV are known and
|
||||
* explicitly provided — such as in deterministic or hardware-based encryption
|
||||
* systems — rather than derived from a password or key derivation function.
|
||||
* </p>
|
||||
*
|
||||
* @param mode the AES mode, which dictates key size and operational
|
||||
* semantics
|
||||
* @param key the symmetric key used for AES encryption or decryption
|
||||
* @param iv the initialization vector (must not be empty; must match
|
||||
* block size required by cipher type)
|
||||
* @param cipherType the cipher mode and padding scheme (e.g., CBC, GCM, CTR)
|
||||
* @param aad optional Additional Authenticated Data for AEAD cipher
|
||||
* types (may be {@code null}); ignored for non-AEAD modes
|
||||
*
|
||||
* @see DerivedAesParameters
|
||||
* @see AesSupport
|
||||
*/
|
||||
public record BasicAesParameters(AesMode mode, KeyParameter key, byte[] iv, byte[] aad, AesCipherType cipherType)
|
||||
implements AesParameters {
|
||||
|
||||
/**
|
||||
* Constructs a {@code BasicAesParameters} instance with strict validation of
|
||||
* its arguments.
|
||||
*
|
||||
* <p>
|
||||
* Validations performed:
|
||||
* </p>
|
||||
* <ul>
|
||||
* <li>All required arguments must be non-null</li>
|
||||
* <li>The IV must not be empty</li>
|
||||
* <li>The key length must match the expected length defined by
|
||||
* {@code mode}</li>
|
||||
* <li>The IV length must match the expected size for the selected
|
||||
* {@code cipherType}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* The {@code aad} parameter is optional and is not validated here. If provided,
|
||||
* it is assumed to be used only with AEAD-capable cipher types (e.g., GCM).
|
||||
* </p>
|
||||
*
|
||||
* @throws NullPointerException if {@code mode}, {@code key}, {@code iv}, or
|
||||
* {@code cipherType} is {@code null}
|
||||
* @throws IllegalArgumentException if the key length or IV length are invalid,
|
||||
* or if the IV is empty
|
||||
*/
|
||||
public BasicAesParameters {
|
||||
mode = Objects.requireNonNull(mode, "mode must not be null");
|
||||
key = Objects.requireNonNull(key, "key must not be null");
|
||||
iv = Objects.requireNonNull(iv, "iv must not be null");
|
||||
cipherType = Objects.requireNonNull(cipherType, "cipherType must not be null");
|
||||
|
||||
if (iv.length == 0) {
|
||||
throw new IllegalArgumentException("iv must not be empty");
|
||||
}
|
||||
|
||||
if (key.getKeyLength() != mode.getKeyLengthBytes()) {
|
||||
throw new IllegalArgumentException(String.format("Key length mismatch: expected %d bytes, got %d",
|
||||
mode.getKeyLengthBytes(), key.getKeyLength()));
|
||||
}
|
||||
|
||||
if (iv.length != cipherType.getIVLengthBytes()) {
|
||||
throw new IllegalArgumentException(String.format("IV length mismatch: expected %d bytes, got %d",
|
||||
cipherType.getIVLengthBytes(), iv.length));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code BasicAesParameters} instance using the default cipher
|
||||
* type {@link AesCipherType#CBC}.
|
||||
*
|
||||
* <p>
|
||||
* Use this constructor when cipher mode selection is fixed to CBC and no AAD is
|
||||
* needed.
|
||||
* </p>
|
||||
*
|
||||
* @param mode the AES mode (e.g., 128, 192, 256-bit)
|
||||
* @param key the AES encryption key
|
||||
* @param iv the initialization vector (must match CBC IV size)
|
||||
*
|
||||
* @throws NullPointerException if any parameter is {@code null}
|
||||
* @throws IllegalArgumentException if key length or IV length are invalid
|
||||
*/
|
||||
public BasicAesParameters(final AesMode mode, final KeyParameter key, final byte[] iv) {
|
||||
this(mode, key, iv, null, AesCipherType.CBC);
|
||||
}
|
||||
}
|
||||
201
lib/src/main/java/zeroecho/util/aes/DerivedAesParameters.java
Normal file
201
lib/src/main/java/zeroecho/util/aes/DerivedAesParameters.java
Normal file
@@ -0,0 +1,201 @@
|
||||
/**
|
||||
* 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 zeroecho.util.aes;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
|
||||
import conflux.Ctx;
|
||||
import conflux.Key;
|
||||
|
||||
/**
|
||||
* An implementation of {@link AesParameters} that encapsulates AES
|
||||
* cryptographic parameters derived from a password using a key derivation
|
||||
* function (KDF), such as PBKDF2.
|
||||
* <p>
|
||||
* In addition to the core parameters required for AES operations (mode, key,
|
||||
* IV, and cipher type), this implementation includes metadata necessary for
|
||||
* reconstructing the derived key: the salt and iteration count used during the
|
||||
* KDF process.
|
||||
* </p>
|
||||
* <p>
|
||||
* This class is intended for deterministic, password-based AES key derivation
|
||||
* workflows, ensuring reproducibility of keys for decryption given the same
|
||||
* password, salt, and iteration count.
|
||||
* </p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link AesMode} — the AES mode (determines key size and behavior)</li>
|
||||
* <li>{@link KeyParameter} — the derived AES key for encryption or
|
||||
* decryption</li>
|
||||
* <li>Initialization Vector (IV) — must match the expected length for the
|
||||
* cipher type</li>
|
||||
* <li>Salt — the salt value used in the password-based key derivation
|
||||
* function</li>
|
||||
* <li>Iterations — the number of iterations used in the key derivation function
|
||||
* (must be >= 1)</li>
|
||||
* <li>{@link AesCipherType} — the AES cipher type (e.g., CBC, GCM, CTR)</li>
|
||||
* <li>Optional Additional Authenticated Data (AAD) — used for AEAD cipher types
|
||||
* (may be {@code null})</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param mode the AES mode (determines key size and behavior)
|
||||
* @param key the derived AES key for encryption or decryption
|
||||
* @param iv the derived initialization vector (IV), must match the
|
||||
* expected length for the cipher type
|
||||
* @param salt the salt value used in the password-based key derivation
|
||||
* function
|
||||
* @param iterations the number of iterations used in the key derivation
|
||||
* function (must be >= 1)
|
||||
* @param aad optional Additional Authenticated Data for AEAD cipher
|
||||
* types (may be {@code null}); ignored for non-AEAD modes
|
||||
* @param cipherType the AES cipher type (e.g., CBC, GCM, CTR)
|
||||
*
|
||||
* @see BasicAesParameters
|
||||
* @see AesSupport
|
||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc8018">RFC 8018 -
|
||||
* PBKDF2</a>
|
||||
*/
|
||||
public record DerivedAesParameters(AesMode mode, KeyParameter key, byte[] iv, byte[] salt, int iterations, byte[] aad,
|
||||
AesCipherType cipherType) implements AesParameters {
|
||||
|
||||
/**
|
||||
* Key used to store the salt in a {@link Ctx} context.
|
||||
*/
|
||||
public static final Key<byte[]> SALT = Key.of("secret.salt", byte[].class);
|
||||
/**
|
||||
* Key used to store the iteration count in a {@link Ctx} context.
|
||||
*/
|
||||
public static final Key<Integer> ITERATIONS = Key.of("secret.iterations", int.class);
|
||||
|
||||
/**
|
||||
* Constructs a new {@code DerivedAesParameters} instance with full validation
|
||||
* of all arguments.
|
||||
* <p>
|
||||
* This constructor performs the following checks:
|
||||
* <ul>
|
||||
* <li>None of the required parameters are {@code null}</li>
|
||||
* <li>The IV is non-empty and matches the expected length for the selected
|
||||
* cipher type</li>
|
||||
* <li>The key length matches the expected size for the selected AES mode</li>
|
||||
* <li>The iteration count is a positive integer (>= 1)</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* The optional {@code aad} may be {@code null} and is not validated here.
|
||||
* </p>
|
||||
*
|
||||
* @throws NullPointerException if any required parameter is {@code null}
|
||||
* @throws IllegalArgumentException if the IV is empty or invalid, the key
|
||||
* length is incorrect, or the iteration count
|
||||
* is less than 1
|
||||
*/
|
||||
public DerivedAesParameters {
|
||||
mode = Objects.requireNonNull(mode, "mode must not be null");
|
||||
key = Objects.requireNonNull(key, "key must not be null");
|
||||
iv = Objects.requireNonNull(iv, "iv must not be null");
|
||||
salt = Objects.requireNonNull(salt, "salt must not be null");
|
||||
cipherType = Objects.requireNonNull(cipherType, "cipherType must not be null");
|
||||
// aad may be null, no validation needed
|
||||
|
||||
if (iv.length == 0) {
|
||||
throw new IllegalArgumentException("iv must not be empty");
|
||||
}
|
||||
|
||||
if (key.getKeyLength() != mode.getKeyLengthBytes()) {
|
||||
throw new IllegalArgumentException(String.format("Key length mismatch: expected %d bytes, got %d",
|
||||
mode.getKeyLengthBytes(), key.getKeyLength()));
|
||||
}
|
||||
|
||||
if (iv.length != cipherType.getIVLengthBytes()) {
|
||||
throw new IllegalArgumentException(String.format("IV length mismatch: expected %d bytes, got %d",
|
||||
cipherType.getIVLengthBytes(), iv.length));
|
||||
}
|
||||
|
||||
if (iterations < 1) { // NOPMD by Leo Galambos on 6/8/25, 9:14 PM
|
||||
throw new IllegalArgumentException("iterations must be a positive integer");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code DerivedAesParameters} instance with the default cipher
|
||||
* type {@link AesCipherType#CBC} and no AAD.
|
||||
*
|
||||
* @param mode the AES mode used
|
||||
* @param key the derived AES key
|
||||
* @param iv the derived initialization vector
|
||||
* @param salt the salt used in the key derivation function
|
||||
* @param iterations the number of iterations used in key derivation
|
||||
*/
|
||||
public DerivedAesParameters(final AesMode mode, final KeyParameter key, final byte[] iv, final byte[] salt,
|
||||
final int iterations) {
|
||||
this(mode, key, iv, salt, iterations, null, AesCipherType.CBC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this derived parameter set into a {@link BasicAesParameters}
|
||||
* instance by omitting derivation-specific metadata (salt, iteration count) and
|
||||
* including the AAD if present.
|
||||
*
|
||||
* @return a new {@link BasicAesParameters} object with equivalent AES mode,
|
||||
* key, IV, cipher type, and optional AAD
|
||||
*/
|
||||
public BasicAesParameters toBasicParameters() {
|
||||
return new BasicAesParameters(mode, key, iv, aad, cipherType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves this parameter set into the provided {@link Ctx} object.
|
||||
* <p>
|
||||
* This includes:
|
||||
* <ul>
|
||||
* <li>The core AES parameters defined in {@link AesParameters}</li>
|
||||
* <li>The {@code salt} under the key {@link #SALT}</li>
|
||||
* <li>The {@code iterations} under the key {@link #ITERATIONS}</li>
|
||||
* </ul>
|
||||
* This enables later retrieval or transmission of the full AES derivation
|
||||
* context.
|
||||
*
|
||||
* @param context the context to populate; must not be {@code null}
|
||||
* @throws NullPointerException if the context is {@code null}
|
||||
*/
|
||||
@Override
|
||||
public void save(final Ctx context) {
|
||||
AesParameters.super.save(context);
|
||||
|
||||
context.put(ITERATIONS, iterations());
|
||||
context.put(SALT, salt());
|
||||
}
|
||||
}
|
||||
81
lib/src/main/java/zeroecho/util/aes/package-info.java
Normal file
81
lib/src/main/java/zeroecho/util/aes/package-info.java
Normal file
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
/**
|
||||
* Provides AES (Advanced Encryption Standard) cryptographic utilities and
|
||||
* abstractions based on the Bouncy Castle library.
|
||||
* <p>
|
||||
* This package includes support for multiple AES cipher modes (e.g., CBC, GCM,
|
||||
* CTR) and key sizes (128, 192, and 256 bits), along with flexible parameter
|
||||
* encapsulation and utility methods for encryption and decryption.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Core Components</h2>
|
||||
* <ul>
|
||||
* <li>{@link AesCipherType} — Enum representing supported AES cipher modes and
|
||||
* their padding schemes.</li>
|
||||
* <li>{@link AesMode} — Enum for AES key sizes: 128, 192, and 256-bit
|
||||
* modes.</li>
|
||||
* <li>{@link AesParameters} — Interface defining cryptographic parameters
|
||||
* required for AES operations.</li>
|
||||
* <li>{@link BasicAesParameters} — Immutable implementation of
|
||||
* {@code AesParameters} with explicit key and IV.</li>
|
||||
* <li>{@link DerivedAesParameters} — Parameter object with AES key and IV
|
||||
* derived from a password using PBKDF2.</li>
|
||||
* <li>{@link AesSupport} — Final utility class for AES key generation and
|
||||
* stream-based encryption/decryption.</li>
|
||||
* <li>{@link AesInputStreamAdapter} — Deprecated input stream adapter for AES
|
||||
* encryption of plaintext input.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Usage Notes</h2>
|
||||
* <p>
|
||||
* Most users will interact with this package through {@link AesSupport} for
|
||||
* high-level operations, or construct {@code AesParameters} objects directly to
|
||||
* configure encryption workflows. Random IV generation and cipher construction
|
||||
* are abstracted by the {@code AesCipherType}.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Dependencies</h2>
|
||||
* <p>
|
||||
* This package relies on the
|
||||
* <a href="https://www.bouncycastle.org/java.html">Bouncy Castle</a> library
|
||||
* for low-level cryptographic primitives. Ensure that the Bouncy Castle
|
||||
* provider is registered and available on the classpath.
|
||||
* </p>
|
||||
*
|
||||
* @author Leo Galambos
|
||||
* @since 1.0
|
||||
*/
|
||||
package zeroecho.util.aes;
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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 zeroecho.util.asymmetric;
|
||||
|
||||
import java.io.Closeable;
|
||||
|
||||
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
|
||||
|
||||
/**
|
||||
* Represents an asymmetric cryptographic context.
|
||||
* <p>
|
||||
* This sealed interface is implemented by specific asymmetric context types,
|
||||
* such as classic asymmetric contexts or key encapsulation mechanism (KEM)
|
||||
* contexts.
|
||||
* </p>
|
||||
*/
|
||||
public sealed interface AsymmetricContext extends Closeable permits ClassicAsymmetricContext, KEMAsymmetricContext, SignatureAsymmetricContext {
|
||||
|
||||
/**
|
||||
* Returns the asymmetric key parameter associated with this context.
|
||||
*
|
||||
* @return the asymmetric key parameter (public or private key)
|
||||
*/
|
||||
AsymmetricKeyParameter key();
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
/**
|
||||
* 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 zeroecho.util.asymmetric;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.security.auth.DestroyFailedException;
|
||||
|
||||
import org.bouncycastle.crypto.AsymmetricBlockCipher;
|
||||
import org.bouncycastle.crypto.EncapsulatedSecretExtractor;
|
||||
import org.bouncycastle.crypto.EncapsulatedSecretGenerator;
|
||||
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
|
||||
|
||||
import zeroecho.util.aes.AesCipherType;
|
||||
|
||||
/**
|
||||
* Builder class to construct {@link InputStream} instances that perform
|
||||
* asymmetric encryption or decryption on data streams.
|
||||
* <p>
|
||||
* This builder supports two main modes of asymmetric cryptographic processing:
|
||||
* <ul>
|
||||
* <li><b>Classic block cipher mode:</b> Using a configured
|
||||
* {@link AsymmetricBlockCipher} engine and key for direct
|
||||
* encryption/decryption.</li>
|
||||
* <li><b>Key Encapsulation Mechanism (KEM) mode:</b> Using
|
||||
* {@link EncapsulatedSecretGenerator} or {@link EncapsulatedSecretExtractor}
|
||||
* for encapsulating or decapsulating symmetric keys combined with AES
|
||||
* encryption.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* The builder allows configuring the input source stream, the asymmetric key
|
||||
* (public or private), the cipher engine or KEM components, and the AES cipher
|
||||
* type used for symmetric encryption within KEM mode.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Example usage for classic encryption mode: <pre>
|
||||
* InputStream encryptedStream = AsymmetricStreamBuilder.newBuilder()
|
||||
* .withInputStream(dataInput)
|
||||
* .withCipherEngine(myAsymmetricCipher)
|
||||
* .withKey(publicKeyParam)
|
||||
* .buildEncryptingStream();
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* Example usage for KEM-based encryption mode: <pre>
|
||||
* InputStream encryptedStream = AsymmetricStreamBuilder.newBuilder()
|
||||
* .withInputStream(dataInput)
|
||||
* .withKEMGenerator(myKEMGenerator)
|
||||
* .withKey(publicKeyParam)
|
||||
* .withKEMCipherType(AesCipherType.GCM)
|
||||
* .buildEncryptingStream();
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* Instances of this builder are immutable after {@code buildEncryptingStream()}
|
||||
* or {@code buildDecryptingStream()} is called.
|
||||
* </p>
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public final class AsymmetricStreamBuilder { // NOPMD
|
||||
private static final Logger LOG = Logger.getLogger(AsymmetricStreamBuilder.class.getName());
|
||||
|
||||
private InputStream input;
|
||||
private AsymmetricBlockCipher engine;
|
||||
private AsymmetricKeyParameter key;
|
||||
private byte[] aad;
|
||||
private EncapsulatedSecretGenerator generator;
|
||||
private EncapsulatedSecretExtractor extractor;
|
||||
private AesCipherType cipherType = AesCipherType.GCM;
|
||||
|
||||
private AsymmetricStreamBuilder() {
|
||||
// Use factory method
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@code AsymmetricStreamBuilder}.
|
||||
*
|
||||
* @return a new builder instance
|
||||
*/
|
||||
public static AsymmetricStreamBuilder newBuilder() {
|
||||
return new AsymmetricStreamBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the input stream to be encrypted or decrypted.
|
||||
*
|
||||
* @param input the source {@link InputStream}
|
||||
* @return this builder instance
|
||||
*/
|
||||
public AsymmetricStreamBuilder withInputStream(final InputStream input) {
|
||||
this.input = Objects.requireNonNull(input, "Input stream cannot be null");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the classic asymmetric cipher engine for block encryption or decryption.
|
||||
*
|
||||
* <p>
|
||||
* Using this method will clear any previously set KEM generator or extractor,
|
||||
* since classic cipher engine and KEM modes are mutually exclusive.
|
||||
* </p>
|
||||
*
|
||||
* @param engine the {@link AsymmetricBlockCipher} engine to use
|
||||
* @return this builder instance for method chaining
|
||||
* @throws NullPointerException if {@code engine} is {@code null}
|
||||
*/
|
||||
public AsymmetricStreamBuilder withCipherEngine(final AsymmetricBlockCipher engine) {
|
||||
this.engine = Objects.requireNonNull(engine, "Cipher engine cannot be null");
|
||||
this.generator = null; // NOPMD
|
||||
this.extractor = null; // NOPMD
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the AES cipher type used internally within KEM-based encryption or
|
||||
* decryption.
|
||||
*
|
||||
* @param cipherType the {@link AesCipherType} to use (e.g., CBC, GCM)
|
||||
* @return this builder instance for method chaining
|
||||
* @throws NullPointerException if {@code cipherType} is {@code null}
|
||||
*/
|
||||
public AsymmetricStreamBuilder withKEMCipherType(final AesCipherType cipherType) {
|
||||
this.cipherType = Objects.requireNonNull(cipherType, "KEM cipher type cannot be null");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Additional Authenticated Data (AAD) to be associated with the Key
|
||||
* Encapsulation Mechanism (KEM) cipher operations.
|
||||
*
|
||||
* <p>
|
||||
* The specified AAD will be bound to the authenticated encryption process
|
||||
* (e.g., when using AES-GCM) to ensure integrity protection of external
|
||||
* associated data that is not encrypted but must be authenticated. This method
|
||||
* should be called before initiating encryption or decryption.
|
||||
* </p>
|
||||
*
|
||||
* @param aad the Additional Authenticated Data to associate with the cipher;
|
||||
* may be {@code null} if no AAD is required
|
||||
* @return this builder instance for method chaining
|
||||
*/
|
||||
public AsymmetricStreamBuilder withKEMCipherAad(final byte[] aad) {
|
||||
this.aad = aad; // NOPMD
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the key encapsulation mechanism (KEM) secret generator for encryption.
|
||||
*
|
||||
* <p>
|
||||
* Using this method will clear any previously set cipher engine or KEM
|
||||
* extractor, since these modes are mutually exclusive.
|
||||
* </p>
|
||||
*
|
||||
* @param generator the {@link EncapsulatedSecretGenerator} to use
|
||||
* @return this builder instance for method chaining
|
||||
* @throws NullPointerException if {@code generator} is {@code null}
|
||||
*/
|
||||
public AsymmetricStreamBuilder withKEMGenerator(final EncapsulatedSecretGenerator generator) {
|
||||
this.engine = null; // NOPMD
|
||||
this.generator = Objects.requireNonNull(generator, "KEM generator cannot be null");
|
||||
this.extractor = null; // NOPMD
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the key encapsulation mechanism (KEM) secret extractor for decryption.
|
||||
*
|
||||
* <p>
|
||||
* Using this method will clear any previously set cipher engine or KEM
|
||||
* generator, since these modes are mutually exclusive.
|
||||
* </p>
|
||||
*
|
||||
* @param extractor the {@link EncapsulatedSecretExtractor} to use
|
||||
* @return this builder instance for method chaining
|
||||
* @throws NullPointerException if {@code extractor} is {@code null}
|
||||
*/
|
||||
public AsymmetricStreamBuilder withKEMExtractor(final EncapsulatedSecretExtractor extractor) {
|
||||
this.engine = null; // NOPMD
|
||||
this.generator = null; // NOPMD
|
||||
this.extractor = Objects.requireNonNull(extractor, "KEM extractor cannot be null");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the asymmetric key (public or private) used for encryption or
|
||||
* decryption.
|
||||
*
|
||||
* @param key the {@link AsymmetricKeyParameter} to use
|
||||
* @return this builder instance
|
||||
*/
|
||||
public AsymmetricStreamBuilder withKey(final AsymmetricKeyParameter key) {
|
||||
this.key = Objects.requireNonNull(key, "Key parameter cannot be null");
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an {@link InputStream} that encrypts data from the configured
|
||||
* input stream using the specified asymmetric cipher engine or KEM generator.
|
||||
*
|
||||
* @return an {@link InputStream} producing encrypted data
|
||||
* @throws IOException if an I/O error occurs during stream creation
|
||||
* @throws DestroyFailedException if secure destruction of cryptographic
|
||||
* material fails
|
||||
* @throws IllegalStateException if required parameters are missing or invalid
|
||||
*/
|
||||
public InputStream buildEncryptingStream() throws IOException, DestroyFailedException {
|
||||
validate();
|
||||
|
||||
if (engine != null) {
|
||||
final ClassicAsymmetricEncryptionStream stream = new ClassicAsymmetricEncryptionStream(input, engine);
|
||||
stream.initialize(true, key);
|
||||
return stream;
|
||||
}
|
||||
|
||||
LOG.info("building KEM-encryption");
|
||||
|
||||
Objects.requireNonNull(generator, "KEM generator must be set for the decryption process");
|
||||
|
||||
try (KEMAsymmetricContext kem = new KEMAsymmetricContext(key, generator)) {
|
||||
return kem.getEncryptedStream(input, cipherType, aad);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an {@link InputStream} that decrypts data from the configured
|
||||
* input stream using the specified asymmetric cipher engine or KEM extractor.
|
||||
*
|
||||
* @return an {@link InputStream} producing decrypted data
|
||||
* @throws IOException if an I/O error occurs during stream creation
|
||||
* @throws DestroyFailedException if secure destruction of cryptographic
|
||||
* material fails
|
||||
* @throws IllegalStateException if required parameters are missing or invalid
|
||||
*/
|
||||
public InputStream buildDecryptingStream() throws IOException, DestroyFailedException {
|
||||
validate();
|
||||
|
||||
if (engine != null) {
|
||||
final ClassicAsymmetricEncryptionStream stream = new ClassicAsymmetricEncryptionStream(input, engine);
|
||||
stream.initialize(false, key);
|
||||
return stream;
|
||||
}
|
||||
|
||||
LOG.info("building KEM-decryption");
|
||||
|
||||
Objects.requireNonNull(extractor, "KEM extractor must be set for the decryption process");
|
||||
|
||||
try (KEMAsymmetricContext kem = new KEMAsymmetricContext(key, extractor)) {
|
||||
return kem.getDecryptedStream(input, cipherType, aad);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that all required parameters have been set correctly before
|
||||
* building encryption or decryption streams.
|
||||
*
|
||||
* @throws NullPointerException if any mandatory parameter is missing
|
||||
* @throws IllegalStateException if none of cipher engine, KEM generator, or KEM
|
||||
* extractor is set
|
||||
*/
|
||||
private void validate() {
|
||||
Objects.requireNonNull(input, "InputStream must be provided");
|
||||
Objects.requireNonNull(key, "Key must be provided");
|
||||
|
||||
if (engine == null && generator == null && extractor == null) {
|
||||
throw new NullPointerException("Cipher engine, KEM generator or extractor must be provided"); // NOPMD
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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 zeroecho.util.asymmetric;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.bouncycastle.crypto.AsymmetricBlockCipher;
|
||||
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
|
||||
|
||||
/**
|
||||
* Classic asymmetric context implementation that uses an asymmetric key
|
||||
* parameter and an asymmetric block cipher for encryption/decryption.
|
||||
*
|
||||
* @param key the asymmetric key parameter (public/private)
|
||||
* @param cipher the asymmetric block cipher to use
|
||||
*/
|
||||
public record ClassicAsymmetricContext(AsymmetricKeyParameter key, AsymmetricBlockCipher cipher)
|
||||
implements AsymmetricContext {
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
// empty intentionally
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
/**
|
||||
* 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 zeroecho.util.asymmetric;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bouncycastle.crypto.AsymmetricBlockCipher;
|
||||
import org.bouncycastle.crypto.InvalidCipherTextException;
|
||||
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
|
||||
|
||||
/**
|
||||
* A streaming class that performs block-based asymmetric encryption or
|
||||
* decryption on data read from an underlying {@link InputStream}.
|
||||
*/
|
||||
class ClassicAsymmetricEncryptionStream extends InputStream {
|
||||
private static final Logger LOG = Logger.getLogger(ClassicAsymmetricEncryptionStream.class.getName());
|
||||
|
||||
private final static int BLOCKS_IN_BUF = 1_000;
|
||||
/**
|
||||
* The input stream from which raw data is read.
|
||||
*/
|
||||
final private InputStream input;
|
||||
/**
|
||||
* The asymmetric block cipher engine used for encryption or decryption.
|
||||
*/
|
||||
final private AsymmetricBlockCipher engine;
|
||||
/**
|
||||
* The input block size in bytes, determined by the cipher engine. This value is
|
||||
* set during initialization.
|
||||
*/
|
||||
private int blockSize;
|
||||
|
||||
private InputStream bais = nullInputStream();
|
||||
|
||||
/**
|
||||
* Constructs a new {@code ClassicAsymmetricEncryptionStream} with the given
|
||||
* input stream and cipher engine.
|
||||
*
|
||||
* @param input the source input stream
|
||||
* @param engine the asymmetric block cipher engine
|
||||
*/
|
||||
public ClassicAsymmetricEncryptionStream(final InputStream input, final AsymmetricBlockCipher engine) {
|
||||
super();
|
||||
this.input = input;
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the cipher engine with encryption or decryption mode.
|
||||
*
|
||||
* @param encrypt {@code true} to initialize for encryption; {@code false} for
|
||||
* decryption
|
||||
* @param key the asymmetric key parameter (public or private)
|
||||
*/
|
||||
protected void initialize(final boolean encrypt, final AsymmetricKeyParameter key) {
|
||||
engine.init(encrypt, key);
|
||||
blockSize = engine.getInputBlockSize();
|
||||
|
||||
if (LOG.isLoggable(Level.FINE)) {
|
||||
LOG.log(Level.FINE, "block size in: {0}, out: {1}",
|
||||
new Object[] { engine.getInputBlockSize(), engine.getOutputBlockSize() });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a single byte from the encrypted/decrypted stream.
|
||||
*
|
||||
* @return the byte read, or {@code -1} if end of stream is reached
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
ensureData();
|
||||
|
||||
return bais.read();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads up to {@code len} bytes of data from the stream into an array of bytes.
|
||||
*
|
||||
* @param b the buffer into which the data is read
|
||||
* @param off the start offset in array {@code b} at which the data is written
|
||||
* @param len the maximum number of bytes to read
|
||||
* @return the total number of bytes read, or {@code -1} if end of stream is
|
||||
* reached
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Override
|
||||
public int read(final byte[] b, final int off, final int len) throws IOException {
|
||||
ensureData();
|
||||
return bais.read(b, off, len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the underlying input stream.
|
||||
*
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
input.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that encrypted or decrypted data is available to read.
|
||||
* <p>
|
||||
* Reads blocks of data from the input stream, processes them with the cipher,
|
||||
* and buffers the results.
|
||||
* </p>
|
||||
*
|
||||
* @throws IOException if an I/O error occurs or encryption fails
|
||||
*/
|
||||
private void ensureData() throws IOException {
|
||||
// we still have some data in a buffer?
|
||||
if (bais.available() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final byte[] buf = new byte[BLOCKS_IN_BUF * blockSize];
|
||||
final int len = input.readNBytes(buf, 0, buf.length);
|
||||
|
||||
if (len == 0) {
|
||||
// we have EOF
|
||||
bais = nullInputStream();
|
||||
return;
|
||||
}
|
||||
|
||||
// XXX we could use the original "buf" iff getInputBlockSize() >=
|
||||
// getOutputBlockSize()
|
||||
final ByteArrayOutputStream encrypted = new ByteArrayOutputStream(buf.length);
|
||||
|
||||
for (int i = 0; i < len; i += blockSize) {
|
||||
try {
|
||||
encrypted.write(engine.processBlock(buf, i, Math.min(blockSize, len - i)));
|
||||
} catch (InvalidCipherTextException e) {
|
||||
LOG.logp(Level.FINE, "AsymmetricEncryptionInputStream", "prepareBuffer", "Exception", e);
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
bais = new ByteArrayInputStream(encrypted.toByteArray());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
/**
|
||||
* 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 zeroecho.util.asymmetric;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.SequenceInputStream;
|
||||
import java.util.Objects;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.security.auth.DestroyFailedException;
|
||||
import javax.security.auth.Destroyable;
|
||||
|
||||
import org.bouncycastle.crypto.EncapsulatedSecretExtractor;
|
||||
import org.bouncycastle.crypto.EncapsulatedSecretGenerator;
|
||||
import org.bouncycastle.crypto.SecretWithEncapsulation;
|
||||
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
|
||||
|
||||
import zeroecho.util.IOUtil;
|
||||
import zeroecho.util.aes.AesCipherType;
|
||||
import zeroecho.util.aes.AesSupport;
|
||||
|
||||
/**
|
||||
* A context object for holding data required in post-quantum Key Encapsulation
|
||||
* Mechanism (KEM) operations.
|
||||
* <p>
|
||||
* This class stores the asymmetric key and either an
|
||||
* {@link EncapsulatedSecretGenerator} (for encapsulation) or an
|
||||
* {@link EncapsulatedSecretExtractor} (for decapsulation), depending on how the
|
||||
* instance is constructed.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* It also supports lazy evaluation and caching of the generated
|
||||
* {@link SecretWithEncapsulation}, and provides access to the derived secret
|
||||
* and encapsulated value. Additionally, this class implements
|
||||
* {@link Destroyable} to allow secure erasure of sensitive material once it is
|
||||
* no longer needed.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This class is intended to be a simple holder of cryptographic components for
|
||||
* use in a KEM workflow.
|
||||
* </p>
|
||||
*
|
||||
* @see EncapsulatedSecretGenerator
|
||||
* @see EncapsulatedSecretExtractor
|
||||
* @see SecretWithEncapsulation
|
||||
* @see AsymmetricContext
|
||||
*/
|
||||
public final class KEMAsymmetricContext implements AsymmetricContext, Destroyable {
|
||||
private static final Logger LOG = Logger.getLogger(KEMAsymmetricContext.class.getName());
|
||||
|
||||
/**
|
||||
* The most recently generated or extracted secret with its encapsulation.
|
||||
* Cached for reuse until destroyed.
|
||||
*/
|
||||
private SecretWithEncapsulation lastEncapsulatedSecret;
|
||||
/**
|
||||
* The asymmetric key used for the KEM operation.
|
||||
*/
|
||||
final private AsymmetricKeyParameter keyField;
|
||||
/**
|
||||
* The generator used for encapsulation (if applicable).
|
||||
*/
|
||||
final private EncapsulatedSecretGenerator generatorField;
|
||||
/**
|
||||
* The extractor used for decapsulation (if applicable).
|
||||
*/
|
||||
final private EncapsulatedSecretExtractor extractorField;
|
||||
|
||||
/**
|
||||
* Constructs a context for KEM encapsulation using the given public key and
|
||||
* generator.
|
||||
*
|
||||
* @param key the public key used for encapsulation
|
||||
* @param generator the generator responsible for producing the encapsulated
|
||||
* secret
|
||||
*/
|
||||
public KEMAsymmetricContext(AsymmetricKeyParameter key, EncapsulatedSecretGenerator generator) {
|
||||
this.keyField = key;
|
||||
|
||||
this.generatorField = Objects.requireNonNull(generator, "generator cannot be null");
|
||||
this.extractorField = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a context for KEM decapsulation using the given private key and
|
||||
* extractor.
|
||||
*
|
||||
* @param key the private key used for decapsulation
|
||||
* @param extractor the extractor responsible for deriving the secret from the
|
||||
* encapsulated input
|
||||
*/
|
||||
public KEMAsymmetricContext(AsymmetricKeyParameter key, EncapsulatedSecretExtractor extractor) {
|
||||
this.keyField = key;
|
||||
|
||||
this.generatorField = null;
|
||||
this.extractorField = Objects.requireNonNull(extractor, "extractor cannot be null");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an input stream that provides the encrypted version of the original
|
||||
* stream by generating a secret key and encapsulation, then encrypting the data
|
||||
* with AES.
|
||||
*
|
||||
* <p>
|
||||
* The resulting stream begins with the encapsulation and IV, followed by the
|
||||
* AES-encrypted payload. If Additional Authenticated Data (AAD) is provided, it
|
||||
* is included in the encryption process for modes that support authentication
|
||||
* (e.g., GCM).
|
||||
* </p>
|
||||
*
|
||||
* @param originalInputStream the original plaintext input stream; must not be
|
||||
* {@code null}
|
||||
* @param cipherType the AES cipher type to use for encryption; must
|
||||
* not be {@code null}
|
||||
* @param aad optional Additional Authenticated Data (AAD) to
|
||||
* bind to the encryption operation; may be
|
||||
* {@code null} if unused
|
||||
* @return an input stream combining encapsulation, IV, and AES-encrypted data
|
||||
* @throws IOException if reading from or writing to the streams fails
|
||||
*/
|
||||
public InputStream getEncryptedStream(InputStream originalInputStream, AesCipherType cipherType, final byte[] aad)
|
||||
throws IOException {
|
||||
// Generate secret and encapsulation
|
||||
byte[] aesKey = getSecret(); // AES key
|
||||
byte[] encapsulation = getEncapsulation(); // to be sent along
|
||||
|
||||
// Generate IV
|
||||
byte[] iv = cipherType.generateRandomIV();
|
||||
|
||||
// AES encrypt original stream
|
||||
InputStream encryptedStream = AesSupport.encrypt(aesKey, iv, aad, cipherType, originalInputStream);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
IOUtil.write(baos, encapsulation);
|
||||
IOUtil.write(baos, iv);
|
||||
|
||||
// Combine everything into a final stream
|
||||
return new SequenceInputStream(new ByteArrayInputStream(baos.toByteArray()), encryptedStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an input stream that provides the decrypted version of the original
|
||||
* encrypted stream.
|
||||
*
|
||||
* <p>
|
||||
* The stream reads the encapsulation and IV, reconstructs the AES key, and
|
||||
* decrypts the payload. If Additional Authenticated Data (AAD) was used during
|
||||
* encryption, the same AAD must be provided to ensure successful authentication
|
||||
* and decryption.
|
||||
* </p>
|
||||
*
|
||||
* @param originalInputStream the encrypted input stream starting with
|
||||
* encapsulation and IV; must not be {@code null}
|
||||
* @param cipherType the AES cipher type used during encryption; must
|
||||
* not be {@code null}
|
||||
* @param aad the same Additional Authenticated Data (AAD) used
|
||||
* during encryption; may be {@code null} if
|
||||
* encryption did not use AAD
|
||||
* @return an input stream yielding the decrypted plaintext data
|
||||
* @throws IOException if reading the stream fails or if authentication fails
|
||||
* (for modes like GCM)
|
||||
*/
|
||||
public InputStream getDecryptedStream(InputStream originalInputStream, AesCipherType cipherType, final byte[] aad)
|
||||
throws IOException {
|
||||
byte[] encapsulation = IOUtil.read(originalInputStream, 24_000);
|
||||
byte[] iv = IOUtil.read(originalInputStream, cipherType.getIVLengthBytes());
|
||||
|
||||
// Recover AES key
|
||||
byte[] aesKey = extractorField.extractSecret(encapsulation);
|
||||
|
||||
// Decrypt
|
||||
return AesSupport.decrypt(aesKey, iv, aad, cipherType, originalInputStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the cached secret, if it exists.
|
||||
*
|
||||
* @throws DestroyFailedException if destruction fails
|
||||
*/
|
||||
@Override
|
||||
public void destroy() throws DestroyFailedException {
|
||||
if (lastEncapsulatedSecret != null) {
|
||||
lastEncapsulatedSecret.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
destroy();
|
||||
} catch (DestroyFailedException e) {
|
||||
LOG.logp(Level.WARNING, "KEMAsymmetricContext", "close", "DestroyFailedException", e);
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the cached secret has been destroyed or never initialized.
|
||||
*
|
||||
* @return {@code true} if the secret is destroyed or not yet generated;
|
||||
* {@code false} otherwise
|
||||
*/
|
||||
@Override
|
||||
public boolean isDestroyed() {
|
||||
return lastEncapsulatedSecret == null || lastEncapsulatedSecret.isDestroyed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the derived shared secret. This is lazily generated and cached if the
|
||||
* context is in encapsulation mode.
|
||||
*
|
||||
* @return the derived secret bytes
|
||||
* @throws IllegalStateException if the context is not initialized for
|
||||
* encapsulation
|
||||
*/
|
||||
public byte[] getSecret() {
|
||||
return getSecretWithEncapsulation().getSecret();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encapsulated data associated with the derived secret. Lazily
|
||||
* generated if not already cached.
|
||||
*
|
||||
* @return the encapsulation bytes
|
||||
* @throws IllegalStateException if the context is not initialized for
|
||||
* encapsulation
|
||||
*/
|
||||
public byte[] getEncapsulation() {
|
||||
return getSecretWithEncapsulation().getEncapsulation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the asymmetric key parameter associated with this context.
|
||||
*
|
||||
* @return the asymmetric key parameter (public or private)
|
||||
*/
|
||||
@Override
|
||||
public AsymmetricKeyParameter key() {
|
||||
return keyField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encapsulated secret generator if available.
|
||||
*
|
||||
* @return the generator instance, or {@code null} if this context is for
|
||||
* decapsulation
|
||||
*/
|
||||
public EncapsulatedSecretGenerator generator() {
|
||||
return generatorField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encapsulated secret extractor if available.
|
||||
*
|
||||
* @return the extractor instance, or {@code null} if this context is for
|
||||
* encapsulation
|
||||
*/
|
||||
public EncapsulatedSecretExtractor extractor() {
|
||||
return extractorField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily generates or returns the cached {@link SecretWithEncapsulation} for
|
||||
* this context.
|
||||
*
|
||||
* @return the encapsulated secret and its encapsulation bytes
|
||||
* @throws IllegalStateException if no generator is present in the context
|
||||
*/
|
||||
private SecretWithEncapsulation getSecretWithEncapsulation() {
|
||||
if (lastEncapsulatedSecret == null) {
|
||||
lastEncapsulatedSecret = generatorField.generateEncapsulated(keyField);
|
||||
}
|
||||
|
||||
return lastEncapsulatedSecret;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 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 zeroecho.util.asymmetric;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
|
||||
|
||||
/**
|
||||
* Classic asymmetric context implementation that uses an asymmetric key
|
||||
* parameter for signing.
|
||||
*
|
||||
* @param key the asymmetric key parameter (public/private)
|
||||
*/
|
||||
public record SignatureAsymmetricContext(AsymmetricKeyParameter key) implements AsymmetricContext {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
// empty intentionally
|
||||
}
|
||||
}
|
||||
82
lib/src/main/java/zeroecho/util/asymmetric/package-info.java
Normal file
82
lib/src/main/java/zeroecho/util/asymmetric/package-info.java
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
/**
|
||||
* Provides core classes and interfaces for performing asymmetric encryption and
|
||||
* decryption on data streams.
|
||||
* <p>
|
||||
* This package supports both classical asymmetric cryptography based on block
|
||||
* ciphers and modern post-quantum cryptography using Key Encapsulation
|
||||
* Mechanisms (KEM).
|
||||
* </p>
|
||||
*
|
||||
* <h2>Key Components</h2>
|
||||
* <ul>
|
||||
* <li>{@link AsymmetricStreamBuilder}: A fluent builder to create
|
||||
* {@link java.io.InputStream} instances that encrypt or decrypt data using
|
||||
* either classic asymmetric ciphers or KEM-based approaches.</li>
|
||||
* <li>{@link AsymmetricContext}: A sealed interface representing asymmetric
|
||||
* cryptographic contexts, with implementations such as
|
||||
* {@link ClassicAsymmetricContext} and {@link KEMAsymmetricContext}.</li>
|
||||
* <li>{@link ClassicAsymmetricEncryptionStream}: An {@code InputStream}
|
||||
* implementation that applies classic block cipher asymmetric
|
||||
* encryption/decryption on streamed data.</li>
|
||||
* <li>{@link KEMAsymmetricContext}: Holds state and cryptographic components
|
||||
* for performing KEM-based encapsulation and decapsulation, supporting
|
||||
* post-quantum secure workflows.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>Concepts</h2>
|
||||
* <p>
|
||||
* <b>Classic block cipher mode:</b> Direct encryption or decryption using an
|
||||
* asymmetric block cipher engine combined with a public or private key.
|
||||
* </p>
|
||||
* <p>
|
||||
* <b>Key Encapsulation Mechanism (KEM) mode:</b> A hybrid approach where a
|
||||
* symmetric key is securely encapsulated or decapsulated using asymmetric
|
||||
* cryptography, and that symmetric key is then used for AES encryption of the
|
||||
* data stream.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Usage</h2>
|
||||
* <p>
|
||||
* Users typically start with {@link AsymmetricStreamBuilder} to configure the
|
||||
* input stream, cryptographic parameters, and mode, then build an encrypting or
|
||||
* decrypting stream to process data.
|
||||
* </p>
|
||||
*
|
||||
* @author Leo Galambos
|
||||
* @since 1.0
|
||||
*/
|
||||
package zeroecho.util.asymmetric;
|
||||
185
lib/src/main/java/zeroecho/util/bc/BcKeyWrapper.java
Normal file
185
lib/src/main/java/zeroecho/util/bc/BcKeyWrapper.java
Normal file
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* 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 zeroecho.util.bc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.NotSerializableException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
|
||||
|
||||
/**
|
||||
* Abstract base class for wrapping asymmetric key parameters from Bouncy
|
||||
* Castle.
|
||||
* <p>
|
||||
* This class provides a common foundation for concrete key wrapper
|
||||
* implementations that encapsulate Bouncy Castle {@link AsymmetricKeyParameter}
|
||||
* instances. It implements {@link Serializable} to signal serializability but
|
||||
* explicitly disables default Java serialization mechanisms by throwing
|
||||
* {@link NotSerializableException} to prevent insecure or unintended
|
||||
* serialization of sensitive key material.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Subclasses must implement {@link #isPrivate()} to indicate whether the
|
||||
* wrapped key is a private key.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Equality and hash code operations are based on the encoded key bytes returned
|
||||
* by {@link #getEncoded()}. By default, {@link #getEncoded()} returns
|
||||
* {@code null}, so subclasses should override it to provide a meaningful key
|
||||
* encoding.
|
||||
* </p>
|
||||
*
|
||||
* <h2>Serialization Note</h2>
|
||||
* <p>
|
||||
* Although this class implements {@link Serializable}, it explicitly disables
|
||||
* serialization and deserialization via the {@code writeObject} and
|
||||
* {@code readObject} methods by throwing {@link NotSerializableException}. This
|
||||
* design prevents accidental exposure of private key material through Java
|
||||
* serialization.
|
||||
* </p>
|
||||
*
|
||||
* @param <T> the specific type of asymmetric key parameters wrapped by this
|
||||
* class, extending {@link AsymmetricKeyParameter}
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public abstract class BcKeyWrapper<T extends AsymmetricKeyParameter> implements Serializable {
|
||||
private static final long serialVersionUID = 1149322274710689183L;
|
||||
/**
|
||||
* The wrapped Bouncy Castle asymmetric key parameters. Marked transient to
|
||||
* prevent default serialization.
|
||||
*/
|
||||
protected transient T params;
|
||||
|
||||
/**
|
||||
* Constructs a new key wrapper instance with the specified key parameters.
|
||||
*
|
||||
* @param params the asymmetric key parameters to wrap
|
||||
*/
|
||||
protected BcKeyWrapper(T params) {
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the wrapped asymmetric key parameters.
|
||||
*
|
||||
* @return the underlying key parameters
|
||||
*/
|
||||
public T getKeyParameters() {
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encoded form of the wrapped key, or {@code null} if encoding is
|
||||
* not supported or not implemented by the subclass.
|
||||
*
|
||||
* @return the encoded key bytes, or {@code null}
|
||||
*/
|
||||
public byte[] getEncoded() { // NOPMD
|
||||
return null; // NOPMD
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the encoding format for the wrapped key.
|
||||
* <p>
|
||||
* For private keys, this returns {@code "PKCS#8"}, indicating the Private-Key
|
||||
* Information Syntax Specification. For public keys, this returns
|
||||
* {@code "X.509"}, indicating the standard for public key certificates.
|
||||
* </p>
|
||||
* <p>
|
||||
* If no encoding format is supported, this method may return {@code null}.
|
||||
* </p>
|
||||
*
|
||||
* @return the name of the encoding format, typically {@code "PKCS#8"} for
|
||||
* private keys and {@code "X.509"} for public keys, or {@code null} if
|
||||
* unsupported
|
||||
*/
|
||||
public String getFormat() {
|
||||
return isPrivate() ? "PKCS#8" : "X.509";
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this key wrapper represents a private key.
|
||||
*
|
||||
* @return {@code true} if this is a private key wrapper; {@code false}
|
||||
* otherwise
|
||||
*/
|
||||
protected abstract boolean isPrivate();
|
||||
|
||||
/**
|
||||
* Prevents default Java serialization by throwing
|
||||
* {@link NotSerializableException}.
|
||||
*
|
||||
* @param out the output stream
|
||||
* @throws IOException always thrown to prevent serialization
|
||||
*/
|
||||
private void writeObject(final ObjectOutputStream out) throws IOException { // NOPMD
|
||||
throw new NotSerializableException(getClass().getName() + " does not support serialization");
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents default Java deserialization by throwing
|
||||
* {@link NotSerializableException}.
|
||||
*
|
||||
* @param in the input stream
|
||||
* @throws IOException always thrown to prevent deserialization
|
||||
*/
|
||||
private void readObject(final ObjectInputStream in) throws IOException { // NOPMD
|
||||
throw new NotSerializableException(getClass().getName() + " does not support deserialization");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof BcKeyWrapper)) {
|
||||
return false;
|
||||
}
|
||||
BcKeyWrapper<?> other = (BcKeyWrapper<?>) obj;
|
||||
return Arrays.equals(this.getEncoded(), other.getEncoded());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(getEncoded());
|
||||
}
|
||||
}
|
||||
110
lib/src/main/java/zeroecho/util/bc/FrodoPrivateKey.java
Normal file
110
lib/src/main/java/zeroecho/util/bc/FrodoPrivateKey.java
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* 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 zeroecho.util.bc;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
|
||||
import org.bouncycastle.pqc.crypto.frodo.FrodoPrivateKeyParameters;
|
||||
|
||||
/**
|
||||
* Represents a Frodo private key, wrapping the underlying
|
||||
* {@link FrodoPrivateKeyParameters} from the Bouncy Castle library.
|
||||
* <p>
|
||||
* This class extends {@link BcKeyWrapper} to provide Frodo-specific key
|
||||
* handling and implements the standard {@link PrivateKey} interface to
|
||||
* integrate with Java Cryptography Architecture (JCA) APIs.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The encoded form of the key is obtained from the underlying
|
||||
* {@link FrodoPrivateKeyParameters#getEncoded()} method.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The {@link #getAlgorithm()} method returns the string {@code "Frodo"} as the
|
||||
* algorithm identifier.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Serialization is explicitly disabled in the superclass to prevent accidental
|
||||
* exposure of sensitive key material.
|
||||
* </p>
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public class FrodoPrivateKey extends BcKeyWrapper<FrodoPrivateKeyParameters> implements PrivateKey {
|
||||
|
||||
private static final long serialVersionUID = -7452299173472996509L;
|
||||
|
||||
/**
|
||||
* Constructs a new Frodo private key wrapper with the specified private key
|
||||
* parameters.
|
||||
*
|
||||
* @param params the Frodo private key parameters to wrap
|
||||
*/
|
||||
public FrodoPrivateKey(FrodoPrivateKeyParameters params) {
|
||||
super(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the standard algorithm name for this key.
|
||||
*
|
||||
* @return the string {@code "Frodo"}
|
||||
*/
|
||||
@Override
|
||||
public String getAlgorithm() {
|
||||
return "Frodo";
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that this key wrapper represents a private key.
|
||||
*
|
||||
* @return {@code true}
|
||||
*/
|
||||
@Override
|
||||
protected boolean isPrivate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encoded form of this private key.
|
||||
*
|
||||
* @return a byte array containing the encoded key
|
||||
*/
|
||||
@Override
|
||||
public byte[] getEncoded() {
|
||||
return params.getEncoded();
|
||||
}
|
||||
}
|
||||
110
lib/src/main/java/zeroecho/util/bc/FrodoPublicKey.java
Normal file
110
lib/src/main/java/zeroecho/util/bc/FrodoPublicKey.java
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* 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 zeroecho.util.bc;
|
||||
|
||||
import java.security.PublicKey;
|
||||
|
||||
import org.bouncycastle.pqc.crypto.frodo.FrodoPublicKeyParameters;
|
||||
|
||||
/**
|
||||
* Represents a Frodo public key, wrapping the underlying
|
||||
* {@link FrodoPublicKeyParameters} from the Bouncy Castle library.
|
||||
* <p>
|
||||
* This class extends {@link BcKeyWrapper} to provide Frodo-specific key
|
||||
* handling and implements the standard {@link PublicKey} interface to integrate
|
||||
* with Java Cryptography Architecture (JCA) APIs.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The encoded form of the key is obtained from the underlying
|
||||
* {@link FrodoPublicKeyParameters#getEncoded()} method.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The {@link #getAlgorithm()} method returns the string {@code "Frodo"} as the
|
||||
* algorithm identifier.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Serialization is explicitly disabled in the superclass to prevent accidental
|
||||
* exposure of sensitive key material.
|
||||
* </p>
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public class FrodoPublicKey extends BcKeyWrapper<FrodoPublicKeyParameters> implements PublicKey {
|
||||
|
||||
private static final long serialVersionUID = -5312503609298272925L;
|
||||
|
||||
/**
|
||||
* Constructs a new Frodo public key wrapper with the specified public key
|
||||
* parameters.
|
||||
*
|
||||
* @param params the Frodo public key parameters to wrap
|
||||
*/
|
||||
public FrodoPublicKey(FrodoPublicKeyParameters params) {
|
||||
super(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the standard algorithm name for this key.
|
||||
*
|
||||
* @return the string {@code "Frodo"}
|
||||
*/
|
||||
@Override
|
||||
public String getAlgorithm() {
|
||||
return "Frodo";
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that this key wrapper represents a public key.
|
||||
*
|
||||
* @return {@code false}
|
||||
*/
|
||||
@Override
|
||||
protected boolean isPrivate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the encoded form of this public key.
|
||||
*
|
||||
* @return a byte array containing the encoded key
|
||||
*/
|
||||
@Override
|
||||
public byte[] getEncoded() {
|
||||
return params.getEncoded();
|
||||
}
|
||||
}
|
||||
94
lib/src/main/java/zeroecho/util/bc/McEliecePrivateKey.java
Normal file
94
lib/src/main/java/zeroecho/util/bc/McEliecePrivateKey.java
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* 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 zeroecho.util.bc;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
|
||||
import org.bouncycastle.pqc.legacy.crypto.mceliece.McEliecePrivateKeyParameters;
|
||||
|
||||
/**
|
||||
* Represents a McEliece private key, wrapping the underlying
|
||||
* {@link McEliecePrivateKeyParameters} from the Bouncy Castle library.
|
||||
* <p>
|
||||
* This class extends {@link BcKeyWrapper} to provide McEliece-specific key
|
||||
* handling and implements the standard {@link PrivateKey} interface to
|
||||
* integrate with Java Cryptography Architecture (JCA) APIs.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The algorithm name returned by {@link #getAlgorithm()} is {@code "McEliece"}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Serialization is explicitly disabled in the superclass to prevent accidental
|
||||
* exposure of sensitive key material.
|
||||
* </p>
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public class McEliecePrivateKey extends BcKeyWrapper<McEliecePrivateKeyParameters> implements PrivateKey {
|
||||
|
||||
private static final long serialVersionUID = -8079222504948456211L;
|
||||
|
||||
/**
|
||||
* Constructs a new McEliece private key wrapper with the specified private key
|
||||
* parameters.
|
||||
*
|
||||
* @param params the McEliece private key parameters to wrap
|
||||
*/
|
||||
public McEliecePrivateKey(McEliecePrivateKeyParameters params) {
|
||||
super(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the standard algorithm name for this key.
|
||||
*
|
||||
* @return the string {@code "McEliece"}
|
||||
*/
|
||||
@Override
|
||||
public String getAlgorithm() {
|
||||
return "McEliece";
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that this key wrapper represents a private key.
|
||||
*
|
||||
* @return {@code true}
|
||||
*/
|
||||
@Override
|
||||
protected boolean isPrivate() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
94
lib/src/main/java/zeroecho/util/bc/McEliecePublicKey.java
Normal file
94
lib/src/main/java/zeroecho/util/bc/McEliecePublicKey.java
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* 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 zeroecho.util.bc;
|
||||
|
||||
import java.security.PublicKey;
|
||||
|
||||
import org.bouncycastle.pqc.legacy.crypto.mceliece.McEliecePublicKeyParameters;
|
||||
|
||||
/**
|
||||
* Represents a McEliece public key, wrapping the underlying
|
||||
* {@link McEliecePublicKeyParameters} from the Bouncy Castle library.
|
||||
* <p>
|
||||
* This class extends {@link BcKeyWrapper} to provide McEliece-specific key
|
||||
* handling and implements the standard {@link PublicKey} interface to integrate
|
||||
* with Java Cryptography Architecture (JCA) APIs.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The algorithm name returned by {@link #getAlgorithm()} is {@code "McEliece"}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Serialization is explicitly disabled in the superclass to prevent accidental
|
||||
* exposure of sensitive key material.
|
||||
* </p>
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public class McEliecePublicKey extends BcKeyWrapper<McEliecePublicKeyParameters> implements PublicKey {
|
||||
|
||||
private static final long serialVersionUID = -2122646985989549694L;
|
||||
|
||||
/**
|
||||
* Constructs a new McEliece public key wrapper with the specified public key
|
||||
* parameters.
|
||||
*
|
||||
* @param params the McEliece public key parameters to wrap
|
||||
*/
|
||||
public McEliecePublicKey(McEliecePublicKeyParameters params) {
|
||||
super(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the standard algorithm name for this key.
|
||||
*
|
||||
* @return the string {@code "McEliece"}
|
||||
*/
|
||||
@Override
|
||||
public String getAlgorithm() {
|
||||
return "McEliece";
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that this key wrapper represents a public key.
|
||||
*
|
||||
* @return {@code false}
|
||||
*/
|
||||
@Override
|
||||
protected boolean isPrivate() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
94
lib/src/main/java/zeroecho/util/bc/NewHopePrivateKey.java
Normal file
94
lib/src/main/java/zeroecho/util/bc/NewHopePrivateKey.java
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* 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 zeroecho.util.bc;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
|
||||
import org.bouncycastle.pqc.crypto.newhope.NHPrivateKeyParameters;
|
||||
|
||||
/**
|
||||
* Represents a NewHope private key, wrapping the underlying
|
||||
* {@link NHPrivateKeyParameters} used for post-quantum key operations.
|
||||
* <p>
|
||||
* This class extends {@link BcKeyWrapper} to provide NewHope-specific key
|
||||
* handling and implements the standard {@link PrivateKey} interface for
|
||||
* compatibility with Java Cryptography Architecture (JCA) APIs.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The algorithm name returned by {@link #getAlgorithm()} is {@code "NewHope"}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Serialization is disabled in the superclass to protect sensitive key material
|
||||
* from accidental exposure.
|
||||
* </p>
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public class NewHopePrivateKey extends BcKeyWrapper<NHPrivateKeyParameters> implements PrivateKey {
|
||||
|
||||
private static final long serialVersionUID = -7098011326074166903L;
|
||||
|
||||
/**
|
||||
* Constructs a new NewHope private key wrapper with the specified private key
|
||||
* parameters.
|
||||
*
|
||||
* @param params the NewHope private key parameters to wrap
|
||||
*/
|
||||
public NewHopePrivateKey(NHPrivateKeyParameters params) {
|
||||
super(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the standard algorithm name for this key.
|
||||
*
|
||||
* @return the string {@code "NewHope"}
|
||||
*/
|
||||
@Override
|
||||
public String getAlgorithm() {
|
||||
return "NewHope";
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that this key wrapper represents a private key.
|
||||
*
|
||||
* @return {@code true}
|
||||
*/
|
||||
@Override
|
||||
protected boolean isPrivate() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
94
lib/src/main/java/zeroecho/util/bc/NewHopePublicKey.java
Normal file
94
lib/src/main/java/zeroecho/util/bc/NewHopePublicKey.java
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* 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 zeroecho.util.bc;
|
||||
|
||||
import java.security.PublicKey;
|
||||
|
||||
import org.bouncycastle.pqc.crypto.newhope.NHPublicKeyParameters;
|
||||
|
||||
/**
|
||||
* Represents a NewHope public key, wrapping the underlying
|
||||
* {@link NHPublicKeyParameters} used for post-quantum key operations.
|
||||
* <p>
|
||||
* This class extends {@link BcKeyWrapper} to provide NewHope-specific key
|
||||
* handling and implements the standard {@link PublicKey} interface for
|
||||
* compatibility with Java Cryptography Architecture (JCA) APIs.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The algorithm name returned by {@link #getAlgorithm()} is {@code "NewHope"}.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Serialization is disabled in the superclass to protect sensitive key material
|
||||
* from accidental exposure.
|
||||
* </p>
|
||||
*
|
||||
* @author Leo Galambos
|
||||
*/
|
||||
public class NewHopePublicKey extends BcKeyWrapper<NHPublicKeyParameters> implements PublicKey {
|
||||
|
||||
private static final long serialVersionUID = -7781131066571667557L;
|
||||
|
||||
/**
|
||||
* Constructs a new NewHope public key wrapper with the specified public key
|
||||
* parameters.
|
||||
*
|
||||
* @param params the NewHope public key parameters to wrap
|
||||
*/
|
||||
public NewHopePublicKey(NHPublicKeyParameters params) {
|
||||
super(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the standard algorithm name for this key.
|
||||
*
|
||||
* @return the string {@code "NewHope"}
|
||||
*/
|
||||
@Override
|
||||
public String getAlgorithm() {
|
||||
return "NewHope";
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that this key wrapper represents a public key.
|
||||
*
|
||||
* @return {@code false}
|
||||
*/
|
||||
@Override
|
||||
protected boolean isPrivate() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
80
lib/src/main/java/zeroecho/util/bc/package-info.java
Normal file
80
lib/src/main/java/zeroecho/util/bc/package-info.java
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
/**
|
||||
* This package provides wrapper implementations for various post-quantum
|
||||
* cryptographic keys, built on top of Bouncy Castle parameter classes.
|
||||
* <p>
|
||||
* The core abstraction is the {@link BcKeyWrapper} class, which encapsulates
|
||||
* the underlying asymmetric key parameters and implements the standard Java
|
||||
* {@link java.security.Key} interfaces (such as
|
||||
* {@link java.security.PrivateKey} and {@link java.security.PublicKey}).
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Specific key types supported include:
|
||||
* <ul>
|
||||
* <li><b>Frodo</b> keys: {@link FrodoPrivateKey}, {@link FrodoPublicKey}</li>
|
||||
* <li><b>McEliece</b> keys: {@link McEliecePrivateKey},
|
||||
* {@link McEliecePublicKey}</li>
|
||||
* <li><b>NewHope</b> keys: {@link NewHopePrivateKey},
|
||||
* {@link NewHopePublicKey}</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* These key wrappers provide methods to retrieve algorithm identifiers, access
|
||||
* encoded key bytes, and support equality and hashing based on the encoded
|
||||
* form.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Note that serialization and deserialization are explicitly disabled to
|
||||
* prevent accidental exposure of sensitive key material.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* All classes in this package adhere to a consistent pattern of wrapping Bouncy
|
||||
* Castle asymmetric key parameter objects and exposing them as standard Java
|
||||
* {@code Key} instances for interoperability with cryptographic frameworks.
|
||||
* </p>
|
||||
*
|
||||
* @see BcKeyWrapper
|
||||
* @see FrodoPrivateKey
|
||||
* @see FrodoPublicKey
|
||||
* @see McEliecePrivateKey
|
||||
* @see McEliecePublicKey
|
||||
* @see NewHopePrivateKey
|
||||
* @see NewHopePublicKey
|
||||
*/
|
||||
package zeroecho.util.bc;
|
||||
55
lib/src/main/java/zeroecho/util/package-info.java
Normal file
55
lib/src/main/java/zeroecho/util/package-info.java
Normal file
@@ -0,0 +1,55 @@
|
||||
/*******************************************************************************
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
/**
|
||||
* Utility classes providing static helper methods and support functions for
|
||||
* cryptographic operations, certificate handling, input/output processing,
|
||||
* secure randomness, and related common tasks.
|
||||
*
|
||||
* This package includes utilities for:
|
||||
* <ul>
|
||||
* <li>Advanced Encryption Standard (AES) key and cipher parameter generation
|
||||
* and manipulation.</li>
|
||||
* <li>X.509 certificate parsing, validation, and encoding.</li>
|
||||
* <li>General input/output stream and file handling helpers.</li>
|
||||
* <li>Secure random number generation and entropy management.</li>
|
||||
* <li>Other miscellaneous static utility methods supporting cryptography and
|
||||
* data processing workflows.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Note: Unless explicitly stated, these utilities are not guaranteed to be
|
||||
* thread-safe.
|
||||
* </p>
|
||||
*/
|
||||
package zeroecho.util;
|
||||
@@ -0,0 +1,232 @@
|
||||
package zeroecho.builder;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.UncheckedIOException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.spec.ECGenParameterSpec;
|
||||
|
||||
import org.bouncycastle.crypto.InvalidCipherTextException;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import conflux.Ctx;
|
||||
import zeroecho.data.DataContent;
|
||||
import zeroecho.util.BouncyCastleActivator;
|
||||
import zeroecho.util.KeyPairAlgorithm;
|
||||
import zeroecho.util.aes.AesCipherType;
|
||||
import zeroecho.util.aes.AesMode;
|
||||
|
||||
class DataContentChainBuilderTest {
|
||||
@BeforeAll
|
||||
public static void setupProvider() {
|
||||
BouncyCastleActivator.init();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void freshContext() {
|
||||
Ctx.INSTANCE.clear();
|
||||
}
|
||||
|
||||
// @Test
|
||||
void testMultiEncryptionDecryptionPipeline() throws Exception {
|
||||
System.out.println("testMultiEncryptionDecryptionPipeline");
|
||||
|
||||
final String originalText = """
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
|
||||
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
""";
|
||||
|
||||
KeyPair kp0 = keyPair("RSA", 2048);
|
||||
KeyPair kp1 = keyPair("RSA", 2048);
|
||||
|
||||
DataContent output = DataContentChainBuilder.encrypt()
|
||||
// input
|
||||
.add(PlainStringBuilder.builder().value(originalText))
|
||||
// encrypt
|
||||
.add(AesBuilder.builder())
|
||||
// ... and save the encryption header
|
||||
.add(MultiRecipientCryptorBuilder.builder().addRecipient(kp0.getPublic()).addRecipient(kp1.getPublic()))
|
||||
.add(AesRandomBuilder.builder().mode(AesMode.AES_256).cipherType(AesCipherType.CBC)).build();
|
||||
|
||||
final byte[] result = output.toBytes();
|
||||
|
||||
Ctx.INSTANCE.clear();
|
||||
|
||||
DataContent output2 = DataContentChainBuilder.decrypt()
|
||||
// input
|
||||
.add(PlainBytesBuilder.builder().bytes(result))
|
||||
// read the encryption header
|
||||
.add(AesRandomBuilder.builder().mode(AesMode.AES_256).cipherType(AesCipherType.CBC))
|
||||
.add(MultiRecipientCryptorBuilder.builder().privateKey(kp1.getPrivate()))
|
||||
// ... for the decryption process
|
||||
.add(AesBuilder.builder())
|
||||
// and obtain the final product
|
||||
.build();
|
||||
|
||||
final String result2 = output2.toText();
|
||||
|
||||
assertEquals(originalText, result2);
|
||||
|
||||
System.out.println("...ok");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testKEMEncryptionDecryptionPipeline() throws Exception {
|
||||
System.out.println("testKEMEncryptionDecryptionPipeline");
|
||||
|
||||
final String originalText = """
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
|
||||
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
""";
|
||||
|
||||
KeyPair kp = KeyPairAlgorithm.KYBER_512.generateKeyPair();
|
||||
|
||||
DataContent output = DataContentChainBuilder.encrypt()
|
||||
// input
|
||||
.add(PlainStringBuilder.builder().value(originalText))
|
||||
// encrypt
|
||||
.add(AesBuilder.builder())
|
||||
// ... and save the encryption header
|
||||
.add(KEMAesParametersBuilder.builder().withKey(kp.getPublic()))
|
||||
// build!
|
||||
.build();
|
||||
|
||||
final byte[] result = output.toBytes();
|
||||
|
||||
Ctx.INSTANCE.clear();
|
||||
|
||||
DataContent output2 = DataContentChainBuilder.decrypt()
|
||||
// input
|
||||
.add(PlainBytesBuilder.builder().bytes(result))
|
||||
// read the encryption header
|
||||
.add(KEMAesParametersBuilder.builder().withKey(kp.getPrivate()))
|
||||
// ... for the decryption process
|
||||
.add(AesBuilder.builder())
|
||||
// and obtain the final product
|
||||
.build();
|
||||
|
||||
final String result2 = output2.toText();
|
||||
|
||||
assertEquals(originalText, result2);
|
||||
|
||||
System.out.println("...ok");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEncryptionDecryptionPipeline() throws Exception {
|
||||
System.out.println("testEncryptionDecryptionPipeline");
|
||||
|
||||
final String originalText = """
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
|
||||
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
""";
|
||||
final String userPassword = "simple_password";
|
||||
|
||||
DataContent output = DataContentChainBuilder.encrypt()
|
||||
// input
|
||||
.add(PlainStringBuilder.builder().value(originalText))
|
||||
// encrypt
|
||||
.add(AesBuilder.builder())
|
||||
// ... and save the encryption header
|
||||
.add(DerivedAesParametersBuilder.builder().password(userPassword)).build();
|
||||
|
||||
final byte[] result = output.toBytes();
|
||||
|
||||
Ctx.INSTANCE.clear();
|
||||
|
||||
DataContent output2 = DataContentChainBuilder.decrypt()
|
||||
// input
|
||||
.add(PlainBytesBuilder.builder().bytes(result))
|
||||
// read the encryption header
|
||||
.add(DerivedAesParametersBuilder.builder().password(userPassword))
|
||||
// ... for the decryption process
|
||||
.add(AesBuilder.builder())
|
||||
// and obtain the final product
|
||||
.build();
|
||||
|
||||
final String result2 = output2.toText();
|
||||
|
||||
assertEquals(originalText, result2);
|
||||
|
||||
System.out.println("...ok");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEncryptionDecryptionPipelineNegative() throws Exception {
|
||||
System.out.println("testEncryptionDecryptionPipeline-negative");
|
||||
|
||||
final String originalText = """
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
|
||||
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
""";
|
||||
final String userPassword = "simple_password";
|
||||
|
||||
DataContent output = DataContentChainBuilder.encrypt()
|
||||
// input
|
||||
.add(PlainStringBuilder.builder().value(originalText))
|
||||
// encrypt
|
||||
.add(AesBuilder.builder())
|
||||
// ... and save the encryption header
|
||||
.add(DerivedAesParametersBuilder.builder().password(userPassword)).build();
|
||||
|
||||
final byte[] result = output.toBytes();
|
||||
|
||||
Ctx.INSTANCE.clear();
|
||||
|
||||
DataContent output2 = DataContentChainBuilder.decrypt()
|
||||
// input
|
||||
.add(PlainBytesBuilder.builder().bytes(result))
|
||||
// read the encryption header
|
||||
.add(DerivedAesParametersBuilder.builder().password(userPassword + userPassword))
|
||||
// ... for the decryption process
|
||||
.add(AesBuilder.builder())
|
||||
// and obtain the final product
|
||||
.build();
|
||||
|
||||
UncheckedIOException thrown = assertThrowsExactly(UncheckedIOException.class, () -> {
|
||||
output2.toText();
|
||||
});
|
||||
|
||||
assertTrue(hasCause(thrown, InvalidCipherTextException.class),
|
||||
"Expected cause chain to include InvalidCipherTextException");
|
||||
|
||||
System.out.println("...ok");
|
||||
}
|
||||
|
||||
private boolean hasCause(Throwable throwable, Class<? extends Throwable> causeClass) {
|
||||
Throwable current = throwable;
|
||||
while (current != null) {
|
||||
if (causeClass.isInstance(current)) {
|
||||
return true;
|
||||
}
|
||||
current = current.getCause();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private KeyPair keyPair(String algorithm, int size) throws Exception {
|
||||
String label = algorithm + "-" + size;
|
||||
System.out.println("Testing " + label);
|
||||
KeyPairGenerator generator = KeyPairGenerator.getInstance(algorithm, BouncyCastleProvider.PROVIDER_NAME);
|
||||
if (algorithm.equals("EC")) {
|
||||
generator.initialize(new ECGenParameterSpec("secp" + size + "r1"));
|
||||
} else {
|
||||
generator.initialize(size);
|
||||
}
|
||||
return generator.generateKeyPair();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* 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 zeroecho.builder;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.security.KeyPair;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
|
||||
import conflux.Ctx;
|
||||
import zeroecho.data.DataContent;
|
||||
import zeroecho.util.BouncyCastleActivator;
|
||||
import zeroecho.util.KeyPairAlgorithm;
|
||||
|
||||
class KEMAesParametersBuilderTest {
|
||||
@BeforeAll
|
||||
public static void setupProvider() {
|
||||
BouncyCastleActivator.init();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void freshContext() {
|
||||
Ctx.INSTANCE.clear();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(KeyPairAlgorithm.class)
|
||||
void testKEMEncryptionDecryptionPipeline(final KeyPairAlgorithm algorithm) throws Exception {
|
||||
System.out.println("Testing algorithm: " + algorithm);
|
||||
|
||||
if (algorithm == KeyPairAlgorithm.ELGAMAL_2048) {
|
||||
System.out.println("...too slow key pair generation, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
final String originalText = """
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
|
||||
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
|
||||
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
""";
|
||||
|
||||
// --- Generate key pair for this algorithm ---
|
||||
KeyPair kp = algorithm.generateKeyPair();
|
||||
|
||||
try {
|
||||
// --- Encryption pipeline ---
|
||||
DataContent encryptedContent = DataContentChainBuilder.encrypt()
|
||||
.add(PlainStringBuilder.builder().value(originalText)).add(AesBuilder.builder())
|
||||
.add(KEMAesParametersBuilder.builder().withKey(kp.getPublic())).build();
|
||||
|
||||
byte[] cipherBytes = encryptedContent.toBytes();
|
||||
|
||||
// --- Clear context before decryption ---
|
||||
Ctx.INSTANCE.clear();
|
||||
|
||||
// --- Decryption pipeline ---
|
||||
DataContent decryptedContent = DataContentChainBuilder.decrypt()
|
||||
.add(PlainBytesBuilder.builder().bytes(cipherBytes))
|
||||
.add(KEMAesParametersBuilder.builder().withKey(kp.getPrivate())).add(AesBuilder.builder()).build();
|
||||
|
||||
final String decryptedText = decryptedContent.toText();
|
||||
|
||||
// --- Verify ---
|
||||
assertEquals(originalText, decryptedText);
|
||||
System.out.println("...algorithm " + algorithm + " passed.");
|
||||
|
||||
} catch (IllegalArgumentException | NullPointerException e) {
|
||||
System.out.println("...algorithm " + algorithm
|
||||
+ " cannot be submitted to the current KEMAesParameters impl: " + e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
75
lib/src/test/java/zeroecho/covert/TextualCodecTest.java
Normal file
75
lib/src/test/java/zeroecho/covert/TextualCodecTest.java
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* 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 zeroecho.covert;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class TextualCodecTest {
|
||||
|
||||
@Test
|
||||
void testGenerate100CharacterText() {
|
||||
System.out.println("testGenerate100CharacterText");
|
||||
|
||||
TextualCodec.Generator generator = TextualCodec.Generator.EN;
|
||||
String result = generator.getText(100);
|
||||
|
||||
System.out.println("...generated text: " + result);
|
||||
|
||||
assertNotNull(result, "Generated text should not be null");
|
||||
assertEquals(100, result.length(), "Generated text should have 100 characters");
|
||||
|
||||
// Ensure all characters are from the expected alphabet
|
||||
for (char c : result.toCharArray()) {
|
||||
assertTrue(TextualCodec.Generator.ENGLISH.containsKey(c),
|
||||
"Generated character '" + c + "' is not part of the English frequency table");
|
||||
}
|
||||
|
||||
// Optional: Analyze distribution for debugging
|
||||
Map<Character, Integer> histogram = new HashMap<>();
|
||||
for (char c : result.toCharArray()) {
|
||||
histogram.merge(c, 1, Integer::sum);
|
||||
}
|
||||
|
||||
System.out.println("...character distribution: " + histogram);
|
||||
System.out.println("...ok");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* 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 zeroecho.covert.jpeg;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import zeroecho.util.aes.AesCipherType;
|
||||
import zeroecho.util.aes.AesMode;
|
||||
import zeroecho.util.aes.AesParameters;
|
||||
import zeroecho.util.aes.AesSupport;
|
||||
|
||||
class JpegExifIntegrationTest {
|
||||
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
|
||||
@Test
|
||||
void testEncryptEmbedExtractDecrypt() throws Exception {
|
||||
System.out.println("testEncryptEmbedExtractDecrypt");
|
||||
|
||||
final String inputText = "Hello, this is a secret message to embed in JPEG.";
|
||||
final byte[] inputBytes = inputText.getBytes(StandardCharsets.UTF_8);
|
||||
final byte[] aad = { 1, 2, 3 };
|
||||
|
||||
// AES config
|
||||
AesCipherType cipher = AesCipherType.GCM;
|
||||
AesMode mode = AesMode.AES_256;
|
||||
AesParameters params = AesSupport.deriveKeyAndIv("one-two-three", AesSupport.KEY_ITERATIONS, mode, aad, cipher);
|
||||
KeyParameter key = params.key();
|
||||
|
||||
// Encrypt
|
||||
ByteArrayOutputStream encryptedOut = new ByteArrayOutputStream();
|
||||
try (OutputStream aesOut = AesSupport.encrypt(key, encryptedOut, params.iv(), cipher, aad)) {
|
||||
aesOut.write(inputBytes);
|
||||
}
|
||||
byte[] encryptedBytes = Base64.getEncoder().encode(encryptedOut.toByteArray());
|
||||
|
||||
// Create temporary JPEG
|
||||
Path jpegOriginal = getResourcePath("test.jpg");
|
||||
|
||||
// Embed encrypted data
|
||||
Path jpegEmbedded = tempDir.resolve("stego.jpg"); // Files.createTempFile("stego", ".jpg");
|
||||
try (InputStream payloadInput = new ByteArrayInputStream(encryptedBytes);
|
||||
OutputStream jpegOutput = Files.newOutputStream(jpegEmbedded)) {
|
||||
JpegExifEmbedder embedder = new JpegExifEmbedder();
|
||||
embedder.embed(jpegOriginal, payloadInput, jpegOutput);
|
||||
}
|
||||
|
||||
System.out.println("...encrypted length=" + encryptedBytes.length + " " + Arrays.toString(encryptedBytes));
|
||||
|
||||
// Extract payload
|
||||
ByteArrayOutputStream extractedEncrypted = new ByteArrayOutputStream();
|
||||
new JpegExifExtractor().extract(jpegEmbedded, extractedEncrypted);
|
||||
|
||||
byte[] extracted = extractedEncrypted.toByteArray();
|
||||
System.out.println("...extracted length=" + extracted.length + " " + Arrays.toString(extracted));
|
||||
|
||||
byte[] extractedEncryptedBytes = Base64.getDecoder().decode(extracted);
|
||||
|
||||
// Decrypt
|
||||
String decrypted;
|
||||
try (InputStream aesIn = AesSupport.decrypt(key, new ByteArrayInputStream(extractedEncryptedBytes), params.iv(),
|
||||
cipher, aad)) {
|
||||
byte[] decryptedBytes = aesIn.readAllBytes();
|
||||
decrypted = new String(decryptedBytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
assertEquals(inputText, decrypted);
|
||||
|
||||
System.out.println("...ok");
|
||||
}
|
||||
|
||||
private Path getResourcePath(String resource) throws URISyntaxException {
|
||||
URL url = getClass().getClassLoader().getResource(resource);
|
||||
if (url == null) {
|
||||
throw new IllegalArgumentException("Missing resource: " + resource);
|
||||
}
|
||||
return Paths.get(url.toURI());
|
||||
}
|
||||
}
|
||||
113
lib/src/test/java/zeroecho/data/output/Base64StreamTest.java
Normal file
113
lib/src/test/java/zeroecho/data/output/Base64StreamTest.java
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* 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 zeroecho.data.output;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class Base64StreamTest {
|
||||
|
||||
@Test
|
||||
void testEncodedOutputFrom4KBInput() throws Exception {
|
||||
System.out.println("testEncodedOutputFrom4KBInput");
|
||||
|
||||
byte[] inputBytes = new byte[4096];
|
||||
new SecureRandom().nextBytes(inputBytes); // random 4KB data
|
||||
|
||||
byte[] inputEncoded = Base64.getEncoder().encode(inputBytes);
|
||||
|
||||
System.out.println("...encoded length should be: " + inputEncoded.length);
|
||||
|
||||
int lineLength = 76;
|
||||
|
||||
String[] prefixes = { "echo ", null };
|
||||
byte[][] lineSeparators = { " >> file.tmp\r\n".getBytes(StandardCharsets.US_ASCII),
|
||||
"\n".getBytes(StandardCharsets.US_ASCII), null };
|
||||
|
||||
for (String prefix : prefixes) {
|
||||
for (byte[] lineSep : lineSeparators) {
|
||||
System.out.printf("...***** testing with prefix=%s and lineSep=%s%n",
|
||||
prefix == null ? "null" : "\"" + prefix + "\"",
|
||||
lineSep == null ? "null" : "\"" + new String(lineSep, StandardCharsets.US_ASCII) + "\"");
|
||||
|
||||
try (InputStream base64Stream = new Base64Stream(new ByteArrayInputStream(inputBytes),
|
||||
prefix == null ? null : prefix.getBytes(StandardCharsets.US_ASCII), lineLength, lineSep)) {
|
||||
|
||||
String encodedOutput = new String(base64Stream.readAllBytes(), StandardCharsets.US_ASCII);
|
||||
System.out.println(encodedOutput);
|
||||
|
||||
String[] lines = encodedOutput.split("\n");
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
int space = lines[i].indexOf(' ');
|
||||
if (space > 0) {
|
||||
lines[i] = lines[i].split(" ")[prefix == null ? 0 : 1];
|
||||
}
|
||||
}
|
||||
|
||||
if (lineSep != null) {
|
||||
for (String line : lines) {
|
||||
assertTrue(line.length() <= lineLength, "Line exceeds max length: " + line.length());
|
||||
}
|
||||
}
|
||||
|
||||
String joined = String.join("", lines);
|
||||
|
||||
System.out.println("...lines: " + lines.length);
|
||||
System.out.println("......encoded length: " + joined.length());
|
||||
|
||||
if (lineSep == null && prefix != null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
byte[] decoded = Base64.getDecoder().decode(joined);
|
||||
|
||||
System.out.println("...decoded length: " + decoded.length);
|
||||
|
||||
assertArrayEquals(inputBytes, decoded, "Decoded content does not match original input");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("...ok");
|
||||
}
|
||||
}
|
||||
136
lib/src/test/java/zeroecho/data/processing/AesEncryptorTest.java
Normal file
136
lib/src/test/java/zeroecho/data/processing/AesEncryptorTest.java
Normal file
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* 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 zeroecho.data.processing;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import conflux.Ctx;
|
||||
import zeroecho.util.BouncyCastleActivator;
|
||||
import zeroecho.util.SymmetricStreamBuilder;
|
||||
import zeroecho.util.aes.AesCipherType;
|
||||
import zeroecho.util.aes.AesMode;
|
||||
import zeroecho.util.aes.AesParameters;
|
||||
import zeroecho.util.aes.AesSupport;
|
||||
|
||||
class AesEncryptorTest {
|
||||
@BeforeAll
|
||||
public static void setupProvider() {
|
||||
BouncyCastleActivator.init();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void freshContext() {
|
||||
Ctx.INSTANCE.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a repeating plaintext message longer than BUF_SIZE.
|
||||
*/
|
||||
private String generateLongPlaintext() {
|
||||
String phrase = "This is a long plaintext message for AES encryption test. ";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (sb.length() <= 3 * SymmetricStreamBuilder.BUF_SIZE) {
|
||||
sb.append(phrase);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link zeroecho.data.processing.AesEncryptor#getStream()}.
|
||||
*/
|
||||
@Test
|
||||
void testGetStream() throws Exception {
|
||||
System.out.println("testGetStream");
|
||||
|
||||
final String input = generateLongPlaintext();
|
||||
final byte[] aad = { 1, 2, 3 };
|
||||
|
||||
for (AesMode mode : AesMode.values()) {
|
||||
for (AesCipherType cipher : AesCipherType.values()) {
|
||||
System.out.println("..." + mode.toString() + "/" + cipher.getId());
|
||||
// Generate AES key and IV
|
||||
final AesParameters params = AesSupport.generateKeyAndIV(mode, cipher, aad);
|
||||
final AesEncryptor aes = new AesEncryptor(params);
|
||||
|
||||
KeyParameter key = params.key();
|
||||
byte[] iv = params.iv();
|
||||
System.out.printf("...key: %s%n", Arrays.toString(key.getKey()));
|
||||
System.out.printf("...iv: %s%n", Arrays.toString(iv));
|
||||
|
||||
// Encrypt to ByteArrayOutputStream using BC core streams
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
OutputStream out = AesSupport.encrypt(key, baos, iv, cipher, aad);
|
||||
System.out.printf("...before write() encrypted length: %d%n", baos.size());
|
||||
out.write(input.getBytes(StandardCharsets.UTF_8));
|
||||
System.out.printf("...before flush() encrypted length: %d%n", baos.size());
|
||||
out.flush();
|
||||
System.out.printf("...before close() encrypted length: %d%n", baos.size());
|
||||
out.close();
|
||||
System.out.printf("...input length: %d, encrypted length: %d%n", input.length(), baos.size());
|
||||
|
||||
// Encrypt using SymmetricStreamBuilder
|
||||
aes.setInput(new PlainBytes(input.getBytes(StandardCharsets.UTF_8)));
|
||||
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
|
||||
try (InputStream encrypted = aes.getStream()) {
|
||||
System.out.println("... --> stream: " + encrypted.getClass().getName());
|
||||
encrypted.transferTo(baos2);
|
||||
}
|
||||
byte[] encryptedBytes = baos2.toByteArray();
|
||||
System.out.printf("... --> input length: %d, encrypted length: %d%n", input.length(),
|
||||
encryptedBytes.length);
|
||||
|
||||
// Byte-by-byte comparison
|
||||
byte[] encryptedViaLowLevelStreamBytes = baos.toByteArray();
|
||||
|
||||
assertArrayEquals(encryptedViaLowLevelStreamBytes, encryptedBytes, "Encrypted arrays should match");
|
||||
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("...ok");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* 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 zeroecho.data.processing;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyPair;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledForJreRange;
|
||||
import org.junit.jupiter.api.condition.JRE;
|
||||
|
||||
import zeroecho.util.BouncyCastleActivator;
|
||||
import zeroecho.util.KeyPairAlgorithm;
|
||||
import zeroecho.util.KeySupport;
|
||||
import zeroecho.util.RandomSupport;
|
||||
import zeroecho.util.aes.AesCipherType;
|
||||
import zeroecho.util.asymmetric.AsymmetricContext;
|
||||
import zeroecho.util.asymmetric.KEMAsymmetricContext;
|
||||
|
||||
@EnabledForJreRange(min = JRE.JAVA_21)
|
||||
class KEMEncryptorDecryptorTest {
|
||||
@BeforeAll
|
||||
public static void setupProvider() {
|
||||
BouncyCastleActivator.init();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAllKemAlgorithms() throws Exception {
|
||||
System.out.println("testAllKemAlgorithms");
|
||||
byte[] testData = RandomSupport.generateRandom(2048);
|
||||
System.out.println("...Initial input size: " + testData.length + " bytes");
|
||||
|
||||
for (KeyPairAlgorithm algo : KeyPairAlgorithm.values()) {
|
||||
KeyPair kp;
|
||||
try {
|
||||
System.out.println("...Generating key for algo: " + algo);
|
||||
if (algo == KeyPairAlgorithm.ELGAMAL_2048) {
|
||||
System.out.println("...Algo " + algo + " generation is slow, skipping");
|
||||
continue;
|
||||
}
|
||||
kp = algo.generateKeyPair();
|
||||
} catch (Exception e) {
|
||||
System.out.println("...Skipping algo " + algo + " (not supported: " + e.getMessage() + ")");
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
AsymmetricContext pubCtx = KeySupport.fromKey(kp.getPublic());
|
||||
AsymmetricContext privCtx = KeySupport.fromKey(kp.getPrivate());
|
||||
|
||||
if (!(pubCtx instanceof KEMAsymmetricContext) || !(privCtx instanceof KEMAsymmetricContext)) {
|
||||
System.out.println("...Skipping algo " + algo + " (not a KEM algorithm)");
|
||||
continue;
|
||||
}
|
||||
|
||||
KEMAsymmetricContext sender = (KEMAsymmetricContext) pubCtx;
|
||||
KEMAsymmetricContext receiver = (KEMAsymmetricContext) privCtx;
|
||||
|
||||
if (sender.generator() == null || receiver.extractor() == null) {
|
||||
System.out.println("...Skipping algo " + algo + " (cannot provide KEM's generator/extractor)");
|
||||
continue;
|
||||
}
|
||||
|
||||
for (AesCipherType cipherType : AesCipherType.values()) {
|
||||
for (byte[] aad : new byte[][] { null, new byte[] { 1, 2, 3 } }) {
|
||||
runEncryptionDecryptionCycle(sender, receiver, cipherType, aad, testData, algo);
|
||||
}
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
System.out.println("...Skipping algo " + algo + " (cannot provide AsymmetricContext)");
|
||||
}
|
||||
}
|
||||
System.out.println("...ok");
|
||||
}
|
||||
|
||||
private void runEncryptionDecryptionCycle(KEMAsymmetricContext sender, KEMAsymmetricContext receiver,
|
||||
AesCipherType cipherType, byte[] aad, byte[] testData, KeyPairAlgorithm algo) throws IOException {
|
||||
|
||||
System.out.printf("...Algo=%s, Cipher=%s, AAD=%s%n", algo, cipherType,
|
||||
(aad == null ? "null" : Arrays.toString(aad)));
|
||||
|
||||
// Encryption
|
||||
KEMEncryptor encryptor = new KEMEncryptor(sender, cipherType, aad);
|
||||
encryptor.setInput(() -> new ByteArrayInputStream(testData));
|
||||
byte[] encryptedBytes = encryptor.toBytes();
|
||||
System.out.println("......Encrypted size: " + encryptedBytes.length + " bytes");
|
||||
|
||||
// Decryption
|
||||
KEMDecryptor decryptor = new KEMDecryptor(receiver, cipherType, aad);
|
||||
decryptor.setInput(() -> new ByteArrayInputStream(encryptedBytes));
|
||||
byte[] decrypted = decryptor.toBytes();
|
||||
System.out.println("......Decrypted size: " + decrypted.length + " bytes");
|
||||
|
||||
// Validation
|
||||
assertArrayEquals(testData, decrypted, () -> "Decryption mismatch for algo=" + algo + ", cipher=" + cipherType
|
||||
+ ", aad=" + Arrays.toString(aad));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* 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 zeroecho.data.processing;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import conflux.Ctx;
|
||||
import zeroecho.util.BouncyCastleActivator;
|
||||
import zeroecho.util.SymmetricStreamBuilder;
|
||||
import zeroecho.util.aes.AesCipherType;
|
||||
import zeroecho.util.aes.AesMode;
|
||||
import zeroecho.util.aes.AesSupport;
|
||||
|
||||
class PasswordBasedAesEncryptorTest {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(PasswordBasedAesEncryptorTest.class.getName());
|
||||
|
||||
@BeforeAll
|
||||
public static void setupProvider() {
|
||||
BouncyCastleActivator.init();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void freshContext() {
|
||||
Ctx.INSTANCE.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a long plaintext message longer than BUF_SIZE for stress testing.
|
||||
*/
|
||||
private String generateLongPlaintext() {
|
||||
String phrase = "This is a long plaintext message for AES encryption test. ";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (sb.length() <= 3 * SymmetricStreamBuilder.BUF_SIZE) {
|
||||
sb.append(phrase);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests encryption and decryption with PasswordBasedAesEncryptor and
|
||||
* PasswordBasedAesDecryptor.
|
||||
*/
|
||||
@Test
|
||||
void testEncryptDecrypt() throws Exception {
|
||||
System.out.println("testEncryptDecrypt");
|
||||
|
||||
final String input = generateLongPlaintext();
|
||||
final String password = "StrongPassword123!";
|
||||
final int iterations = AesSupport.KEY_ITERATIONS;
|
||||
final byte[] aad = { 1, 2, 3 };
|
||||
|
||||
for (AesMode mode : AesMode.values()) {
|
||||
for (AesCipherType cipher : AesCipherType.values()) {
|
||||
System.out.printf("...Testing mode: %s, cipher: %s%n", mode, cipher);
|
||||
|
||||
final PasswordBasedAesEncryptor encryptor = new PasswordBasedAesEncryptor(password, iterations, aad,
|
||||
mode, cipher);
|
||||
encryptor.setInput(new PlainBytes(input.getBytes(StandardCharsets.UTF_8)));
|
||||
|
||||
byte[] encryptedBytes;
|
||||
try (InputStream encryptedStream = encryptor.getStream()) {
|
||||
encryptedBytes = encryptedStream.readAllBytes();
|
||||
}
|
||||
|
||||
LOG.log(Level.INFO, "Encrypted length: {0}", encryptedBytes.length);
|
||||
|
||||
final PasswordBasedAesDecryptor decryptor = new PasswordBasedAesDecryptor(password, aad, mode, cipher);
|
||||
decryptor.setInput(new PlainBytes(encryptedBytes));
|
||||
|
||||
byte[] decryptedBytes;
|
||||
try (InputStream decryptedStream = decryptor.getStream()) {
|
||||
decryptedBytes = decryptedStream.readAllBytes();
|
||||
}
|
||||
|
||||
String decryptedText = new String(decryptedBytes, StandardCharsets.UTF_8);
|
||||
assertEquals(input, decryptedText,
|
||||
"Decrypted text mismatch for mode " + mode + " and cipher " + cipher);
|
||||
|
||||
Ctx.INSTANCE.clear();
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("...ok");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testInvalidParameters() {
|
||||
System.out.println("testInvalidParameters");
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
new PasswordBasedAesEncryptor(null, 1000, AesMode.AES_128);
|
||||
}, "Expected IllegalArgumentException for null password");
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
new PasswordBasedAesEncryptor("password", -1, AesMode.AES_128);
|
||||
}, "Expected IllegalArgumentException for negative iterations");
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> {
|
||||
new PasswordBasedAesEncryptor("password", 1000, null);
|
||||
}, "Expected IllegalArgumentException for null AES mode");
|
||||
|
||||
System.out.println("...ok");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
/**
|
||||
* 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 zeroecho.data.processing;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.SequenceInputStream;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import conflux.Ctx;
|
||||
import zeroecho.data.EncryptedContent;
|
||||
import zeroecho.data.PlainContent;
|
||||
import zeroecho.util.BouncyCastleActivator;
|
||||
import zeroecho.util.KeyPairAlgorithm;
|
||||
import zeroecho.util.RandomSupport;
|
||||
|
||||
class SecretMultiRecipientCryptorTest {
|
||||
@BeforeAll
|
||||
public static void setupProvider() {
|
||||
BouncyCastleActivator.init();
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void freshContext() {
|
||||
Ctx.INSTANCE.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() throws Exception {
|
||||
System.out.println("multiRecipientCryptor-header");
|
||||
|
||||
SecureRandom rnd = new SecureRandom();
|
||||
|
||||
byte[] secret = RandomSupport.generateRandom(128);
|
||||
|
||||
System.out.println("...Demo users keys generation");
|
||||
|
||||
PublicKey pub[] = new PublicKey[16];
|
||||
PrivateKey priv[] = new PrivateKey[16];
|
||||
|
||||
final KeyPairAlgorithm algo[] = new KeyPairAlgorithm[16];
|
||||
|
||||
// prepare various keys (pub+priv)
|
||||
for (int i = 0; i < pub.length; i++) {
|
||||
algo[i] = switch (rnd.nextInt(6)) {
|
||||
case 0 -> KeyPairAlgorithm.RSA_1024;
|
||||
case 1 -> KeyPairAlgorithm.RSA_2048;
|
||||
case 2 -> KeyPairAlgorithm.KYBER_512;
|
||||
case 3 -> KeyPairAlgorithm.KYBER_768;
|
||||
case 4 -> KeyPairAlgorithm.ELGAMAL_1024;
|
||||
case 5 -> KeyPairAlgorithm.ELGAMAL_512;
|
||||
default -> KeyPairAlgorithm.RSA_4096;
|
||||
};
|
||||
|
||||
System.out.println("...key " + algo[i]);
|
||||
|
||||
final KeyPair kp = algo[i].generateKeyPair();
|
||||
|
||||
pub[i] = kp.getPublic();
|
||||
priv[i] = kp.getPrivate();
|
||||
}
|
||||
|
||||
System.out.println("...Decoys keys generation");
|
||||
|
||||
PublicKey pubDecoy[] = new PublicKey[16];
|
||||
PrivateKey privDecoy[] = new PrivateKey[16];
|
||||
|
||||
final KeyPairAlgorithm algo_decoy[] = new KeyPairAlgorithm[16];
|
||||
|
||||
// prepare various keys (pub+priv)
|
||||
for (int i = 0; i < pubDecoy.length; i++) {
|
||||
algo_decoy[i] = switch (rnd.nextInt(6)) {
|
||||
case 0 -> KeyPairAlgorithm.RSA_1024;
|
||||
case 1 -> KeyPairAlgorithm.RSA_2048;
|
||||
case 2 -> KeyPairAlgorithm.KYBER_512;
|
||||
case 3 -> KeyPairAlgorithm.KYBER_768;
|
||||
case 4 -> KeyPairAlgorithm.ELGAMAL_1024;
|
||||
case 5 -> KeyPairAlgorithm.ELGAMAL_512;
|
||||
default -> KeyPairAlgorithm.RSA_4096;
|
||||
};
|
||||
|
||||
System.out.println("...key " + algo_decoy[i]);
|
||||
|
||||
final KeyPair kp = algo_decoy[i].generateKeyPair();
|
||||
|
||||
pubDecoy[i] = kp.getPublic();
|
||||
privDecoy[i] = kp.getPrivate();
|
||||
}
|
||||
|
||||
System.out.println("...keys generated");
|
||||
|
||||
// simulate an empty data stream with the header
|
||||
SecretMultiRecipientCryptor writer = new SecretMultiRecipientCryptor(pub, pubDecoy);
|
||||
writer.setInput(new EncryptedContent() {
|
||||
@Override
|
||||
public InputStream getStream() throws IOException {
|
||||
return InputStream.nullInputStream();
|
||||
}
|
||||
});
|
||||
Ctx.INSTANCE.put(SecretMultiRecipientCryptor.SECRET, secret);
|
||||
// generate a header for us
|
||||
ByteArrayOutputStream headerOut = new ByteArrayOutputStream();
|
||||
writer.getStream().transferTo(headerOut);
|
||||
byte header[] = headerOut.toByteArray();
|
||||
|
||||
Ctx.INSTANCE.clear();
|
||||
|
||||
// test that all keys can reach the secret in the header
|
||||
System.out.println("...can all recipients access the shared secret?");
|
||||
|
||||
for (int i = 0; i < pub.length; i++) {
|
||||
System.out.printf("... recipient #%d %s%n", i, algo[i].toString());
|
||||
|
||||
SecretMultiRecipientCryptor reader = new SecretMultiRecipientCryptor(priv[i]);
|
||||
reader.setInput(new PlainContent() {
|
||||
@Override
|
||||
public InputStream getStream() throws IOException {
|
||||
return new SequenceInputStream(new ByteArrayInputStream(header), InputStream.nullInputStream());
|
||||
}
|
||||
});
|
||||
// find the secret in a header
|
||||
ByteArrayOutputStream headerIn = new ByteArrayOutputStream();
|
||||
reader.getStream().transferTo(headerIn);
|
||||
|
||||
assertEquals(0, headerIn.size()); // there is nothing else than the header
|
||||
assertArrayEquals(secret, Ctx.INSTANCE.get(SecretMultiRecipientCryptor.SECRET));
|
||||
}
|
||||
|
||||
System.out.println("... --> yes!");
|
||||
|
||||
Ctx.INSTANCE.clear();
|
||||
|
||||
// test that all decoy keys can reach the decoy secret in the header
|
||||
System.out.println("...cannot all decoys access the shared secret?");
|
||||
|
||||
for (int i = 0; i < pubDecoy.length; i++) {
|
||||
System.out.printf("... decoy #%d %s%n", i, algo_decoy[i].toString());
|
||||
|
||||
SecretMultiRecipientCryptor reader = new SecretMultiRecipientCryptor(privDecoy[i]);
|
||||
reader.setInput(new PlainContent() {
|
||||
@Override
|
||||
public InputStream getStream() throws IOException {
|
||||
return new SequenceInputStream(new ByteArrayInputStream(header), InputStream.nullInputStream());
|
||||
}
|
||||
});
|
||||
// find the secret in a header
|
||||
ByteArrayOutputStream headerIn = new ByteArrayOutputStream();
|
||||
reader.getStream().transferTo(headerIn);
|
||||
|
||||
assertEquals(0, headerIn.size()); // there is nothing else than the header
|
||||
assertFalse(Arrays.equals(secret, Ctx.INSTANCE.get(SecretMultiRecipientCryptor.SECRET)));
|
||||
}
|
||||
|
||||
System.out.println("... --> yes!");
|
||||
|
||||
System.out.println("...ok");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*******************************************************************************
|
||||
* 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 zeroecho.data.processing;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class SecretPasswordTest {
|
||||
@Test
|
||||
void testGetPlainText() {
|
||||
|
||||
int len = 12;
|
||||
|
||||
System.out.printf("generating password, %d characters...%n", len);
|
||||
|
||||
SecretPassword sp = new SecretPassword(len);
|
||||
|
||||
System.out.printf("...string %s (length %d)%n", sp.toText(), sp.toText().length());
|
||||
|
||||
assertEquals(len, sp.toText().length());
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user