Work in progress ...

This commit is contained in:
Konrad Neitzel
2025-11-30 18:58:05 +01:00
parent 9bbddaaae9
commit 5503dd192e
64 changed files with 4855 additions and 4252 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
@@ -2,20 +2,26 @@ package de.neitzel.encryption;
import com.idealista.fpe.config.Alphabet;
public abstract class BaseAlphabet implements Alphabet {
/**
* Base implementation of the {@link Alphabet} interface providing common helper methods
* for working with character sets and validating characters against the alphabet.
*/
public interface BaseAlphabet extends Alphabet {
/**
* Replaces illegal characters of a string with a given replacement character
* @param text Text to check.
* @param replacement Replacement character.
* @return String in which all characters was replaced.
* Replaces illegal characters of a string with a given replacement character.
*
* @param text Text to check; may not be null
* @param replacement Replacement character to use for characters not present in the alphabet
* @return a new string in which every invalid character was replaced with the provided replacement
* @throws IllegalArgumentException if {@code replacement} is not a valid character in the alphabet
*/
public String replaceIllegalCharacters(String text, char replacement) {
default String replaceIllegalCharacters(String text, char replacement) {
// Validate
if (!isValidCharacter(replacement)) throw new IllegalArgumentException("replacement");
StringBuilder result = new StringBuilder();
for (char ch: text.toCharArray()) {
if (isValidCharacter(ch)){
for (char ch : text.toCharArray()) {
if (isValidCharacter(ch)) {
result.append(ch);
} else {
result.append(replacement);
@@ -26,10 +32,11 @@ public abstract class BaseAlphabet implements Alphabet {
/**
* Checks if a given Character is part of the alphabet.
*
* @param character Character to check.
* @return true if character is valid, else false.
*/
public boolean isValidCharacter(char character) {
default boolean isValidCharacter(char character) {
// Compare with all characters
for (char ch : availableCharacters()) {
if (ch == character) return true;
@@ -7,49 +7,53 @@ import com.idealista.fpe.config.LengthRange;
import javax.crypto.KeyGenerator;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
import java.security.SecureRandom;
/**
* Helper class to deal with FF1Encryption using com.idealista:format-preserving-encryption:1.0.0.
*/
@SuppressWarnings("unused")
public class FF1Encryption {
/**
* Key to use for all encryption / unencryption
*/
private byte[] key;
private static final SecureRandom RANDOM = new SecureRandom();
/**
* Should to small strings be ignored?
*
* If this is set to true, small strings (less than 2 characters) will not be encrypted!
* Key to use for all encryption / unencryption.
*/
private boolean ignoreToShortStrings;
private final byte[] key;
/**
* tweak to use for encryption
* Should too small strings be ignored?
* If this is set to true, small strings (less than {@code minLength} characters)
* will not be encrypted or decrypted and will be returned unchanged.
*/
private byte[] tweak;
private final boolean ignoreToShortStrings;
/**
* Domain used for encryption
* Tweak to use for encryption.
*/
private Domain domain;
private final byte[] tweak;
/**
* Format preserving encryption to use.
* Domain used for encryption.
*/
private FormatPreservingEncryption encryption;
private final Domain domain;
/**
* Minimum length of a string.
* Format preserving encryption implementation to use.
*/
private int minLength;
private final FormatPreservingEncryption encryption;
/**
* Minimum length of a string for which encryption/decryption will be applied.
*/
private final int minLength;
/**
* Creates a new instance of FF1Encryption
* @param key AES key to use.
* @param tweak tweak to use for encryption / decryption
*
* @param key AES key to use.
* @param tweak tweak to use for encryption / decryption
* @param ignoreToShortStrings Ignore strings that are to short.
*/
public FF1Encryption(byte[] key, byte[] tweak, boolean ignoreToShortStrings) {
@@ -58,12 +62,13 @@ public class FF1Encryption {
/**
* Creates a new instance of FF1Encryption
* @param key AES key to use.
* @param tweak tweak to use for encryption / decryption
*
* @param key AES key to use.
* @param tweak tweak to use for encryption / decryption
* @param ignoreToShortStrings Ignore strings that are to short.
* @param domain Domain to use for encryption
* @param minLength Minimum length of string.
* @param maxLength Maximum length of string.
* @param domain Domain to use for encryption
* @param minLength Minimum length of string.
* @param maxLength Maximum length of string.
*/
public FF1Encryption(byte[] key, byte[] tweak, boolean ignoreToShortStrings, Domain domain, int minLength, int maxLength) {
this.key = key;
@@ -72,15 +77,45 @@ public class FF1Encryption {
this.domain = domain;
this.minLength = minLength;
encryption = FormatPreservingEncryptionBuilder
.ff1Implementation().withDomain(domain)
this.encryption = FormatPreservingEncryptionBuilder
.ff1Implementation()
.withDomain(domain)
.withDefaultPseudoRandomFunction(key)
.withLengthRange(new LengthRange(minLength, maxLength))
.build();
}
/**
* Creates a new AES key woth the given length.
*
* @param length Length of the key in bits. Must be 128, 192 or 256 bits
* @return Byte array of the new key.
*/
public static byte[] createNewKey(int length) {
try {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(length); // for example
return keyGen.generateKey().getEncoded();
} catch (NoSuchAlgorithmException ex) {
throw new RuntimeException(ex);
}
}
/**
* Creates a new tweak of the given length.
*
* @param length Number of bytes the new teeak should have.
* @return byte array with the new tweak.
*/
public static byte[] createNewTweak(int length) {
byte[] key = new byte[length];
RANDOM.nextBytes(key);
return key;
}
/**
* Encrypts a given text.
*
* @param plainText Unencrypted text.
* @return Encrypted text.
*/
@@ -97,6 +132,7 @@ public class FF1Encryption {
/**
* Decrypt a given text.
*
* @param cipherText Encrypted text.
* @return Decrypted text.
*/
@@ -110,31 +146,4 @@ public class FF1Encryption {
// Return decrypted text.
return encryption.decrypt(cipherText, tweak);
}
/**
* Creates a new AES key woth the given length.
* @param length Length of the key in bits. Must be 128, 192 or 256 bits
* @return Byte array of the new key.
*/
public static byte[] createNewKey(int length) {
try {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(length); // for example
return keyGen.generateKey().getEncoded();
} catch (NoSuchAlgorithmException ex) {
throw new RuntimeException(ex);
}
}
/**
* Creates a new tweak of the given length.
* @param length Number of bytes the new teeak should have.
* @return byte array with the new tweak.
*/
public static byte[] createNewTweak(int length) {
Random random = new Random();
byte[] key = new byte[length];
random.nextBytes(key);
return key;
}
}
@@ -3,12 +3,14 @@ package de.neitzel.encryption;
/**
* Main characters of the Alphabet.
*/
public class MainAlphabet extends BaseAlphabet {
public class MainAlphabet implements BaseAlphabet {
/**
* All characters we want to use.
* All characters that are part of the alphabet used by the MainDomain.
* This array includes lowercase letters, uppercase letters, digits, a set of
* special characters, and German-specific characters.
*/
private static final char[] ALL_CHARS = new char[] {
private static final char[] ALL_CHARS = new char[]{
// lowercase characters
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
@@ -21,16 +23,38 @@ public class MainAlphabet extends BaseAlphabet {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
// special characters
'-', '_', '?', ' ', '#', '+', '/', '*', '!', '\"', '§', '$', '%', '&', '(', ')', '=', '@',
'-', '_', '?', ' ', '#', '+', '/', '*', '!', '"', '§', '$', '%', '&', '(', ')', '=', '@',
'€', ',', ';', '.', ':', '<', '>', '|', '\'', '\\', '{', '}', '[', ']',
// german special characters
'ä', 'Ä', 'ö', 'Ö', 'ü', 'Ü', 'ß'
};
/**
* Creates a new MainAlphabet instance.
*
* <p>This constructor performs no runtime initialization because the
* alphabet is represented by the static {@code ALL_CHARS} array. Use
* {@link #availableCharacters()} to obtain the supported characters and
* {@link #radix()} to obtain the alphabet size.</p>
*
* <p> Important usage notes: </p>
* <ul>
* <li>The array returned by {@link #availableCharacters()} is the internal
* static array; callers must not modify it.</li>
* <li>The alphabet intentionally contains letters (lower and upper case),
* digits, common punctuation and a set of German-specific characters
* to support locale-sensitive operations.</li>
* </ul>
*/
public MainAlphabet() {
// default constructor only ...
}
/**
* Gets the available characters.
* @return
*
* @return an array containing all supported characters in the alphabet
*/
@Override
public char[] availableCharacters() {
@@ -39,7 +63,8 @@ public class MainAlphabet extends BaseAlphabet {
/**
* Gets the radix of the alphabet.
* @return
*
* @return the number of characters in the alphabet (radix)
*/
@Override
public Integer radix() {
@@ -4,26 +4,60 @@ import com.idealista.fpe.config.Alphabet;
import com.idealista.fpe.config.Domain;
import com.idealista.fpe.config.GenericTransformations;
/**
* Domain implementation that provides the alphabet and transformation helpers
* used by the format-preserving encryption implementation in this project.
* <p>
* This class delegates character-to-index and index-to-character transformations
* to a {@link GenericTransformations} helper configured with the {@link MainAlphabet}.
*/
public class MainDomain implements Domain {
/**
* The alphabet used by the domain for encoding/decoding characters.
*/
private Alphabet _alphabet;
/**
* Helper instance performing actual transformations between characters and indices.
*/
private GenericTransformations transformations;
/**
* Constructs a new {@code MainDomain} instance and initializes its alphabet and transformation helper.
*/
public MainDomain() {
_alphabet = new MainAlphabet();
transformations = new GenericTransformations(_alphabet.availableCharacters());
}
/**
* Returns the {@link Alphabet} implementation used by this domain.
*
* @return the alphabet used for transforming values in this domain
*/
@Override
public Alphabet alphabet() {
return _alphabet;
}
/**
* Transforms a string of characters into an array of integer indices according to the configured alphabet.
*
* @param data the input string to transform; may not be null
* @return an integer array representing the indices of the characters in the given string
*/
@Override
public int[] transform(String data) {
return transformations.transform(data);
}
/**
* Transforms an array of integer indices back into a string using the configured alphabet.
*
* @param data the array of indices to transform back into characters
* @return the resulting string produced from the given index array
*/
@Override
public String transform(int[] data) {
return transformations.transform(data);