From 519c94b10fb8e057b6df9a693844487419ec7c28 Mon Sep 17 00:00:00 2001 From: Benjamin Dod Date: Wed, 6 Nov 2024 09:54:53 -0500 Subject: [PATCH 1/3] Add byte[] overloads for all String ctors Add constructors that accept byte[] values for key and tweak. In the event a user is working with byte[] cryptographic primitives, this allows them to avoid the unnecessary overhead of converting to/from Strings to initialize the cipher. This does not contain any breaking changes w.r.t. the FF3Cipher class interface. --- .../java/com/privacylogistics/FF3Cipher.java | 48 +++++++++++++++---- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/privacylogistics/FF3Cipher.java b/src/main/java/com/privacylogistics/FF3Cipher.java index dea89f5..cdc2c22 100644 --- a/src/main/java/com/privacylogistics/FF3Cipher.java +++ b/src/main/java/com/privacylogistics/FF3Cipher.java @@ -42,6 +42,16 @@ public FF3Cipher(String key, String tweak) { this(key, tweak, 10); } + /** + * Constructor with default radix of 10. + * + * @param key encryption key used to initialize AES ECB + * @param tweak used in each round and split into right and left sides + */ + public FF3Cipher(byte[] key, byte[] tweak) { + this(key, tweak, 10); + } + /** * Constructor with a custom alphabet * @@ -49,12 +59,10 @@ public FF3Cipher(String key, String tweak) { * @param tweak used in each round and split into right and left sides * @param alphabet the cipher alphabet */ - public FF3Cipher(String key, String tweak, String alphabet) { + public FF3Cipher(byte[] key, byte[] tweak, String alphabet) { this.alphabet = alphabet; this.radix = alphabet.length(); - byte[] keyBytes = hexStringToByteArray(key); - // Calculate range of supported message lengths [minLen..maxLen] // radix 10: 6 ... 56, 26: 5 ... 40, 36: 4 .. 36 @@ -66,10 +74,9 @@ public FF3Cipher(String key, String tweak, String alphabet) { // ToDo: With log2 we could further simplify this // this.maxLen = (int) (2 * Math.floor(96/Math.log2(radix))); - int keyLen = keyBytes.length; // Check if the key is 128, 192, or 256 bits = 16, 24, or 32 bytes - if (keyLen != 16 && keyLen != 24 && keyLen != 32) { - throw new IllegalArgumentException("key length " + keyLen + " but must be 128, 192, or 256 bits"); + if (key.length != 16 && key.length != 24 && key.length != 32) { + throw new IllegalArgumentException("key length " + key.length + " but must be 128, 192, or 256 bits"); } // While FF3 allows radices in [2, 2^16], currently only tested up to 64 @@ -82,15 +89,16 @@ public FF3Cipher(String key, String tweak, String alphabet) { throw new IllegalArgumentException("minLen or maxLen invalid, adjust your radix"); } - this.defaultTweak = hexStringToByteArray(tweak); + this.defaultTweak = tweak; // AES block cipher in ECB mode with the block size derived based on the length of the key // Always use the reversed key since Encrypt and Decrypt call cipher expecting that // Feistel ciphers use the same func for encrypt/decrypt, so mode is always ENCRYPT_MODE try { - reverseBytes(keyBytes); - SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES"); + byte[] reversedKey = key.clone(); + reverseBytes(reversedKey); + SecretKeySpec keySpec = new SecretKeySpec(reversedKey, "AES"); aesCipher = Cipher.getInstance("AES/ECB/NoPadding"); aesCipher.init(Cipher.ENCRYPT_MODE, keySpec); } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) { @@ -99,6 +107,17 @@ public FF3Cipher(String key, String tweak, String alphabet) { } } + /** + * Constructor with a custom alphabet + * + * @param key encryption key used to initialize AES ECB + * @param tweak used in each round and split into right and left sides + * @param alphabet the cipher alphabet + */ + public FF3Cipher(String key, String tweak, String alphabet) { + this(hexStringToByteArray(key), hexStringToByteArray(tweak), alphabet); + } + /** * Constructor with a standardized radix * @@ -107,6 +126,17 @@ public FF3Cipher(String key, String tweak, String alphabet) { * @param radix the domain of the alphabet, 10, 26 or 36 */ public FF3Cipher(String key, String tweak, int radix) { + this(hexStringToByteArray(key), hexStringToByteArray(tweak), alphabetForBase(radix)); + } + + /** + * Constructor with a standardized radix + * + * @param key encryption key used to initialize AES ECB + * @param tweak used in each round and split into right and left sides + * @param radix the domain of the alphabet, 10, 26 or 36 + */ + public FF3Cipher(byte[] key, byte[] tweak, int radix) { this(key, tweak, alphabetForBase(radix)); } From 4eb3c82d9ed773ad5d2f5e83831fb39b80d5dde5 Mon Sep 17 00:00:00 2001 From: Benjamin Dod Date: Thu, 7 Nov 2024 11:33:26 -0500 Subject: [PATCH 2/3] Test all FF3Cipher ctors --- .../com/privacylogistics/FF3CipherTest.java | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/test/java/com/privacylogistics/FF3CipherTest.java b/src/test/java/com/privacylogistics/FF3CipherTest.java index f146867..14d744e 100644 --- a/src/test/java/com/privacylogistics/FF3CipherTest.java +++ b/src/test/java/com/privacylogistics/FF3CipherTest.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledOnJre; + import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.condition.JRE; @@ -27,6 +28,7 @@ import static com.privacylogistics.FF3Cipher.reverseString; import static com.privacylogistics.FF3Cipher.encode_int_r; import static com.privacylogistics.FF3Cipher.decode_int; +import static com.privacylogistics.FF3Cipher.hexStringToByteArray; public class FF3CipherTest { @@ -119,9 +121,25 @@ public class FF3CipherTest { }; @Test - public void testCreate() { - FF3Cipher c = new FF3Cipher("EF4359D8D580AA4F7F036D6F04FC6A94", "D8E7920AFA330A73"); - assertNotNull(c); + public void testConstructors() { + String keyStr = "EF4359D8D580AA4F7F036D6F04FC6A94"; + String tweakStr = "D8E7920AFA330A73"; + byte[] keyBytes = hexStringToByteArray(keyStr); + byte[] tweakBytes = hexStringToByteArray(tweakStr); + + FF3Cipher cs0 = new FF3Cipher(keyStr, tweakStr); + FF3Cipher cs1 = new FF3Cipher(keyStr, tweakStr, 62); + FF3Cipher cs2 = new FF3Cipher(keyStr, tweakStr, "0123456789"); + FF3Cipher cb0 = new FF3Cipher(keyBytes, tweakBytes); + FF3Cipher cb1 = new FF3Cipher(keyBytes, tweakBytes, 62); + FF3Cipher cb2 = new FF3Cipher(keyBytes, tweakBytes, "0123456789"); + + assertNotNull(cs0); + assertNotNull(cs1); + assertNotNull(cs2); + assertNotNull(cb0); + assertNotNull(cb1); + assertNotNull(cb2); } @Test From 6556460c0674298e52cb3358031aaf01afdd4939 Mon Sep 17 00:00:00 2001 From: Benjamin Dod Date: Thu, 7 Nov 2024 11:52:37 -0500 Subject: [PATCH 3/3] Add testFF3_1_bytes to exercise byte[] ctor --- .../java/com/privacylogistics/FF3CipherTest.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/test/java/com/privacylogistics/FF3CipherTest.java b/src/test/java/com/privacylogistics/FF3CipherTest.java index 14d744e..7ea19aa 100644 --- a/src/test/java/com/privacylogistics/FF3CipherTest.java +++ b/src/test/java/com/privacylogistics/FF3CipherTest.java @@ -239,7 +239,7 @@ public void testAcvpFF3_1() throws Exception { } @Test - public void testFF3_1() throws Exception { + public void testFF3_1_str() throws Exception { // Test with 56 bit tweak String[] testVector = TestVectors[0]; FF3Cipher c = new FF3Cipher(testVector[Tkey], "D8E7920AFA330A", Integer.parseInt(testVector[Tradix])); @@ -250,6 +250,18 @@ public void testFF3_1() throws Exception { assertEquals(pt, plaintext); } + @Test + public void testFF3_1_bytes() throws Exception { + // Test with 56 bit tweak + String[] testVector = TestVectors[0]; + FF3Cipher c = new FF3Cipher(hexStringToByteArray(testVector[Tkey]), hexStringToByteArray("D8E7920AFA330A"), Integer.parseInt(testVector[Tradix])); + String pt = testVector[Tplaintext], ct = "477064185124354662"; + String ciphertext = c.encrypt(pt); + String plaintext = c.decrypt(ciphertext); + assertEquals(ct, ciphertext); + assertEquals(pt, plaintext); + } + @Test @EnabledOnJre(value = JRE.JAVA_11) public void testCustomAlphabet() throws Exception {