diff --git a/sechub-commons-core/build.gradle b/sechub-commons-core/build.gradle
index c3a3d14c85..f373ca021d 100644
--- a/sechub-commons-core/build.gradle
+++ b/sechub-commons-core/build.gradle
@@ -3,6 +3,8 @@ dependencies {
api spring_boot_dependency.slf4j_api
+ implementation 'org.bouncycastle:bcprov-jdk18on:1.73'
+
testImplementation spring_boot_dependency.junit_jupiter
testImplementation spring_boot_dependency.mockito_core
diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/CryptoAccess.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/CryptoAccess.java
index 5be0e7a846..8ce34d1039 100644
--- a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/CryptoAccess.java
+++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/CryptoAccess.java
@@ -10,10 +10,10 @@
import javax.crypto.SecretKey;
/**
- * Represents a common possiblity to encrypt data. The secret key of an instance
- * can unseal and seal objects for example. Be aware: Every crypto access object
- * has its own secret key inside! So you need to use the same crypto access
- * object for you operations...
+ * Represents a common possibility to encrypt data. The secret key of an
+ * instance can unseal and seal objects for example. Be aware: Every crypto
+ * access object has its own secret key inside! So you need to use the same
+ * crypto access object for you operations...
*
* @author Albert Tregnaghi
*
diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/AbstractBinaryString.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/AbstractBinaryString.java
new file mode 100644
index 0000000000..40e5324c8e
--- /dev/null
+++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/AbstractBinaryString.java
@@ -0,0 +1,53 @@
+package com.mercedesbenz.sechub.commons.core.security.persistence;
+
+import java.util.Arrays;
+
+/**
+ * @author Jeremias Eppler
+ */
+public abstract class AbstractBinaryString implements BinaryString {
+ byte[] bytes;
+
+ protected AbstractBinaryString(byte[] bytes) {
+ if (bytes == null) {
+ throw new IllegalArgumentException("Byte array cannot be null.");
+ }
+
+ this.bytes = bytes;
+ }
+
+ protected AbstractBinaryString(String string) {
+ if (string == null) {
+ throw new IllegalArgumentException("String cannot be null.");
+ }
+
+ this.bytes = string.getBytes();
+ }
+
+ public abstract String toString();
+
+ public byte[] getBytes() {
+ // deep copy
+ return Arrays.copyOf(bytes, bytes.length);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(bytes);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ AbstractBinaryString other = (AbstractBinaryString) obj;
+ return Arrays.equals(bytes, other.bytes);
+ }
+}
diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/AesGcmSiv.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/AesGcmSiv.java
new file mode 100644
index 0000000000..dbb8926bd4
--- /dev/null
+++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/AesGcmSiv.java
@@ -0,0 +1,141 @@
+package com.mercedesbenz.sechub.commons.core.security.persistence;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.SecureRandom;
+import java.security.Security;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+/**
+ * Providing access to AES-GCM-SIV
+ *
+ * AES-GCM-SIV is a nonce misuse-resistant authenticated encryption algorithm.
+ *
+ * For more information refer to
+ * RFC 8452
+ *
+ * @author Jeremias Eppler
+ */
+public class AesGcmSiv implements PersistenceCipher {
+
+ private SecretKey secret;
+ private Provider cryptoProvider;
+ private PersistenceCipherType cipherType;
+
+ private static final String ALGORITHM = "AES/GCM-SIV/NoPadding";
+
+ /**
+ * The recommended initialization vector (iv) for AES-GCM-SIV is 12 bytes or 96
+ * bits.
+ *
+ * For an explanation have a look at: -
+ * https://datatracker.ietf.org/doc/html/rfc8452#section-4 -
+ * https://crypto.stackexchange.com/questions/41601/aes-gcm-recommended-iv-size-why-12-bytes
+ */
+ public static final int IV_LENGTH_IN_BYTES = 12;
+
+ public static final int AUTHENTICATION_TAG_LENGTH_IN_BITS = 16 * 8; // 16 bytes (128 bits)
+
+ private AesGcmSiv(SecretKey secret, PersistenceCipherType cipherType) {
+ this.secret = secret;
+ this.cipherType = cipherType;
+ cryptoProvider = new BouncyCastleProvider();
+ Security.addProvider(cryptoProvider);
+ }
+
+ public static AesGcmSiv create(BinaryString secret) throws InvalidKeyException {
+ AesGcmSiv instance = null;
+
+ byte[] rawSecret = secret.getBytes();
+
+ if (rawSecret.length == 32 || rawSecret.length == 16) {
+ PersistenceCipherType cipherType = (rawSecret.length == 32) ? PersistenceCipherType.AES_GCM_SIV_256 : PersistenceCipherType.AES_GCM_SIV_128;
+ SecretKey secretKey = new SecretKeySpec(rawSecret, 0, rawSecret.length, "AES");
+
+ instance = new AesGcmSiv(secretKey, cipherType);
+ } else {
+ throw new InvalidKeyException("The secret has to be 128 or 256 bits long, but was " + (rawSecret.length * 8) + " bits long.");
+ }
+
+ return instance;
+ }
+
+ public BinaryString generateNewInitializationVector() {
+ return generateNewInitializationVector(BinaryStringEncodingType.BASE64);
+ }
+
+ public BinaryString generateNewInitializationVector(BinaryStringEncodingType encodingType) {
+ byte[] initializationVector = new byte[IV_LENGTH_IN_BYTES];
+
+ SecureRandom random = new SecureRandom();
+ random.nextBytes(initializationVector);
+
+ return BinaryStringFactory.createFromBytes(initializationVector, encodingType);
+ }
+
+ public BinaryString encrypt(String plaintext, BinaryString initializationVector) throws InvalidAlgorithmParameterException, InvalidKeyException {
+ return encrypt(plaintext, initializationVector, BinaryStringEncodingType.BASE64);
+ }
+
+ public String decrypt(BinaryString ciphertext, BinaryString initializationVector)
+ throws InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
+ Cipher cipher;
+ try {
+ cipher = Cipher.getInstance(ALGORITHM, cryptoProvider);
+
+ SecretKeySpec keySpec = new SecretKeySpec(secret.getEncoded(), "AES");
+
+ GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(AUTHENTICATION_TAG_LENGTH_IN_BITS, initializationVector.getBytes());
+
+ cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
+
+ byte[] ciphertextBytes = ciphertext.getBytes();
+
+ byte[] plaintextBytes = cipher.doFinal(ciphertextBytes);
+
+ String plaintext = new String(plaintextBytes);
+
+ return plaintext;
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException providerException) {
+ throw new IllegalStateException("Decryption not possible, please check the provider", providerException);
+ }
+ }
+
+ @Override
+ public PersistenceCipherType getCipherType() {
+ return cipherType;
+ }
+
+ @Override
+ public BinaryString encrypt(String plaintext, BinaryString initializationVector, BinaryStringEncodingType encodingType)
+ throws InvalidAlgorithmParameterException, InvalidKeyException {
+ try {
+ Cipher cipher = Cipher.getInstance(ALGORITHM, cryptoProvider);
+
+ SecretKeySpec keySpec = new SecretKeySpec(secret.getEncoded(), "AES");
+
+ GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(AUTHENTICATION_TAG_LENGTH_IN_BITS, initializationVector.getBytes());
+
+ cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);
+
+ byte[] ciphertext = cipher.doFinal(plaintext.getBytes());
+
+ return BinaryStringFactory.createFromBytes(ciphertext, encodingType);
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException providerException) {
+ throw new IllegalStateException("Encryption not possible, please check the provider", providerException);
+ } catch (BadPaddingException | IllegalBlockSizeException paddingBlockException) {
+ throw new IllegalStateException("Should not occure. AES in GCM-SIV mode does not require padding.", paddingBlockException);
+ }
+ }
+}
diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/Base64String.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/Base64String.java
new file mode 100644
index 0000000000..bed757bb45
--- /dev/null
+++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/Base64String.java
@@ -0,0 +1,50 @@
+package com.mercedesbenz.sechub.commons.core.security.persistence;
+
+import java.util.Base64;
+
+/**
+ * A base64 encoded string.
+ *
+ * @author Jeremias Eppler
+ *
+ */
+public class Base64String extends AbstractBinaryString {
+
+ Base64String(byte[] bytes) {
+ super(bytes);
+ }
+
+ Base64String(String string) {
+ super(string);
+ }
+
+ @Override
+ public String toString() {
+ return Base64.getEncoder().encodeToString(bytes);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + java.util.Arrays.hashCode(bytes);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Base64String other = (Base64String) obj;
+ return java.util.Arrays.equals(bytes, other.bytes);
+ }
+
+ @Override
+ public BinaryStringEncodingType getType() {
+ return BinaryStringEncodingType.BASE64;
+ }
+}
diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryString.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryString.java
new file mode 100644
index 0000000000..2a3446ad7e
--- /dev/null
+++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryString.java
@@ -0,0 +1,19 @@
+package com.mercedesbenz.sechub.commons.core.security.persistence;
+
+/**
+ * A binary string type which knows it's encoding.
+ *
+ * The binary string keeps it's internal representation in binary format. In
+ * addition, the binary string knows it's encoding (e.g. base64 etc.).
+ *
+ * @see BinaryStringEncodingType
+ *
+ * @author Jeremias Eppler
+ */
+public interface BinaryString {
+ public byte[] getBytes();
+
+ public String toString();
+
+ public BinaryStringEncodingType getType();
+}
diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringEncodingType.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringEncodingType.java
new file mode 100644
index 0000000000..ac1a3e4219
--- /dev/null
+++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringEncodingType.java
@@ -0,0 +1,25 @@
+package com.mercedesbenz.sechub.commons.core.security.persistence;
+
+/**
+ * Encoding type of a binary string.
+ *
+ * @see BinaryString
+ *
+ * @author Jeremias Eppler
+ */
+public enum BinaryStringEncodingType {
+ /**
+ * A string with no encoding.
+ */
+ PLAIN,
+
+ /**
+ * A string encoded in hexadecimal format
+ */
+ HEX,
+
+ /**
+ * A string encoded in base64 format
+ */
+ BASE64,
+}
diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringFactory.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringFactory.java
new file mode 100644
index 0000000000..828969b3b3
--- /dev/null
+++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringFactory.java
@@ -0,0 +1,170 @@
+package com.mercedesbenz.sechub.commons.core.security.persistence;
+
+import java.util.Base64;
+import java.util.HexFormat;
+
+/**
+ * Creates {@link BinaryString} differently encoded strings.
+ *
+ * It can create {@link BinaryString} from differently encoded strings and
+ * returns a sub-type of {@link BinaryString} based on the desired output
+ * encoding.
+ *
+ * @author Jeremias Eppler
+ */
+public class BinaryStringFactory {
+ private static final BinaryStringEncodingType DEFAULT_ENCODING_TYPE = BinaryStringEncodingType.BASE64;
+
+ public static BinaryString createFromBytes(byte[] bytes) {
+ return createFromBytes(bytes, DEFAULT_ENCODING_TYPE);
+ }
+
+ public static BinaryString createFromBytes(byte[] bytes, BinaryStringEncodingType encodingType) {
+ if (bytes == null) {
+ throw new IllegalArgumentException("String cannot be null.");
+ }
+
+ BinaryString binaryString;
+
+ switch (encodingType) {
+ case BASE64:
+ binaryString = new Base64String(bytes);
+ break;
+ case HEX:
+ binaryString = new HexString(bytes);
+ break;
+ case PLAIN:
+ binaryString = new PlainString(bytes);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown binary string type");
+ }
+
+ return binaryString;
+ }
+
+ /**
+ * Creates a {@link BinaryString} from a plain Java string.
+ *
+ * The BinaryString will be in the default encoding.
+ *
+ * @param string
+ * @return
+ */
+ public static BinaryString createFromString(String string) {
+ return createFromString(string, DEFAULT_ENCODING_TYPE);
+ }
+
+ /**
+ * Creates a {@link BinaryString} from a plain Java string.
+ *
+ * It returns the string in the specified encoding.
+ *
+ * @param string
+ * @param encodingType
+ * @return
+ */
+ public static BinaryString createFromString(String string, BinaryStringEncodingType encodingType) {
+ if (string == null) {
+ throw new IllegalArgumentException("String cannot be null.");
+ }
+
+ BinaryString binaryString;
+
+ switch (encodingType) {
+ case BASE64:
+ binaryString = new Base64String(string);
+ break;
+ case HEX:
+ binaryString = new HexString(string);
+ break;
+ case PLAIN:
+ binaryString = new PlainString(string);
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown binary string type");
+ }
+
+ return binaryString;
+ }
+
+ /**
+ * Create a new {@link BinaryString} from a string which is in hexadecimal
+ * encoding.
+ *
+ * The given string needs to be hexadecimal encoded.
+ *
+ * For example: Given the string "616263" (orginal value "abc") it will decode
+ * it and store it as bytes.
+ *
+ * @param stringInHexFormat
+ * @param encodingType
+ * @return
+ */
+ public static BinaryString createFromHex(String stringInHexFormat) {
+ return createFromHex(stringInHexFormat, DEFAULT_ENCODING_TYPE);
+ }
+
+ /**
+ * Create a new {@link BinaryString} from a string which is in hexadecimal
+ * encoding.
+ *
+ * The given string needs to be hexadecimal encoded.
+ *
+ * For example: Given the string "616263" (orginal value "abc") it will decode
+ * it and store it as bytes.
+ *
+ * @param stringInHexFormat
+ * @param encodingType
+ * @return
+ */
+ public static BinaryString createFromHex(String stringInHexFormat, BinaryStringEncodingType encodingType) {
+ if (stringInHexFormat == null) {
+ throw new IllegalArgumentException("String cannot be null.");
+ }
+
+ HexFormat hexFormat = HexFormat.of();
+ byte[] hexBytes = hexFormat.parseHex(stringInHexFormat);
+
+ return createFromBytes(hexBytes, encodingType);
+ }
+
+ /**
+ * Create a new {@link BinaryString} from a string which is base64 encoded.
+ *
+ * The given string needs to be base64 encoded.
+ *
+ * For example: Given the string "YWJj" (orginal value "abc") it will decode it
+ * and store it as bytes.
+ *
+ * @param stringInBase64Format
+ * @return
+ */
+ public static BinaryString createFromBase64(String stringInBase64Format) {
+ return createFromBase64(stringInBase64Format, DEFAULT_ENCODING_TYPE);
+ }
+
+ /**
+ * Create a new {@link BinaryString} from a string which is base64 encoded.
+ *
+ * The given string needs to be base64 encoded.
+ *
+ * For example: Given the string "YWJj" (orginal value "abc") it will decode it
+ * and store it as bytes.
+ *
+ * @param stringInBase64Format
+ * @param encodingType
+ * @return
+ */
+ public static BinaryString createFromBase64(String stringInBase64Format, BinaryStringEncodingType encodingType) {
+ if (stringInBase64Format == null) {
+ throw new IllegalArgumentException("String cannot be null.");
+ }
+
+ // if it cannot be decoded,
+ // it will throw an IllegalArgumentException
+ byte[] decoded = Base64.getDecoder().decode(stringInBase64Format);
+
+ return createFromBytes(decoded, encodingType);
+ }
+}
diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/HexString.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/HexString.java
new file mode 100644
index 0000000000..62e7c28940
--- /dev/null
+++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/HexString.java
@@ -0,0 +1,32 @@
+package com.mercedesbenz.sechub.commons.core.security.persistence;
+
+import java.util.HexFormat;
+
+/**
+ * Hexadecimal encoded string.
+ *
+ * @author Jeremias Eppler
+ */
+public class HexString extends AbstractBinaryString {
+
+ protected HexFormat hexFormat = HexFormat.of();
+
+ HexString(byte[] bytes) {
+ super(bytes);
+ }
+
+ HexString(String string) {
+ super(string);
+ }
+
+ @Override
+ public String toString() {
+ return hexFormat.formatHex(bytes);
+ }
+
+ @Override
+ public BinaryStringEncodingType getType() {
+ return BinaryStringEncodingType.HEX;
+ }
+
+}
diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/NoneCipher.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/NoneCipher.java
new file mode 100644
index 0000000000..c1dd175fc6
--- /dev/null
+++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/NoneCipher.java
@@ -0,0 +1,59 @@
+package com.mercedesbenz.sechub.commons.core.security.persistence;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+
+/**
+ * A cipher which does not encrypt anything.
+ *
+ * It can be used during testing or debugging.
+ *
+ * Furthermore, this cipher can be used to change the encoding of the data. For
+ * example, from plain to Base64.
+ *
+ * @author Jeremias Eppler
+ */
+public class NoneCipher implements PersistenceCipher {
+ private NoneCipher() {
+ }
+
+ @Override
+ public PersistenceCipherType getCipherType() {
+ return PersistenceCipherType.NONE;
+ }
+
+ public static PersistenceCipher create(BinaryString secret) throws InvalidKeyException {
+ return new NoneCipher();
+ }
+
+ @Override
+ public BinaryString encrypt(String plaintext, BinaryString initializationVector) throws InvalidAlgorithmParameterException, InvalidKeyException {
+ return encrypt(plaintext, initializationVector, BinaryStringEncodingType.PLAIN);
+ }
+
+ @Override
+ public BinaryString encrypt(String plaintext, BinaryString initializationVector, BinaryStringEncodingType encodingType)
+ throws InvalidAlgorithmParameterException, InvalidKeyException {
+ return BinaryStringFactory.createFromString(plaintext, encodingType);
+ }
+
+ @Override
+ public String decrypt(BinaryString ciphertext, BinaryString initializationVector)
+ throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
+
+ return ciphertext.toString();
+ }
+
+ @Override
+ public BinaryString generateNewInitializationVector() {
+ return generateNewInitializationVector(BinaryStringEncodingType.PLAIN);
+ }
+
+ @Override
+ public BinaryString generateNewInitializationVector(BinaryStringEncodingType encodingType) {
+ return BinaryStringFactory.createFromString("", encodingType);
+ }
+}
diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipher.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipher.java
new file mode 100644
index 0000000000..286a56b472
--- /dev/null
+++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipher.java
@@ -0,0 +1,35 @@
+package com.mercedesbenz.sechub.commons.core.security.persistence;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+
+/**
+ * Interface for cryptographic algorithms used to protect data at rest.
+ *
+ * "At rest" refers to data which is usually stored in a database, file or other
+ * persistent storage.
+ *
+ * @author Jeremias Eppler
+ */
+public interface PersistenceCipher {
+ public static PersistenceCipher create(BinaryString secret) throws InvalidKeyException {
+ return null;
+ }
+
+ public BinaryString generateNewInitializationVector();
+
+ public BinaryString generateNewInitializationVector(BinaryStringEncodingType encodingType);
+
+ public BinaryString encrypt(String plaintext, BinaryString initializationVector) throws InvalidAlgorithmParameterException, InvalidKeyException;
+
+ public BinaryString encrypt(String plaintext, BinaryString initializationVector, BinaryStringEncodingType encodingType)
+ throws InvalidAlgorithmParameterException, InvalidKeyException;
+
+ public String decrypt(BinaryString ciphertext, BinaryString initializationVector)
+ throws IllegalArgumentException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException;
+
+ public PersistenceCipherType getCipherType();
+}
diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherFactory.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherFactory.java
new file mode 100644
index 0000000000..ee77c3ada5
--- /dev/null
+++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherFactory.java
@@ -0,0 +1,43 @@
+package com.mercedesbenz.sechub.commons.core.security.persistence;
+
+import java.security.InvalidKeyException;
+
+/**
+ * The factory creates persistent ciphers used to protect data at rest.
+ *
+ * The algorithms protect both the confidentiality and integrity of the
+ * information at rest.
+ *
+ * In addition, the ciphers which it creates are nonce (initialization vector)
+ * misuse-resistant.
+ *
+ * @author Jeremias Eppler
+ */
+public class PersistenceCipherFactory {
+
+ /**
+ * Creates a new persistent cipher based on the type and secret.
+ *
+ * @param cipherType
+ * @param secret
+ * @return
+ * @throws InvalidKeyException
+ */
+ public static PersistenceCipher create(PersistenceCipherType cipherType, BinaryString secret) throws InvalidKeyException {
+ PersistenceCipher cipher = null;
+
+ switch (cipherType) {
+ case NONE:
+ cipher = NoneCipher.create(secret);
+ break;
+ case AES_GCM_SIV_256:
+ case AES_GCM_SIV_128:
+ cipher = AesGcmSiv.create(secret);
+ break;
+ default:
+ throw new IllegalArgumentException("Unable to create cipher. Unknown cipher.");
+ }
+
+ return cipher;
+ }
+}
diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherType.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherType.java
new file mode 100644
index 0000000000..2f80ba26d4
--- /dev/null
+++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherType.java
@@ -0,0 +1,37 @@
+package com.mercedesbenz.sechub.commons.core.security.persistence;
+
+/**
+ * Contains the available ciphers.
+ *
+ * @author Jeremias Eppler
+ */
+public enum PersistenceCipherType {
+ /**
+ * A special cipher type which does not protect data.
+ *
+ * This is intended for testing.
+ */
+ NONE,
+
+ /**
+ * Advanced Encryption Standard (AES) 128 bit key (secret) in Galois/Counter
+ * Mode (GCM) and synthetic initialization vector (SIV).
+ *
+ * AES GCM-SIV is a nonce (initialization vector) misuse-resistant authenticated
+ * encryption.
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc8452
+ */
+ AES_GCM_SIV_128,
+
+ /**
+ * Advanced Encryption Standard (AES) 256 bit key (secret) in Galois/Counter
+ * Mode (GCM) and synthetic initialization vector (SIV).
+ *
+ * AES GCM-SIV is a nonce (initialization vector) misuse-resistant authenticated
+ * encryption.
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc8452
+ */
+ AES_GCM_SIV_256;
+}
diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PlainString.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PlainString.java
new file mode 100644
index 0000000000..bec4545519
--- /dev/null
+++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/PlainString.java
@@ -0,0 +1,36 @@
+package com.mercedesbenz.sechub.commons.core.security.persistence;
+
+/**
+ * A wrapper around the string class.
+ *
+ * The string class in Java is final and therefore it is impossible to inherited
+ * from.
+ *
+ * @author Jeremias Eppler
+ */
+public class PlainString extends AbstractBinaryString {
+
+ PlainString(byte[] bytes) {
+ super(bytes);
+ }
+
+ PlainString(String string) {
+ super(string);
+ }
+
+ @Override
+ public byte[] getBytes() {
+ return bytes;
+ }
+
+ @Override
+ public BinaryStringEncodingType getType() {
+ return BinaryStringEncodingType.PLAIN;
+ }
+
+ @Override
+ public String toString() {
+ return new String(bytes);
+ }
+
+}
diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/RotationStrategy.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/RotationStrategy.java
new file mode 100644
index 0000000000..04659d256b
--- /dev/null
+++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/RotationStrategy.java
@@ -0,0 +1,198 @@
+package com.mercedesbenz.sechub.commons.core.security.persistence;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+
+/**
+ * The rotation strategy helps to rotate the cipher text.
+ *
+ * It can be used to rotate (re-encrpyt) by using: - different initialization
+ * vectors - different secret keys - different algorithms
+ *
+ * or a combination of the above.
+ *
+ * @author Jeremias Eppler
+ */
+public class RotationStrategy {
+ private PersistenceCipher currentCipher;
+ private PersistenceCipher newCipher;
+ private boolean performSecretRotation = false;
+
+ private RotationStrategy(PersistenceCipher currentCipher, PersistenceCipher newCipher, boolean performSecretRotation) {
+ this.currentCipher = currentCipher;
+ this.newCipher = newCipher;
+ this.performSecretRotation = performSecretRotation;
+ }
+
+ /**
+ * Create a new rotation strategy which only allows to rotate the initialization
+ * vector.
+ *
+ * @param secret
+ * @param cipher
+ * @return
+ * @throws InvalidKeyException
+ */
+ public static RotationStrategy createInitializationVectorOnlyRotationStrategy(BinaryString secret, PersistenceCipherType cipher)
+ throws InvalidKeyException {
+ PersistenceCipher currentCipher = PersistenceCipherFactory.create(cipher, secret);
+ PersistenceCipher newCipher = PersistenceCipherFactory.create(cipher, secret);
+
+ boolean performSecretRotation = false;
+
+ return new RotationStrategy(currentCipher, newCipher, performSecretRotation);
+ }
+
+ /**
+ * Create a new rotation strategy which allows to rotate the secret.
+ *
+ * This is useful in case of a secret leak.
+ *
+ * @param currentSecret
+ * @param newSecret
+ * @param cipher
+ * @return
+ * @throws InvalidKeyException
+ */
+ public static RotationStrategy createSecretRotationStrategy(BinaryString currentSecret, BinaryString newSecret, PersistenceCipherType cipher)
+ throws InvalidKeyException {
+ PersistenceCipher currentCipher = PersistenceCipherFactory.create(cipher, currentSecret);
+ PersistenceCipher newCipher = PersistenceCipherFactory.create(cipher, newSecret);
+
+ boolean performSecretRotation = true;
+
+ return new RotationStrategy(currentCipher, newCipher, performSecretRotation);
+ }
+
+ /**
+ * Create a new rotation strategy which allows to rotate the secret and ciphers.
+ *
+ * This is useful if the underling cryptographic cipher or mode of operation is
+ * deemed as insecure.
+ *
+ * For example, this is the case with Data Encryption Standard (DES) and the
+ * Triple DES variant.
+ *
+ *
+ * @param currentSecret
+ * @param newSecret
+ * @param currentCipherType
+ * @param newCipherType
+ * @return
+ * @throws InvalidKeyException
+ */
+ public static RotationStrategy createCipherAndSecretRotationStrategy(BinaryString currentSecret, BinaryString newSecret,
+ PersistenceCipherType currentCipherType, PersistenceCipherType newCipherType) throws InvalidKeyException {
+ PersistenceCipher currentCipher = PersistenceCipherFactory.create(currentCipherType, currentSecret);
+ PersistenceCipher newCipher = PersistenceCipherFactory.create(newCipherType, newSecret);
+
+ boolean performSecretRotation = true;
+
+ return new RotationStrategy(currentCipher, newCipher, performSecretRotation);
+ }
+
+ /**
+ * Rotate the encrypted cipher text using the same initialization vector.
+ *
+ * The given cipher text is decrypted and encrypted again using the given
+ * initialization vector the old and the new cipher text.
+ *
+ * @param cipherText
+ * @param initializationVector
+ * @return
+ * @throws InvalidKeyException
+ * @throws IllegalArgumentException
+ * @throws InvalidAlgorithmParameterException
+ * @throws IllegalBlockSizeException
+ * @throws BadPaddingException
+ */
+ public BinaryString rotate(BinaryString cipherText, BinaryString initializationVector)
+ throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
+ return rotate(cipherText, initializationVector, null, cipherText.getType());
+ }
+
+ /**
+ * Rotate the encrypted cipher text using different initialization vectors.
+ *
+ * The given cipher text is decrypted and encrypted again using two different
+ * initialization vectors.
+ *
+ * @param cipherText
+ * @param initializationVector
+ * @param newIntializationVector
+ * @return
+ * @throws InvalidKeyException
+ * @throws IllegalArgumentException
+ * @throws InvalidAlgorithmParameterException
+ * @throws IllegalBlockSizeException
+ * @throws BadPaddingException
+ */
+ public BinaryString rotate(BinaryString cipherText, BinaryString initializationVector, BinaryString newIntializationVector)
+ throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
+ return rotate(cipherText, initializationVector, newIntializationVector, cipherText.getType());
+ }
+
+ /**
+ * Rotate the encrypted cipher text using different initialization vectors and
+ * the given encoding type.
+ *
+ * The given cipher text is decrypted and encrypted again using two different
+ * initialization vectors.
+ *
+ * The resulting cipher text is returned in the specified
+ * {@link BinaryStringEncodingType}.
+ *
+ * @param cipherText
+ * @param initializationVector
+ * @param newIntializationVector
+ * @param newBinaryStringEncoding
+ * @return
+ * @throws InvalidKeyException
+ * @throws IllegalArgumentException
+ * @throws InvalidAlgorithmParameterException
+ * @throws IllegalBlockSizeException
+ * @throws BadPaddingException
+ */
+ public BinaryString rotate(BinaryString cipherText, BinaryString initializationVector, BinaryString newIntializationVector,
+ BinaryStringEncodingType newBinaryStringEncoding)
+ throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
+ if (cipherText == null) {
+ throw new IllegalArgumentException("The ciphertext cannot be null!");
+ }
+
+ if (initializationVector == null) {
+ throw new IllegalArgumentException("The initialization vector (nonce) cannot be null!");
+ }
+
+ String plainText = currentCipher.decrypt(cipherText, initializationVector);
+
+ BinaryString newCipherText = null;
+
+ if (newIntializationVector != null) {
+ newCipherText = newCipher.encrypt(plainText, newIntializationVector);
+ } else {
+ newCipherText = newCipher.encrypt(plainText, initializationVector);
+ }
+
+ return newCipherText;
+ }
+
+ public PersistenceCipherType getCurrentCipher() {
+ return currentCipher.getCipherType();
+ }
+
+ public PersistenceCipherType getNewCipher() {
+ return newCipher.getCipherType();
+ }
+
+ public boolean isSecretRotationStrategy() {
+ return performSecretRotation;
+ }
+
+ public boolean isCipherRotationStrategy() {
+ return currentCipher.getCipherType() != newCipher.getCipherType();
+ }
+}
diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/package-info.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/package-info.java
new file mode 100644
index 0000000000..fbd13b6983
--- /dev/null
+++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/security/persistence/package-info.java
@@ -0,0 +1,79 @@
+/**
+ * This package contains classes to protect data at-rest.
+ *
+ * The package is named persistence, because the persistence layer is used to
+ * help store data in a database or other data storage systems.
+ *
+ * The requirements for the cryptographic algorithms for data at rest are:
+ *
+ *
+ * - authenticated encryption (AE)
+ * - nonce (initialization vector) misuse-resistant
+ *
+ *
+ * Authenticated encryption (AE) provides confidentiality and integrity at the
+ * same time. Confidentiality is provided by encrypting and integrity by hashing
+ * the data. If during decryption, the hash and the data do not match, the data
+ * has been corrupted or tampered with.
+ *
+ * Because AE algorithms do both, encrypt and hash at the same time, it is
+ * difficult to use them incorrectly.
+ *
+ * Furthermore, it is impossible to ensure a nonce (initialization vector) is
+ * not used more than once in a multi-server database scenario, therefore a
+ * nonce misuse-resistant algorithm is required.
+ *
+ * In theory one could make sure, that there is a single data entry point which
+ * keeps track of the used nonces. Before writing the data to the database, the
+ * single data entry point generates a nonce and looks into the list of used
+ * nonces whether the nonce is already used or not. If the nonce is already used
+ * the single data entry point needs to generate a nonce until the nonce is
+ * unique and has never been used before. The consequence would be, that data
+ * cannot be written in parallel, which is unacceptable in the case of a
+ * distributed application, as it would slow down the rate at which data can be
+ * inserted into the data storage (e.g. database).
+ *
+ * To avoid those problems, a nonce misuse-resistant algorithm is required. A
+ * nonce misuse-algorithm provides security even if the same combination of
+ * secret key, additional data, nonce and plain text is encrypted and stored
+ * more than once.
+ *
+ * There are a number of authenticated encryption algorithms. However only a few
+ * are nonce misuse-resistant. Examples for misuse resistant algorithms are:
+ *
+ *
+ * - AES-GCM-SIV
+ * - AES-SIV
+ * - Deoxys-II
+ * - COLM
+ * - Romulus-M
+ *
+ *
+ * Furthermore, symmetric encryption algorithms are not in danger to be
+ * completely broken by quantum computers (see:
+ * https://security.stackexchange.com/a/103560). However, it might be necessary
+ * to use different cryptographic algorithms in the future as attacks on
+ * existing algorithms become possible.
+ *
+ * Bruce Schneier puts it this way:
+ *
+ * > "In the face of all that uncertainty, agility is the only way to maintain
+ * security." (source:
+ * https://www.schneier.com/essays/archives/2022/08/nists-post-quantum-cryptography-standards-competition.html)
+ *
+ * This package also contains classes which can help with algorithm, secret and
+ * initialization vector rotation:
+ *
+ *
+ * - Initialization vector (or nonce) rotation swaps the nonce used during
+ * encryption.
+ *
+ * - Secret rotation is used to change the secret key.
+ *
+ * - Algorithm rotation can be used if the algorithms need to be
+ * exchanged.
+ *
+ *
+ * @author Jeremias Eppler
+ */
+package com.mercedesbenz.sechub.commons.core.security.persistence;
\ No newline at end of file
diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/AesGcmSivTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/AesGcmSivTest.java
new file mode 100644
index 0000000000..d196a1c772
--- /dev/null
+++ b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/AesGcmSivTest.java
@@ -0,0 +1,376 @@
+package com.mercedesbenz.sechub.commons.core.security.persistence;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.AEADBadTagException;
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+
+import org.junit.jupiter.api.Test;
+
+public class AesGcmSivTest {
+ private static final String LONG_TEXT = "Hello world, this is long text with different emojis. Today, I had for breakfast two π₯, 1 π₯ and some π₯ͺ. That made me happy βΊοΈ!";
+
+ @Test
+ void generate_new_initialization_vector() throws InvalidKeyException {
+ /* prepare */
+ BinaryString secret = new Base64String("a".repeat(32));
+ AesGcmSiv cipher = AesGcmSiv.create(secret);
+
+ /* execute */
+ BinaryString initializationVector = cipher.generateNewInitializationVector();
+
+ /* test */
+ assertEquals(AesGcmSiv.IV_LENGTH_IN_BYTES, initializationVector.getBytes().length);
+ }
+
+ @Test
+ void create_secret_32_bytes() throws InvalidKeyException {
+ /* prepare */
+ BinaryString secret = new Base64String("a".repeat(32));
+
+ /* execute */
+ AesGcmSiv cipher = AesGcmSiv.create(secret);
+
+ /* test */
+ assertNotNull(cipher);
+ assertEquals(PersistenceCipherType.AES_GCM_SIV_256, cipher.getCipherType());
+ }
+
+ @Test
+ void create_secret_16_bytes() throws InvalidKeyException {
+ /* prepare */
+ BinaryString secret = new Base64String("a".repeat(16));
+
+ /* execute */
+ AesGcmSiv cipher = AesGcmSiv.create(secret);
+
+ /* test */
+ assertNotNull(cipher);
+ assertEquals(PersistenceCipherType.AES_GCM_SIV_128, cipher.getCipherType());
+ }
+
+ @Test
+ void create_secret_secret_6_bytes_invalid() {
+ /* prepare */
+ BinaryString secret = new Base64String("abcdef");
+
+ /* execute + test */
+ assertThrows(InvalidKeyException.class, () -> {
+ AesGcmSiv.create(secret);
+ });
+ }
+
+ @Test
+ void create_secret_secret_31_bytes_invalid() {
+ /* prepare */
+ BinaryString secret = new Base64String("a".repeat(31));
+
+ /* execute + test */
+ assertThrows(InvalidKeyException.class, () -> {
+ AesGcmSiv.create(secret);
+ });
+ }
+
+ @Test
+ void encrypt__aes_256() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException,
+ IllegalBlockSizeException, BadPaddingException {
+
+ /* prepare */
+ BinaryString secret = new Base64String("w".repeat(32));
+ String plaintext = "bca";
+ String expectedCiphertext = "1qKKtEpM2ppl4wWrJxJo0MiFdw==";
+ BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES));
+
+ AesGcmSiv cipher = AesGcmSiv.create(secret);
+
+ /* execute */
+ BinaryString ciphertext = cipher.encrypt(plaintext, initializationVector);
+
+ /* test */
+ assertEquals(expectedCiphertext, ciphertext.toString());
+ assertEquals(PersistenceCipherType.AES_GCM_SIV_256, cipher.getCipherType());
+ }
+
+ @Test
+ void encrypt__aes_256_initialization_vector_too_short() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException,
+ InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
+
+ /* prepare */
+ BinaryString secret = new Base64String("w".repeat(32));
+ String plaintext = "bca";
+ BinaryString initializationVector = new Base64String("abc".repeat(2));
+
+ AesGcmSiv cipher = AesGcmSiv.create(secret);
+
+ /* execute */
+ Exception exception = assertThrows(InvalidAlgorithmParameterException.class, () -> {
+ cipher.encrypt(plaintext, initializationVector);
+ });
+
+ /* test */
+ assertEquals("Invalid nonce", exception.getMessage());
+ }
+
+ @Test
+ void encrypt__aes_256_initialization_vector_too_long() throws InvalidKeyException, InvalidAlgorithmParameterException {
+
+ /* prepare */
+ BinaryString secret = new Base64String("w".repeat(32));
+ String plaintext = "bca";
+ BinaryString initializationVector = new Base64String("abc".repeat(50));
+
+ AesGcmSiv cipher = AesGcmSiv.create(secret);
+
+ /* execute */
+ Exception exception = assertThrows(InvalidAlgorithmParameterException.class, () -> {
+ cipher.encrypt(plaintext, initializationVector);
+ });
+
+ /* test */
+ assertEquals("Invalid nonce", exception.getMessage());
+ }
+
+ @Test
+ void decrypt__aes_256() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException,
+ IllegalBlockSizeException, BadPaddingException {
+ /* prepare */
+ BinaryString secret = new Base64String("w".repeat(32));
+ String expectedPlaintext = "bca";
+ BinaryString ciphertext = BinaryStringFactory.createFromBase64("1qKKtEpM2ppl4wWrJxJo0MiFdw==", BinaryStringEncodingType.BASE64);
+ BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES));
+
+ AesGcmSiv cipher = AesGcmSiv.create(secret);
+
+ /* execute */
+ String plaintext = cipher.decrypt(ciphertext, initializationVector);
+
+ /* test */
+ assertEquals(expectedPlaintext, plaintext);
+ }
+
+ @Test
+ void encrypt__aes_128() throws InvalidKeyException, InvalidAlgorithmParameterException {
+ /* prepare */
+ BinaryString secret = new Base64String("a".repeat(16));
+ String plaintext = "bca";
+ String expectedCiphertext = "yGcKhuWbewS+R4tlegECshiTSQ==";
+ BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES));
+
+ AesGcmSiv cipher = AesGcmSiv.create(secret);
+
+ /* execute */
+ BinaryString ciphertext = cipher.encrypt(plaintext, initializationVector);
+
+ /* test */
+ assertEquals(expectedCiphertext, ciphertext.toString());
+ assertEquals(PersistenceCipherType.AES_GCM_SIV_128, cipher.getCipherType());
+ }
+
+ @Test
+ void encrypt__aes_256_hex_format_and_emojis() throws InvalidKeyException, InvalidAlgorithmParameterException {
+ /* prepare */
+ BinaryString secret = new HexString("π₯¦π₯π₯π«π₯π«π½π");
+ String plaintext = "Hello π, welcome to π.";
+ String expectedCiphertext = "d09be77ddd8dbac86b69b0f5f554faef740555ac93f12aedfdf62700e4ea3016e03dacc105f32f114791d8e6";
+ BinaryString initializationVector = new Base64String("π§
".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES / 4));
+
+ AesGcmSiv cipher = AesGcmSiv.create(secret);
+
+ /* execute */
+ BinaryString ciphertext = cipher.encrypt(plaintext, initializationVector, BinaryStringEncodingType.HEX);
+
+ /* test */
+ assertEquals(expectedCiphertext, ciphertext.toString());
+ assertEquals(PersistenceCipherType.AES_GCM_SIV_256, cipher.getCipherType());
+ }
+
+ @Test
+ void encrypt__aes_128_base64_format_and_emojis() throws InvalidKeyException, InvalidAlgorithmParameterException {
+ /* prepare */
+ BinaryString secret = new Base64String("ππππ");
+
+ String plaintext = "Hello π, welcome to π.";
+ String expectedCiphertext = "Qu7ICJBGMw9dAPPBWx86e5bjOq3YKC+x25n/YkluWZAGdSna08tKaE78pMk=";
+ BinaryString initializationVector = new Base64String("π§
".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES / 4));
+
+ AesGcmSiv cipher = AesGcmSiv.create(secret);
+
+ /* execute */
+ BinaryString ciphertext = cipher.encrypt(plaintext, initializationVector, BinaryStringEncodingType.BASE64);
+
+ /* test */
+ assertEquals(expectedCiphertext, ciphertext.toString());
+ assertEquals(PersistenceCipherType.AES_GCM_SIV_128, cipher.getCipherType());
+ }
+
+ @Test
+ void decrypt__aes_128() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException,
+ IllegalBlockSizeException, BadPaddingException {
+ /* prepare */
+ BinaryString secret = new Base64String("a".repeat(16));
+ String expectedPlaintext = "bca";
+ BinaryString cipherText = BinaryStringFactory.createFromBase64("yGcKhuWbewS+R4tlegECshiTSQ==", BinaryStringEncodingType.BASE64);
+ BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES));
+
+ AesGcmSiv cipher = AesGcmSiv.create(secret);
+
+ /* execute */
+ String plaintext = cipher.decrypt(cipherText, initializationVector);
+
+ /* test */
+ assertEquals(expectedPlaintext, plaintext);
+ }
+
+ @Test
+ void decrypt__aes_128_wrong_cipher_text() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException,
+ IllegalBlockSizeException, BadPaddingException {
+ /* prepare */
+ BinaryString secret = new Base64String("a".repeat(16));
+ BinaryString ciphertext = new Base64String("hello world, this is base 64 encoded");
+ BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES));
+
+ AesGcmSiv cipher = AesGcmSiv.create(secret);
+
+ /* execute */
+ Exception exception = assertThrows(AEADBadTagException.class, () -> {
+ cipher.decrypt(ciphertext, initializationVector);
+ });
+
+ /* test */
+ assertEquals("mac check failed", exception.getMessage());
+ }
+
+ @Test
+ void decrypt__aes_256_wrong_cipher_text() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException,
+ IllegalBlockSizeException, BadPaddingException {
+
+ /* prepare */
+ BinaryString secret = new Base64String("a".repeat(32));
+ BinaryString ciphertext = new Base64String("hello world, this is base 64 encoded");
+ BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES));
+
+ AesGcmSiv cipher = AesGcmSiv.create(secret);
+
+ /* execute */
+ Exception exception = assertThrows(AEADBadTagException.class, () -> {
+ cipher.decrypt(ciphertext, initializationVector);
+ });
+
+ /* test */
+ assertEquals("mac check failed", exception.getMessage());
+ }
+
+ @Test
+ void encrypt__aes_128_long_text_with_emojis() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException,
+ InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
+
+ /* prepare */
+ BinaryString secret = new Base64String("a".repeat(16));
+ String plaintext = LONG_TEXT;
+ BinaryString expectedCiphertext = BinaryStringFactory.createFromBase64(
+ "28/RdEWgYbpbiraiWcSo58+8sfCRRQpSoZiiFqNsYN8tLLVE6AXeQjxh4zazK65G7T0dmFnqrbyx6aRUB+7I6guFXMxqjRij9HdRkae4OalWZVNtCs2+mjBBMNOB5Ke2bgIcYDZbDMRWceBtJnE5PKg7vxrNFgR+8uFw9ejbRVxzGTbkyNeh48QVT9Knk7LpmqQ/eHFTvsvnD0M=",
+ BinaryStringEncodingType.BASE64);
+ BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES));
+
+ AesGcmSiv cipher = AesGcmSiv.create(secret);
+
+ /* execute */
+ BinaryString ciphertext = cipher.encrypt(plaintext, initializationVector);
+
+ /* test */
+ assertEquals(expectedCiphertext, ciphertext);
+ }
+
+ @Test
+ void encrypt__aes_256_long_text_with_emojis() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException,
+ InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
+
+ /* prepare */
+ BinaryString secret = new Base64String("a".repeat(32));
+ String plaintext = LONG_TEXT;
+ BinaryString expectedCiphertext = BinaryStringFactory.createFromBase64(
+ "E2RrqhXtKG39okWxxvw3d4NQ+DXr2+Qa78JvdpHS4+FOckRECTkjoX2JfZNKHP3on0sDO1q8uTc+BY9QJkMK+MsWzp8YT4SR0UxWo7uy5SSPMXOLLcQg0vzTOTdgo00vPQy34vogNYO1V/TTzOzzP6Ng0kT9TDsYUWu+v0y3uZw/ujl2X8bP8Nfrp2ZRMrgfpj7NbjQQd8hD5AY=",
+ BinaryStringEncodingType.BASE64);
+ BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES));
+
+ AesGcmSiv cipher = AesGcmSiv.create(secret);
+
+ /* execute */
+ BinaryString ciphertext = cipher.encrypt(plaintext, initializationVector);
+
+ /* test */
+ assertEquals(expectedCiphertext, ciphertext);
+ }
+
+ @Test
+ void decrypt__aes_128_long_text_with_emojis() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException,
+ InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
+
+ /* prepare */
+ BinaryString secret = new Base64String("a".repeat(16));
+ String expectedPlaintext = LONG_TEXT;
+ BinaryString ciphertext = BinaryStringFactory.createFromBase64(
+ "28/RdEWgYbpbiraiWcSo58+8sfCRRQpSoZiiFqNsYN8tLLVE6AXeQjxh4zazK65G7T0dmFnqrbyx6aRUB+7I6guFXMxqjRij9HdRkae4OalWZVNtCs2+mjBBMNOB5Ke2bgIcYDZbDMRWceBtJnE5PKg7vxrNFgR+8uFw9ejbRVxzGTbkyNeh48QVT9Knk7LpmqQ/eHFTvsvnD0M=",
+ BinaryStringEncodingType.BASE64);
+ BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES));
+
+ AesGcmSiv cipher = AesGcmSiv.create(secret);
+
+ /* execute */
+ String plaintext = cipher.decrypt(ciphertext, initializationVector);
+
+ /* test */
+ assertEquals(expectedPlaintext, plaintext);
+ }
+
+ @Test
+ void decrypt__aes_256_long_text_with_emojis() throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException,
+ InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
+
+ /* prepare */
+ BinaryString secret = new Base64String("a".repeat(32));
+ String expectedPlaintext = LONG_TEXT;
+ BinaryString ciphertext = BinaryStringFactory.createFromBase64(
+ "E2RrqhXtKG39okWxxvw3d4NQ+DXr2+Qa78JvdpHS4+FOckRECTkjoX2JfZNKHP3on0sDO1q8uTc+BY9QJkMK+MsWzp8YT4SR0UxWo7uy5SSPMXOLLcQg0vzTOTdgo00vPQy34vogNYO1V/TTzOzzP6Ng0kT9TDsYUWu+v0y3uZw/ujl2X8bP8Nfrp2ZRMrgfpj7NbjQQd8hD5AY=",
+ BinaryStringEncodingType.BASE64);
+ BinaryString initializationVector = new Base64String("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES));
+
+ AesGcmSiv cipher = AesGcmSiv.create(secret);
+
+ /* execute */
+ String plaintext = cipher.decrypt(ciphertext, initializationVector);
+
+ /* test */
+ assertEquals(expectedPlaintext, plaintext);
+ }
+
+ @Test
+ void getCipherType_aes_256() throws InvalidKeyException {
+ /* prepare */
+ BinaryString secret = new Base64String("a".repeat(32));
+
+ /* execute */
+ AesGcmSiv cipher = AesGcmSiv.create(secret);
+
+ /* test */
+ assertEquals(PersistenceCipherType.AES_GCM_SIV_256, cipher.getCipherType());
+ }
+
+ @Test
+ void getCiphersType_aes_128() throws InvalidKeyException {
+ /* prepare */
+ BinaryString secret = new Base64String("a".repeat(16));
+
+ /* execute */
+ AesGcmSiv cipher = AesGcmSiv.create(secret);
+
+ /* test */
+ assertEquals(PersistenceCipherType.AES_GCM_SIV_128, cipher.getCipherType());
+ }
+}
diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/Base64StringTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/Base64StringTest.java
new file mode 100644
index 0000000000..d9a1800d60
--- /dev/null
+++ b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/Base64StringTest.java
@@ -0,0 +1,93 @@
+package com.mercedesbenz.sechub.commons.core.security.persistence;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+public class Base64StringTest {
+ @Test
+ void from_string() {
+ /* prepare */
+ String string = "Hello";
+ String expectedString = "SGVsbG8=";
+
+ /* execute + test */
+ assertEquals(expectedString, new Base64String(string).toString());
+ }
+
+ @Test
+ void from_string_null_throw_illegal_argument_exception() {
+ /* execute */
+ Exception exception = assertThrows(IllegalArgumentException.class, () -> {
+ new Base64String((String) null);
+ });
+
+ /* test */
+ assertEquals("String cannot be null.", exception.getMessage());
+ }
+
+ @Test
+ void from_string_unicode() {
+ String string = "Hello π¦";
+ String expectedString = "SGVsbG8g8J+mhA==";
+
+ assertEquals(expectedString, new Base64String(string).toString());
+ }
+
+ @Test
+ void from_bytes() {
+ byte[] bytes = "Hello".getBytes();
+ String expectedString = "SGVsbG8=";
+
+ assertEquals(expectedString, new Base64String(bytes).toString());
+ }
+
+ @Test
+ void from_bytes_unicode() {
+ byte[] bytes = "Hello π¦".getBytes();
+ String expectedString = "SGVsbG8g8J+mhA==";
+
+ assertEquals(expectedString, new Base64String(bytes).toString());
+ }
+
+ @Test
+ void from_bytes_null_throw_illegal_argument_exception() {
+ Exception exception = assertThrows(IllegalArgumentException.class, () -> {
+ new Base64String((byte[]) null);
+ });
+
+ assertEquals("Byte array cannot be null.", exception.getMessage());
+ }
+
+ @Test
+ void toString_unicode_test() {
+ String string = "Hello π¦";
+ String expectedString = "SGVsbG8g8J+mhA==";
+
+ assertEquals(expectedString, new Base64String(string).toString());
+ }
+
+ @Test
+ void equals_test() {
+ String string = "I like π";
+
+ Base64String b64String = new Base64String(string);
+ Base64String b64String2 = new Base64String(string);
+
+ assertEquals(b64String, b64String2);
+ assertTrue(b64String.equals(b64String2));
+ assertEquals(b64String.hashCode(), b64String2.hashCode());
+ }
+
+ @Test
+ void test_immutablitiy() {
+ String string = "A πΊ in a π skin";
+ Base64String b64String = new Base64String(string);
+
+ assertFalse(string == b64String.toString());
+ assertFalse(string.getBytes() == b64String.getBytes());
+ }
+}
diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringFactoryTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringFactoryTest.java
new file mode 100644
index 0000000000..314127b636
--- /dev/null
+++ b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/BinaryStringFactoryTest.java
@@ -0,0 +1,270 @@
+package com.mercedesbenz.sechub.commons.core.security.persistence;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+public class BinaryStringFactoryTest {
+ @Test
+ void create_from_string_no_type_given() {
+ /* prepare */
+ String string = "hello";
+
+ /* execute */
+ BinaryString binaryString = BinaryStringFactory.createFromString(string);
+
+ /* test */
+ assertNotNull(binaryString);
+ assertEquals(binaryString.getType(), BinaryStringEncodingType.BASE64);
+ assertTrue(binaryString instanceof Base64String);
+ assertEquals("aGVsbG8=", binaryString.toString());
+ }
+
+ @Test
+ void create_from_string_base64_type_given() {
+ /* prepare */
+ String string = "Hello π!";
+
+ /* execute */
+ BinaryString binaryString = BinaryStringFactory.createFromString(string, BinaryStringEncodingType.BASE64);
+
+ /* test */
+ assertNotNull(binaryString);
+ assertEquals(binaryString.getType(), BinaryStringEncodingType.BASE64);
+ assertTrue(binaryString instanceof Base64String);
+ assertEquals("SGVsbG8g8J+MjCE=", binaryString.toString());
+ }
+
+ @Test
+ void create_from_base64_no_type_given() {
+ /* prepare */
+ String string = "SGVsbG8g8J+MjCE=";
+
+ /* execute */
+ BinaryString binaryString = BinaryStringFactory.createFromBase64(string);
+
+ /* test */
+ assertNotNull(binaryString);
+ assertEquals(binaryString.getType(), BinaryStringEncodingType.BASE64);
+ assertTrue(binaryString instanceof Base64String);
+ assertEquals("SGVsbG8g8J+MjCE=", binaryString.toString());
+ }
+
+ @Test
+ void createFromBase64_null_input() {
+ /* execute */
+ Exception exception = assertThrows(IllegalArgumentException.class, () -> {
+ BinaryStringFactory.createFromBase64(null);
+ });
+
+ /* test */
+ assertEquals("String cannot be null.", exception.getMessage());
+ }
+
+ @Test
+ void createFromHex_null_input() {
+ /* execute */
+ Exception exception = assertThrows(IllegalArgumentException.class, () -> {
+ BinaryStringFactory.createFromHex(null);
+ });
+
+ /* test */
+ assertEquals("String cannot be null.", exception.getMessage());
+ }
+
+ @Test
+ void create_from_string_plain_type_given() {
+ /* prepare */
+ String string = "hello";
+
+ /* execute */
+ BinaryString binaryString = BinaryStringFactory.createFromString(string, BinaryStringEncodingType.PLAIN);
+
+ /* test */
+ assertNotNull(binaryString);
+ assertEquals(binaryString.getType(), BinaryStringEncodingType.PLAIN);
+ assertTrue(binaryString instanceof PlainString);
+ assertEquals("hello", binaryString.toString());
+ }
+
+ @Test
+ void create_from_string_hex_type_given() {
+ /* prepare */
+ String string = "hello";
+
+ /* execute */
+ BinaryString binaryString = BinaryStringFactory.createFromString(string, BinaryStringEncodingType.HEX);
+
+ /* test */
+ assertNotNull(binaryString);
+ assertEquals(BinaryStringEncodingType.HEX, binaryString.getType());
+ assertTrue(binaryString instanceof HexString);
+ assertEquals("68656c6c6f", binaryString.toString());
+ }
+
+ @Test
+ void create_from_string_no_type_given_and_input_null() {
+ /* prepare */
+ String string = null;
+
+ /* execute */
+ Exception exception = assertThrows(IllegalArgumentException.class, () -> {
+ BinaryStringFactory.createFromString(string);
+ });
+
+ /* test */
+ assertEquals("String cannot be null.", exception.getMessage());
+ }
+
+ @Test
+ void create_from_bytes_no_type_given() {
+ /* prepare */
+ byte[] bytes = new byte[] { 104, 101, 108, 108, 111 };
+
+ /* execute */
+ BinaryString binaryString = BinaryStringFactory.createFromBytes(bytes);
+
+ /* test */
+ assertNotNull(binaryString);
+ assertEquals(BinaryStringEncodingType.BASE64, binaryString.getType());
+ assertTrue(binaryString instanceof Base64String);
+ assertEquals("aGVsbG8=", binaryString.toString());
+ }
+
+ @Test
+ void create_from_bytes_base64_type_given() {
+ /* prepare */
+ byte[] bytes = new byte[] { 104, 101, 108, 108, 111 };
+
+ /* execute */
+ BinaryString binaryString = BinaryStringFactory.createFromBytes(bytes, BinaryStringEncodingType.BASE64);
+
+ /* test */
+ assertNotNull(binaryString);
+ assertEquals(BinaryStringEncodingType.BASE64, binaryString.getType());
+ assertTrue(binaryString instanceof Base64String);
+ assertEquals("aGVsbG8=", binaryString.toString());
+ }
+
+ @Test
+ void create_from_bytes_plain_type_given() {
+ /* prepare */
+ byte[] bytes = new byte[] { 104, 101, 108, 108, 111 };
+
+ /* execute */
+ BinaryString binaryString = BinaryStringFactory.createFromBytes(bytes, BinaryStringEncodingType.PLAIN);
+
+ /* test */
+ assertNotNull(binaryString);
+ assertEquals(BinaryStringEncodingType.PLAIN, binaryString.getType());
+ assertTrue(binaryString instanceof PlainString);
+ assertEquals("hello", binaryString.toString());
+ }
+
+ @Test
+ void create_from_bytes_hex_type_given() {
+ /* prepare */
+ byte[] bytes = new byte[] { 104, 101, 108, 108, 111 };
+
+ /* execute */
+ BinaryString binaryString = BinaryStringFactory.createFromBytes(bytes, BinaryStringEncodingType.HEX);
+
+ /* test */
+ assertNotNull(binaryString);
+ assertEquals(BinaryStringEncodingType.HEX, binaryString.getType());
+ assertTrue(binaryString instanceof HexString);
+ assertEquals("68656c6c6f", binaryString.toString());
+ }
+
+ @Test
+ void create_from_bytes_no_type_given_and_input_null() {
+ /* prepare */
+ byte[] bytes = null;
+
+ /* execute */
+ Exception exception = assertThrows(IllegalArgumentException.class, () -> {
+ BinaryStringFactory.createFromBytes(bytes);
+ });
+
+ /* test */
+ assertEquals("String cannot be null.", exception.getMessage());
+ }
+
+ @Test
+ void createFromHex_from_string_plain_type_given() {
+ /* prepare */
+ String string = "68656c6c6f";
+
+ /* execute */
+ BinaryString binaryString = BinaryStringFactory.createFromHex(string, BinaryStringEncodingType.PLAIN);
+
+ /* test */
+ assertNotNull(binaryString);
+ assertEquals(BinaryStringEncodingType.PLAIN, binaryString.getType());
+ assertTrue(binaryString instanceof PlainString);
+ assertEquals("hello", binaryString.toString());
+ }
+
+ @Test
+ void createFromHex_base64_type_given() {
+ /* prepare */
+ String string = "68656c6c6f";
+
+ /* execute */
+ BinaryString binaryString = BinaryStringFactory.createFromHex(string, BinaryStringEncodingType.BASE64);
+
+ /* test */
+ assertNotNull(binaryString);
+ assertEquals(BinaryStringEncodingType.BASE64, binaryString.getType());
+ assertTrue(binaryString instanceof Base64String);
+ assertEquals("aGVsbG8=", binaryString.toString());
+ }
+
+ @Test
+ void createFromHex_no_type_given() {
+ /* prepare */
+ String string = "68656c6c6f";
+
+ /* execute */
+ BinaryString binaryString = BinaryStringFactory.createFromHex(string);
+
+ /* test */
+ assertNotNull(binaryString);
+ assertEquals(BinaryStringEncodingType.BASE64, binaryString.getType());
+ assertTrue(binaryString instanceof Base64String);
+ assertEquals("aGVsbG8=", binaryString.toString());
+ }
+
+ @Test
+ void createFromBase64_plain_type_given() {
+ /* prepare */
+ String string = "aGVsbG8=";
+
+ /* execute */
+ BinaryString binaryString = BinaryStringFactory.createFromBase64(string, BinaryStringEncodingType.PLAIN);
+
+ /* test */
+ assertNotNull(binaryString);
+ assertEquals(BinaryStringEncodingType.PLAIN, binaryString.getType());
+ assertTrue(binaryString instanceof PlainString);
+ assertEquals("hello", binaryString.toString());
+ }
+
+ @Test
+ void createFromBase64_hex_type_given() {
+ /* prepare */
+ String string = "aGVsbG8=";
+
+ /* execute */
+ BinaryString binaryString = BinaryStringFactory.createFromBase64(string, BinaryStringEncodingType.HEX);
+
+ /* test */
+ assertNotNull(binaryString);
+ assertEquals(BinaryStringEncodingType.HEX, binaryString.getType());
+ assertTrue(binaryString instanceof HexString);
+ assertEquals("68656c6c6f", binaryString.toString());
+ }
+}
diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/HexStringTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/HexStringTest.java
new file mode 100644
index 0000000000..2723e8e9fe
--- /dev/null
+++ b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/HexStringTest.java
@@ -0,0 +1,110 @@
+package com.mercedesbenz.sechub.commons.core.security.persistence;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+public class HexStringTest {
+ @Test
+ void from_string() {
+ /* prepare */
+ String string = "Hello";
+ String expectedString = "48656c6c6f";
+
+ /* execute + test */
+ assertEquals(expectedString, new HexString(string).toString());
+ }
+
+ @Test
+ void from_string_null_throw_illegal_argument_exception() {
+ /* execute */
+ Exception exception = assertThrows(IllegalArgumentException.class, () -> {
+ new HexString((String) null);
+ });
+
+ /* test */
+ assertEquals("String cannot be null.", exception.getMessage());
+ }
+
+ @Test
+ void from_string_unicode() {
+ /* prepare */
+ String string = "Hello π¦";
+ String expectedString = "48656c6c6f20f09fa684";
+
+ /* execute + test */
+ assertEquals(expectedString, new HexString(string).toString());
+ }
+
+ @Test
+ void from_bytes() {
+ /* prepare */
+ byte[] bytes = "Hello".getBytes();
+ String expectedString = "48656c6c6f";
+
+ /* execute + test */
+ assertEquals(expectedString, new HexString(bytes).toString());
+ }
+
+ @Test
+ void from_bytes_unicode() {
+ /* prepare */
+ byte[] bytes = "Hello π¦".getBytes();
+ String expectedString = "48656c6c6f20f09fa684";
+
+ /* execute + test */
+ assertEquals(expectedString, new HexString(bytes).toString());
+ }
+
+ @Test
+ void from_bytes_null_throw_illegal_argument_exception() {
+ /* execute */
+ Exception exception = assertThrows(IllegalArgumentException.class, () -> {
+ new HexString((byte[]) null);
+ });
+
+ /* test */
+ assertEquals("Byte array cannot be null.", exception.getMessage());
+ }
+
+ @Test
+ void toString_unicode_test() {
+ /* prepare */
+ String string = "Hello π¦";
+ String expectedString = "48656c6c6f20f09fa684";
+
+ /* execute + test */
+ assertEquals(expectedString, new HexString(string).toString());
+ }
+
+ @Test
+ void equals_test() {
+ /* prepare */
+ String string = "I like π";
+
+ /* execute */
+ HexString hexString = new HexString(string);
+ HexString hexString2 = new HexString(string);
+
+ /* test */
+ assertEquals(hexString, hexString2);
+ assertTrue(hexString.equals(hexString2));
+ assertEquals(hexString.hashCode(), hexString2.hashCode());
+ }
+
+ @Test
+ void test_immutablitiy() {
+ /* prepare */
+ String string = "A πΊ in a π skin";
+
+ /* execute */
+ HexString hexString = new HexString(string);
+
+ /* test */
+ assertFalse(string == hexString.toString());
+ assertFalse(string.getBytes() == hexString.getBytes());
+ }
+}
diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/NoneCipherTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/NoneCipherTest.java
new file mode 100644
index 0000000000..16d34407b2
--- /dev/null
+++ b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/NoneCipherTest.java
@@ -0,0 +1,115 @@
+package com.mercedesbenz.sechub.commons.core.security.persistence;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+
+import org.junit.jupiter.api.Test;
+
+public class NoneCipherTest {
+ private BinaryString initializationVector = BinaryStringFactory.createFromString("Hello");
+
+ @Test
+ void encrypt_null_iv() throws InvalidKeyException, InvalidAlgorithmParameterException {
+ /* prepare */
+ String plaintext = "This is plaintext";
+ PersistenceCipher cipher = NoneCipher.create(null);
+
+ /* execute */
+ BinaryString ciphertext = cipher.encrypt(plaintext, null);
+
+ /* test */
+ assertEquals(plaintext, ciphertext.toString());
+ }
+
+ @Test
+ void encrypt_with_iv() throws InvalidKeyException, InvalidAlgorithmParameterException {
+ /* prepare */
+ String plaintext = "This is plaintext";
+ PersistenceCipher cipher = NoneCipher.create(null);
+
+ /* execute */
+ BinaryString ciphertext = cipher.encrypt(plaintext, initializationVector);
+
+ /* test */
+ assertEquals(plaintext, ciphertext.toString());
+ }
+
+ @Test
+ void decrypt_null_iv() throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
+ /* prepare */
+ String expectedPlaintext = "This is plaintext";
+ BinaryString ciphertext = new PlainString(expectedPlaintext);
+ PersistenceCipher cipher = NoneCipher.create(null);
+
+ /* execute */
+ String plaintext = cipher.decrypt(ciphertext, null);
+
+ /* test */
+ assertEquals(expectedPlaintext, plaintext);
+ }
+
+ @Test
+ void decrypt_with_iv() throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
+ /* prepare */
+ String expectedPlaintext = "This is plaintext with a π!";
+ BinaryString ciphertext = new PlainString(expectedPlaintext);
+ PersistenceCipher cipher = NoneCipher.create(null);
+
+ /* execute */
+ String plaintext = cipher.decrypt(ciphertext, initializationVector);
+
+ /* test */
+ assertEquals(expectedPlaintext, plaintext);
+ }
+
+ @Test
+ void getCipher_null() throws InvalidKeyException {
+ /* prepare */
+ PersistenceCipher cipher = NoneCipher.create(null);
+
+ /* execute + test */
+ assertEquals(PersistenceCipherType.NONE, cipher.getCipherType());
+ }
+
+ @Test
+ void getCipher_string() throws InvalidKeyException {
+ /* prepare */
+ PersistenceCipher cipher = NoneCipher.create(new Base64String("Hello"));
+
+ /* execute + test */
+ assertEquals(PersistenceCipherType.NONE, cipher.getCipherType());
+ }
+
+ @Test
+ void generateNewInitializationVector_default_type() throws InvalidKeyException {
+ /* prepare */
+ PersistenceCipher cipher = NoneCipher.create(new Base64String("Hello"));
+
+ /* execute */
+ BinaryString initializationVector = cipher.generateNewInitializationVector();
+
+ /* test */
+ assertEquals(PersistenceCipherType.NONE, cipher.getCipherType());
+ assertEquals(BinaryStringEncodingType.PLAIN, initializationVector.getType());
+ assertEquals("", initializationVector.toString());
+ }
+
+ @Test
+ void generateNewInitializationVector_type_provided() throws InvalidKeyException {
+ /* prepare */
+ PersistenceCipher cipher = NoneCipher.create(new Base64String("Hello"));
+
+ /* execute */
+ BinaryString initializationVector = cipher.generateNewInitializationVector(BinaryStringEncodingType.HEX);
+
+ /* test */
+ assertEquals(PersistenceCipherType.NONE, cipher.getCipherType());
+ assertEquals(BinaryStringEncodingType.HEX, initializationVector.getType());
+ assertEquals("", initializationVector.toString());
+ }
+}
diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherFactoryTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherFactoryTest.java
new file mode 100644
index 0000000000..3b9db36f3c
--- /dev/null
+++ b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/PersistenceCipherFactoryTest.java
@@ -0,0 +1,97 @@
+package com.mercedesbenz.sechub.commons.core.security.persistence;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.security.InvalidKeyException;
+
+import org.junit.jupiter.api.Test;
+
+public class PersistenceCipherFactoryTest {
+ @Test
+ void create_none_cipher() throws InvalidKeyException {
+ /* prepare */
+ PersistenceCipherType cipherType = PersistenceCipherType.NONE;
+ Base64String secret = new Base64String("topSecret");
+
+ /* execute */
+ PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, secret);
+
+ /* test */
+ assertEquals(cipher.getCipherType(), cipherType);
+ }
+
+ @Test
+ void create_aes_gcm_siv_256() throws InvalidKeyException {
+ /* prepare */
+ PersistenceCipherType cipherType = PersistenceCipherType.AES_GCM_SIV_256;
+ BinaryString secret = new PlainString("a".repeat(32));
+
+ /* execute */
+ PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, secret);
+
+ /* test */
+ assertEquals(cipher.getCipherType(), cipherType);
+ }
+
+ @Test
+ void create_aes_gcm_siv_128() throws InvalidKeyException {
+ /* prepare */
+ PersistenceCipherType cipherType = PersistenceCipherType.AES_GCM_SIV_128;
+ BinaryString secret = new HexString("a".repeat(16));
+
+ /* execute */
+ PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, secret);
+
+ /* test */
+ assertEquals(cipher.getCipherType(), cipherType);
+ }
+
+ @Test
+ void create_initializationVector_aes_gcm_siv_256() throws InvalidKeyException {
+ /* prepare */
+ PersistenceCipherType cipherType = PersistenceCipherType.AES_GCM_SIV_256;
+ BinaryString secret = new PlainString("a".repeat(32));
+ PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, secret);
+
+ /* execute */
+ BinaryString initializationVector = cipher.generateNewInitializationVector();
+
+ /* test */
+ assertEquals(cipher.getCipherType(), cipherType);
+ assertNotNull(initializationVector);
+ assertEquals(AesGcmSiv.IV_LENGTH_IN_BYTES, initializationVector.getBytes().length);
+ }
+
+ @Test
+ void create_initializationVector_aes_gcm_siv_128() throws InvalidKeyException {
+ /* prepare */
+ PersistenceCipherType cipherType = PersistenceCipherType.AES_GCM_SIV_128;
+ BinaryString secret = new PlainString("a".repeat(16));
+ PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, secret);
+
+ /* execute */
+ BinaryString initializationVector = cipher.generateNewInitializationVector();
+
+ /* test */
+ assertEquals(cipher.getCipherType(), cipherType);
+ assertNotNull(initializationVector);
+ assertEquals(AesGcmSiv.IV_LENGTH_IN_BYTES, initializationVector.getBytes().length);
+ }
+
+ @Test
+ void create_initializationVector_none_cipher() throws InvalidKeyException {
+ /* prepare */
+ PersistenceCipherType cipherType = PersistenceCipherType.NONE;
+ BinaryString secret = new PlainString("a".repeat(3));
+ PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, secret);
+
+ /* execute */
+ BinaryString initializationVector = cipher.generateNewInitializationVector();
+
+ /* test */
+ assertEquals(cipher.getCipherType(), cipherType);
+ assertNotNull(initializationVector);
+ assertEquals("", initializationVector.toString());
+ }
+}
diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/PlainStringTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/PlainStringTest.java
new file mode 100644
index 0000000000..2697313a9f
--- /dev/null
+++ b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/PlainStringTest.java
@@ -0,0 +1,104 @@
+package com.mercedesbenz.sechub.commons.core.security.persistence;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+public class PlainStringTest {
+ @Test
+ void from_string() {
+ /* prepare */
+ String string = "Hello";
+
+ /* execute + test */
+ assertEquals(string, new PlainString(string).toString());
+ }
+
+ @Test
+ void from_string_null_throw_illegal_argument_exception() {
+ /* execute */
+ Exception exception = assertThrows(IllegalArgumentException.class, () -> {
+ new PlainString((String) null);
+ });
+
+ /* test */
+ assertEquals("String cannot be null.", exception.getMessage());
+ }
+
+ @Test
+ void from_string_unicode() {
+ /* prepare */
+ String string = "Hello π¦";
+
+ /* execute + test */
+ assertEquals(string, new PlainString(string).toString());
+ }
+
+ @Test
+ void from_bytes() {
+ /* prepare */
+ byte[] bytes = "Hello".getBytes();
+
+ /* execute + test */
+ assertEquals(new String(bytes), new PlainString(bytes).toString());
+ }
+
+ @Test
+ void from_bytes_unicode() {
+ /* prepare */
+ byte[] bytes = "Hello π¦".getBytes();
+
+ /* execute + test */
+ assertEquals(new String(bytes), new PlainString(bytes).toString());
+ }
+
+ @Test
+ void from_bytes_null_throw_illegal_argument_exception() {
+ Exception exception = assertThrows(IllegalArgumentException.class, () -> {
+ new PlainString((byte[]) null);
+ });
+
+ /* execute + test */
+ assertEquals("Byte array cannot be null.", exception.getMessage());
+ }
+
+ @Test
+ void toString_unicode_test() {
+ /* prepare */
+ String string = "Hello π¦";
+
+ /* execute + test */
+ assertEquals(string, new PlainString(string).toString());
+ }
+
+ @Test
+ void equals_test() {
+ /* prepare */
+ String string = "I like π";
+
+ /* execute */
+ PlainString plainString1 = new PlainString(string);
+ PlainString plainString2 = new PlainString(string);
+
+ /* test */
+ assertEquals(plainString1, plainString2);
+ assertTrue(plainString1.equals(plainString2));
+ assertEquals(plainString1.hashCode(), plainString2.hashCode());
+ }
+
+ @Test
+ void test_immutablitiy() {
+ /* prepare */
+ String string = "A πΊ in a π skin";
+
+ /* execute */
+ PlainString plainString = new PlainString(string);
+
+ /* test */
+ assertFalse(string == plainString.toString());
+ assertFalse(string.getBytes() == plainString.getBytes());
+ }
+}
diff --git a/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/RotationTest.java b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/RotationTest.java
new file mode 100644
index 0000000000..1c5a861415
--- /dev/null
+++ b/sechub-commons-core/src/test/java/com/mercedesbenz/sechub/commons/core/security/persistence/RotationTest.java
@@ -0,0 +1,299 @@
+package com.mercedesbenz.sechub.commons.core.security.persistence;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+
+import org.junit.jupiter.api.Test;
+
+public class RotationTest {
+ @Test
+ void secret_rotation_cipher_none()
+ throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
+ /* prepare */
+ BinaryString currentSecret = new PlainString("abc");
+ BinaryString newSecret = new PlainString("bca");
+ BinaryString cipherText = new PlainString("hello");
+ BinaryString initializationVector = new PlainString("iv");
+
+ PersistenceCipherType cipherType = PersistenceCipherType.NONE;
+ RotationStrategy rotation = RotationStrategy.createSecretRotationStrategy(currentSecret, newSecret, cipherType);
+
+ /* execute */
+ BinaryString newCipherText = rotation.rotate(cipherText, initializationVector);
+
+ /* test */
+ assertNotNull(rotation);
+ assertEquals(cipherText, newCipherText);
+ assertTrue(rotation.isSecretRotationStrategy());
+ assertFalse(rotation.isCipherRotationStrategy());
+ assertEquals(PersistenceCipherType.NONE, rotation.getCurrentCipher());
+ assertEquals(PersistenceCipherType.NONE, rotation.getNewCipher());
+ }
+
+ @Test
+ void secret_rotation_cipher_aes_gcm_siv_128()
+ throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
+ /* prepare */
+ String expectedPlainText = "Hello, I am βΊοΈ 4 you.";
+ BinaryString expectedCipherText = BinaryStringFactory.createFromBase64("8gWa4YPRlshBZCml8a0xvAJ1Y1mNU9iovclpvOhwVj4XiiaZvWKHWkU=",
+ BinaryStringEncodingType.BASE64);
+ BinaryString currentSecret = new PlainString("a".repeat(16));
+ BinaryString newSecret = new PlainString("z".repeat(16));
+ BinaryString cipherText = BinaryStringFactory.createFromBase64("DuwfqoAJrzZiK3u5v0XEnARPOjLugpobvWCxfTV6Y1FkAOECII/J8RU=",
+ BinaryStringEncodingType.BASE64);
+ BinaryString initializationVector = new PlainString("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES));
+
+ PersistenceCipherType cipherType = PersistenceCipherType.AES_GCM_SIV_128;
+ RotationStrategy rotation = RotationStrategy.createSecretRotationStrategy(currentSecret, newSecret, cipherType);
+
+ /* execute */
+ BinaryString newCipherText = rotation.rotate(cipherText, initializationVector);
+
+ /* test */
+ assertNotNull(rotation);
+ assertEquals(expectedCipherText, newCipherText);
+ assertTrue(rotation.isSecretRotationStrategy());
+ assertFalse(rotation.isCipherRotationStrategy());
+ assertEquals(PersistenceCipherType.AES_GCM_SIV_128, rotation.getCurrentCipher());
+ assertEquals(PersistenceCipherType.AES_GCM_SIV_128, rotation.getNewCipher());
+
+ PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, newSecret);
+ String plainText = cipher.decrypt(newCipherText, initializationVector);
+
+ assertEquals(expectedPlainText, plainText);
+ }
+
+ @Test
+ void secret_rotation_cipher_aes_gcm_siv_256()
+ throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
+ /* prepare */
+ String expectedPlainText = "Hello, I am βΊοΈ 4 you.";
+ BinaryString expectedCipherText = BinaryStringFactory.createFromBase64("y5/I7wBmKwqKKazHrRnj2j6v9uuySiHJ9MK3pUvWjkUIC/3jDSFJLPI=",
+ BinaryStringEncodingType.BASE64);
+ BinaryString currentSecret = new PlainString("a".repeat(32));
+ BinaryString newSecret = new PlainString("z".repeat(32));
+ BinaryString cipherText = BinaryStringFactory.createFromBase64("KSIAj+JAD95o77GF91GQShbuh0dyuIMdDjhX1VQFi7DW2KSutD0uOt8=",
+ BinaryStringEncodingType.BASE64);
+ BinaryString initializationVector = new HexString("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES));
+
+ PersistenceCipherType cipherType = PersistenceCipherType.AES_GCM_SIV_256;
+ RotationStrategy rotation = RotationStrategy.createSecretRotationStrategy(currentSecret, newSecret, cipherType);
+
+ /* execute */
+ BinaryString newCipherText = rotation.rotate(cipherText, initializationVector);
+
+ /* test */
+ assertNotNull(rotation);
+ assertEquals(expectedCipherText, newCipherText);
+ assertTrue(rotation.isSecretRotationStrategy());
+ assertFalse(rotation.isCipherRotationStrategy());
+ assertEquals(PersistenceCipherType.AES_GCM_SIV_256, rotation.getCurrentCipher());
+ assertEquals(PersistenceCipherType.AES_GCM_SIV_256, rotation.getNewCipher());
+
+ PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, newSecret);
+ String plainText = cipher.decrypt(newCipherText, initializationVector);
+
+ assertEquals(expectedPlainText, plainText);
+ }
+
+ @Test
+ void cipher_and_secret_rotation_cipher_none_to_cipher_aes_gcm_siv_128()
+ throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
+ /* prepare */
+ String expectedPlainText = "Hello, I am βΊοΈ 4 you.";
+ BinaryString expectedCipherText = BinaryStringFactory.createFromBase64("DuwfqoAJrzZiK3u5v0XEnARPOjLugpobvWCxfTV6Y1FkAOECII/J8RU=",
+ BinaryStringEncodingType.BASE64);
+ BinaryString currentSecret = new PlainString("abc");
+ BinaryString newSecret = new PlainString("a".repeat(16));
+ BinaryString cipherText = new PlainString(expectedPlainText);
+ BinaryString initializationVector = new PlainString("i".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES));
+
+ PersistenceCipherType currentCipherType = PersistenceCipherType.NONE;
+ PersistenceCipherType newCipherType = PersistenceCipherType.AES_GCM_SIV_128;
+ RotationStrategy rotation = RotationStrategy.createCipherAndSecretRotationStrategy(currentSecret, newSecret, currentCipherType, newCipherType);
+
+ /* execute */
+ BinaryString newCipherText = rotation.rotate(cipherText, initializationVector);
+
+ /* test */
+ assertNotNull(rotation);
+ assertEquals(expectedCipherText, newCipherText);
+ assertTrue(rotation.isSecretRotationStrategy());
+ assertTrue(rotation.isCipherRotationStrategy());
+ assertEquals(PersistenceCipherType.NONE, rotation.getCurrentCipher());
+ assertEquals(PersistenceCipherType.AES_GCM_SIV_128, rotation.getNewCipher());
+
+ PersistenceCipher cipher = PersistenceCipherFactory.create(newCipherType, newSecret);
+ String plainText = cipher.decrypt(newCipherText, initializationVector);
+
+ assertEquals(expectedPlainText, plainText);
+ }
+
+ @Test
+ void cipher_and_secret_rotation_cipher_aes_gcm_siv_128_to_cipher_aes_gcm_siv_256()
+ throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
+ /* prepare */
+ String expectedPlainText = "Hello π, welcome to π.";
+
+ BinaryString expectedCipherText = BinaryStringFactory
+ .createFromHex("d09be77ddd8dbac86b69b0f5f554faef740555ac93f12aedfdf62700e4ea3016e03dacc105f32f114791d8e6", BinaryStringEncodingType.BASE64);
+ BinaryString currentSecret = new Base64String("ππππ");
+ BinaryString newSecret = new HexString("π₯¦π₯π₯π«π₯π«π½π");
+ BinaryString cipherText = BinaryStringFactory.createFromBase64("Qu7ICJBGMw9dAPPBWx86e5bjOq3YKC+x25n/YkluWZAGdSna08tKaE78pMk=",
+ BinaryStringEncodingType.BASE64);
+ BinaryString initializationVector = new PlainString("π§
".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES / 4));
+
+ PersistenceCipherType currentCipherType = PersistenceCipherType.AES_GCM_SIV_128;
+ PersistenceCipherType newCipherType = PersistenceCipherType.AES_GCM_SIV_256;
+ RotationStrategy rotation = RotationStrategy.createCipherAndSecretRotationStrategy(currentSecret, newSecret, currentCipherType, newCipherType);
+
+ /* execute */
+ BinaryString newCipherText = rotation.rotate(cipherText, initializationVector);
+
+ /* test */
+ assertNotNull(rotation);
+ assertEquals(expectedCipherText, newCipherText);
+ assertEquals(PersistenceCipherType.AES_GCM_SIV_128, rotation.getCurrentCipher());
+ assertEquals(PersistenceCipherType.AES_GCM_SIV_256, rotation.getNewCipher());
+ assertTrue(rotation.isSecretRotationStrategy());
+ assertTrue(rotation.isCipherRotationStrategy());
+
+ PersistenceCipher cipher = PersistenceCipherFactory.create(newCipherType, newSecret);
+ String plainText = cipher.decrypt(newCipherText, initializationVector);
+
+ assertEquals(expectedPlainText, plainText);
+ }
+
+ @Test
+ void cipher_and_secret_rotation_aes_gcm_siv_256_to_none()
+ throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
+ /* prepare */
+ String expectedPlainText = "Hello π, welcome to π.";
+
+ BinaryString expectedCipherText = BinaryStringFactory.createFromString(expectedPlainText, BinaryStringEncodingType.PLAIN);
+ BinaryString currentSecret = new HexString("π₯¦π₯π₯π«π₯π«π½π");
+ BinaryString newSecret = new HexString("abc");
+ BinaryString cipherText = BinaryStringFactory.createFromHex("d09be77ddd8dbac86b69b0f5f554faef740555ac93f12aedfdf62700e4ea3016e03dacc105f32f114791d8e6",
+ BinaryStringEncodingType.HEX);
+ BinaryString initializationVector = new Base64String("π§
".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES / 4));
+
+ PersistenceCipherType currentCipherType = PersistenceCipherType.AES_GCM_SIV_256;
+ PersistenceCipherType newCipherType = PersistenceCipherType.NONE;
+ RotationStrategy rotation = RotationStrategy.createCipherAndSecretRotationStrategy(currentSecret, newSecret, currentCipherType, newCipherType);
+
+ /* execute */
+ BinaryString newCipherText = rotation.rotate(cipherText, initializationVector);
+
+ /* test */
+ assertNotNull(rotation);
+ assertEquals(expectedCipherText, newCipherText);
+ assertEquals(PersistenceCipherType.AES_GCM_SIV_256, rotation.getCurrentCipher());
+ assertEquals(PersistenceCipherType.NONE, rotation.getNewCipher());
+ assertTrue(rotation.isSecretRotationStrategy());
+ assertTrue(rotation.isCipherRotationStrategy());
+
+ PersistenceCipher cipher = PersistenceCipherFactory.create(newCipherType, newSecret);
+ String plainText = cipher.decrypt(newCipherText, initializationVector);
+
+ assertEquals(expectedPlainText, plainText);
+ }
+
+ @Test
+ void initializationVector_rotation_cipher_none()
+ throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
+ /* prepare */
+ BinaryString secret = new PlainString("abc");
+ BinaryString cipherText = new PlainString("hello");
+ BinaryString initializationVector = new PlainString("vi");
+ BinaryString newInitializationVector = new PlainString("iv");
+
+ PersistenceCipherType cipherType = PersistenceCipherType.NONE;
+ RotationStrategy rotation = RotationStrategy.createInitializationVectorOnlyRotationStrategy(secret, cipherType);
+
+ /* execute */
+ BinaryString newCipherText = rotation.rotate(cipherText, initializationVector, newInitializationVector);
+
+ /* test */
+ assertNotNull(rotation);
+ assertEquals(cipherText, newCipherText);
+ assertFalse(rotation.isSecretRotationStrategy());
+ assertFalse(rotation.isCipherRotationStrategy());
+ assertEquals(PersistenceCipherType.NONE, rotation.getCurrentCipher());
+ assertEquals(PersistenceCipherType.NONE, rotation.getNewCipher());
+ }
+
+ @Test
+ void initializationVector_rotation_aes_gcm_siv_128()
+ throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
+ /* prepare */
+ String expectedPlainText = "The quick brown fox jumps over the lazy dog.";
+
+ BinaryString expectedCipherText = BinaryStringFactory
+ .createFromBase64("roRrChR9D/VIaebHZgxGG3tYuCR+yXjPleQr4bDZ7B5qsVzC7EnXTi3rT3qDv09t62W/W4V9CIfQRlWs");
+ BinaryString secret = new PlainString("d".repeat(16));
+ BinaryString cipherText = BinaryStringFactory.createFromBase64("h3Pz6Kue6V4CUYtkZdtWml0vmXkEXjSpXyud98Z3NJjNGk+qFYJmpLCoyO+qBxWKQfxbmnmup9+vjYJQ");
+ BinaryString initializationVector = new HexString("vi".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES / 2));
+ BinaryString newInitializationVector = new Base64String("iv".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES / 2));
+
+ PersistenceCipherType cipherType = PersistenceCipherType.AES_GCM_SIV_128;
+ RotationStrategy rotation = RotationStrategy.createInitializationVectorOnlyRotationStrategy(secret, cipherType);
+
+ /* execute */
+ BinaryString newCipherText = rotation.rotate(cipherText, initializationVector, newInitializationVector);
+
+ /* test */
+ assertNotNull(rotation);
+ assertEquals(expectedCipherText, newCipherText);
+ assertFalse(rotation.isSecretRotationStrategy());
+ assertFalse(rotation.isCipherRotationStrategy());
+ assertEquals(PersistenceCipherType.AES_GCM_SIV_128, rotation.getCurrentCipher());
+ assertEquals(PersistenceCipherType.AES_GCM_SIV_128, rotation.getNewCipher());
+
+ PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, secret);
+ String plainText = cipher.decrypt(newCipherText, newInitializationVector);
+
+ assertEquals(expectedPlainText, plainText);
+ }
+
+ @Test
+ void initializationVector_rotation_aes_gcm_siv_256()
+ throws InvalidKeyException, IllegalArgumentException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
+ /* prepare */
+ String expectedPlainText = "Victor jagt zwΓΆlf BoxkΓ€mpfer quer ΓΌber den groΓen Sylter Deich";
+
+ BinaryString expectedCipherText = BinaryStringFactory
+ .createFromBase64("0eG35+7x+nr4MbsgRsTqOEjRReF6JzM2thWe+3zHleta1cCXzyCxQJ5Jaxeq7TnYDDg3nR/RcQdc00UlqEJw3WODaffeETDJ0bk5LfP19FWr/g==");
+ BinaryString secret = new PlainString("ThisIsAStringWith32Characters!?!");
+ BinaryString cipherText = BinaryStringFactory
+ .createFromBase64("AfVfrXdv8JImZ9qrpO7vOJRwveOaYRJNsQaeJiuDg5nX3ZT13EwH7CXlZa8IBNvhrqUvEn3w0L0J+JjYVYPxh80CqCpgjbMl4a8Ql5eyqKQIZQ==");
+ BinaryString initializationVector = new HexString("vi".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES / 2));
+ BinaryString newInitializationVector = new Base64String("iv".repeat(AesGcmSiv.IV_LENGTH_IN_BYTES / 2));
+
+ PersistenceCipherType cipherType = PersistenceCipherType.AES_GCM_SIV_256;
+ RotationStrategy rotation = RotationStrategy.createInitializationVectorOnlyRotationStrategy(secret, cipherType);
+
+ /* execute */
+ BinaryString newCipherText = rotation.rotate(cipherText, initializationVector, newInitializationVector);
+
+ /* test */
+ assertNotNull(rotation);
+ assertEquals(expectedCipherText, newCipherText);
+ assertFalse(rotation.isSecretRotationStrategy());
+ assertFalse(rotation.isCipherRotationStrategy());
+ assertEquals(PersistenceCipherType.AES_GCM_SIV_256, rotation.getCurrentCipher());
+ assertEquals(PersistenceCipherType.AES_GCM_SIV_256, rotation.getNewCipher());
+
+ PersistenceCipher cipher = PersistenceCipherFactory.create(cipherType, secret);
+ String plainText = cipher.decrypt(newCipherText, newInitializationVector);
+
+ assertEquals(expectedPlainText, plainText);
+ }
+}