From 9b1e6a77de7f8a888b7b668034b5e0ae7c6453cb Mon Sep 17 00:00:00 2001 From: Benjamin Dod Date: Wed, 13 Nov 2024 12:20:07 -0500 Subject: [PATCH 1/4] Refactor String -> char[], decode int in reverse MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor the methods `decode_int` and `encode_int_r` to accept/return char arrays rather than Strings to reduce the number of allocations. Change the behavior of `decode_int` to decode its char[] input in reverse, rather than reversing its input. This method's name is also changed to `decode_int_r` to reflect its behavior. This change nets a modest 6.8% improvement in the existing benchmark suite on a 16GB M1 Pro: ``` Original: Benchmark Mode Cnt Score Error Units FF3CipherPerf.testEncrypt thrpt 5 93096.351 ± 1781.433 ops/s New: Benchmark Mode Cnt Score Error Units FF3CipherPerf.testEncrypt thrpt 5 99928.775 ± 1557.381 ops/s ``` --- .../java/com/privacylogistics/FF3Cipher.java | 56 +++++++++---------- .../com/privacylogistics/FF3CipherTest.java | 26 ++++----- 2 files changed, 39 insertions(+), 43 deletions(-) diff --git a/src/main/java/com/privacylogistics/FF3Cipher.java b/src/main/java/com/privacylogistics/FF3Cipher.java index dea89f5..17bf68e 100644 --- a/src/main/java/com/privacylogistics/FF3Cipher.java +++ b/src/main/java/com/privacylogistics/FF3Cipher.java @@ -156,8 +156,8 @@ public String encrypt(String plaintext, byte[] tweak) throws BadPaddingException int v = n - u; // Split the message - String A = plaintext.substring(0, u); - String B = plaintext.substring(u); + char[] A = plaintext.substring(0, u).toCharArray(); + char[] B = plaintext.substring(u).toCharArray(); logger.trace("r {} A {} B {}", this.radix, A, B); if ((tweak.length != TWEAK_LEN) && (tweak.length != TWEAK_LEN_NEW)) { @@ -211,7 +211,7 @@ public String encrypt(String plaintext, byte[] tweak) throws BadPaddingException BigInteger y = new BigInteger(byteArrayToHexString(S), 16); // Calculate c - c = decode_int(reverseString(A), alphabet); + c = decode_int_r(A, alphabet); c = c.add(y); @@ -223,14 +223,14 @@ public String encrypt(String plaintext, byte[] tweak) throws BadPaddingException logger.trace("\tm: {} A: {} c: {} y: {}", m, A, c, y); - String C = encode_int_r(c, this.alphabet, m); + char[] C = encode_int_r(c, this.alphabet, m); // Final steps A = B; B = C; logger.trace("A: {} B: {}", A, B); } - return A + B; + return new String(A) + new String(B); } /** @@ -279,8 +279,8 @@ public String decrypt(String ciphertext, byte[] tweak) throws BadPaddingExceptio int v = n - u; // Split the message - String A = ciphertext.substring(0, u); - String B = ciphertext.substring(u); + char[] A = ciphertext.substring(0, u).toCharArray(); + char[] B = ciphertext.substring(u).toCharArray(); if ((tweak.length != TWEAK_LEN) && (tweak.length != TWEAK_LEN_NEW)) { throw new IllegalArgumentException(String.format("tweak length %d is invalid: tweak must be 56 or 64 bits", @@ -333,7 +333,7 @@ public String decrypt(String ciphertext, byte[] tweak) throws BadPaddingExceptio BigInteger y = new BigInteger(byteArrayToHexString(S), 16); // Calculate c - c = decode_int(reverseString(B), alphabet); + c = decode_int_r(B, alphabet); c = c.subtract(y); @@ -345,14 +345,14 @@ public String decrypt(String ciphertext, byte[] tweak) throws BadPaddingExceptio logger.trace("\tm: {} B: {} c: {} y: {}", m, B, c, y); - String C = encode_int_r(c, this.alphabet, m); + char[] C = encode_int_r(c, this.alphabet, m); // Final steps B = A; A = C; logger.trace("A: {} B: {}", A, B); } - return A + B; + return new String(A) + new String(B); } /** @@ -383,7 +383,7 @@ protected static byte[] calculateTweak64_FF3_1(byte[] tweak56) * @param B a string value * @return a byte array */ - protected static byte[] calculateP(int i, String alphabet, byte[] W, String B) { + protected static byte[] calculateP(int i, String alphabet, byte[] W, char[] B) { byte[] P = new byte[BLOCK_SIZE]; // P is always 16 bytes, zero initialized @@ -398,8 +398,7 @@ protected static byte[] calculateP(int i, String alphabet, byte[] W, String B) { // The remaining 12 bytes of P are copied from reverse(B) with padding - B = reverseString(B); - byte[] bBytes = decode_int(B, alphabet).toByteArray(); + byte[] bBytes = decode_int_r(B, alphabet).toByteArray(); System.arraycopy(bBytes, 0, P, (BLOCK_SIZE - bBytes.length), bBytes.length); logger.trace("round: {} W: {} P: {}", () -> i, () -> byteArrayToHexString(W), () -> byteArrayToIntString(P)); @@ -466,9 +465,9 @@ protected static String byteArrayToIntString(byte[] byteArray) { } /** - * Return a string representation of a number in the given base system - * - the string is right padded with zeros to length - * - the string is returned in reversed order expected by the calling cryptographic function + * Return a char[] representation of a number in the given base system + * - the char[] is right padded with zeros to length + * - the char[] is returned in reversed order expected by the calling cryptographic function * i.e., the decimal value 123 in five decimal places would be '32100' * * examples: @@ -477,9 +476,9 @@ protected static String byteArrayToIntString(byte[] byteArray) { * @param n the integer number * @param alphabet the alphabet used for encoding * @param length the length used for padding the output string - * @return a string encoding of the number + * @return a char[] encoding of the number */ - protected static String encode_int_r(BigInteger n, String alphabet, int length) { + protected static char[] encode_int_r(BigInteger n, String alphabet, int length) { char[] x = new char[length]; int i=0; @@ -496,25 +495,22 @@ protected static String encode_int_r(BigInteger n, String alphabet, int length) while (i < length) { x[i++] = alphabet.charAt(0); } - return new String(x); + return x; } /** - * Decode a base X string representation into an integer - * @param str the original string + * Decode a base X char[] representation into an integer. + * - the BigInteger is returned in reversed order expected by the calling cryptographic function + * @param str the original char[] * @param alphabet the alphabet and radix (len(alphabet)) for decoding - * @return an integer value of the encoded string + * @return an integer value of the encoded char[] */ - protected static BigInteger decode_int(String str, String alphabet) { - - int strlen = str.length(); + protected static BigInteger decode_int_r(char[] str, String alphabet) { BigInteger base = BigInteger.valueOf(alphabet.length()); BigInteger num = BigInteger.ZERO; - int idx =0; - for (char each : str.toCharArray()) { - int exponent = (strlen - (idx + 1)); - num = num.add(base.pow(exponent).multiply(BigInteger.valueOf(alphabet.indexOf(each)))); - idx += 1; + for (int i = 0; i < str.length; i++) { + char ch = str[i]; + num = num.add(base.pow(i).multiply(BigInteger.valueOf(alphabet.indexOf(ch)))); } return num; } diff --git a/src/test/java/com/privacylogistics/FF3CipherTest.java b/src/test/java/com/privacylogistics/FF3CipherTest.java index f146867..82be250 100644 --- a/src/test/java/com/privacylogistics/FF3CipherTest.java +++ b/src/test/java/com/privacylogistics/FF3CipherTest.java @@ -26,7 +26,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.decode_int_r; public class FF3CipherTest { @@ -141,7 +141,7 @@ public void testCalculateP() { String alphabet = "0123456789"; String B = "567890000"; byte[] W = FF3Cipher.hexStringToByteArray("FA330A73"); - byte[] P = FF3Cipher.calculateP(i, alphabet, W, B); + byte[] P = FF3Cipher.calculateP(i, alphabet, W, B.toCharArray()); assertArrayEquals(P, new byte[] {(byte) 250, 51, 10, 115, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, (byte) 129, (byte) 205}); } @@ -156,21 +156,21 @@ public void testInvalidPlaintext() throws Exception { @Test public void testEncodeBigInt() { - assertEquals("101", reverseString(encode_int_r(BigInteger.valueOf(5), "01", 3))); - assertEquals("11", reverseString(encode_int_r(BigInteger.valueOf(6), "01234", 2))); - assertEquals("00012", reverseString(encode_int_r(BigInteger.valueOf(7), "01234", 5))); - assertEquals("a", reverseString(encode_int_r(BigInteger.valueOf(10), "0123456789abcdef", 1))); - assertEquals("20", reverseString(encode_int_r(BigInteger.valueOf(32), "0123456789abcdef", 2))); + assertEquals("101", reverseString(new String(encode_int_r(BigInteger.valueOf(5), "01", 3)))); + assertEquals("11", reverseString(new String(encode_int_r(BigInteger.valueOf(6), "01234", 2)))); + assertEquals("00012", reverseString(new String(encode_int_r(BigInteger.valueOf(7), "01234", 5)))); + assertEquals("a", reverseString(new String(encode_int_r(BigInteger.valueOf(10), "0123456789abcdef", 1)))); + assertEquals("20", reverseString(new String(encode_int_r(BigInteger.valueOf(32), "0123456789abcdef", 2)))); } @Test public void testDecodeInt() { - assertEquals(BigInteger.valueOf(321), (decode_int("321", "0123456789"))); - assertEquals(BigInteger.valueOf(101), (decode_int("101", "0123456789"))); - assertEquals(BigInteger.valueOf(101), (decode_int("00101", "0123456789"))); - assertEquals(BigInteger.valueOf(0x02), (decode_int("02", "0123456789abcdef"))); - assertEquals(BigInteger.valueOf(0xAA), (decode_int("aa", "0123456789abcdef"))); - assertEquals(new BigInteger("2658354847544284194395037922"), (decode_int("2658354847544284194395037922", "0123456789"))); + assertEquals(BigInteger.valueOf(123), (decode_int_r("321".toCharArray(), "0123456789"))); + assertEquals(BigInteger.valueOf(101), (decode_int_r("101".toCharArray(), "0123456789"))); + assertEquals(BigInteger.valueOf(10100), (decode_int_r("00101".toCharArray(), "0123456789"))); + assertEquals(BigInteger.valueOf(0x20), (decode_int_r("02".toCharArray(), "0123456789abcdef"))); + assertEquals(BigInteger.valueOf(0xAA), (decode_int_r("aa".toCharArray(), "0123456789abcdef"))); + assertEquals(new BigInteger("2297305934914824457484538562"), (decode_int_r("2658354847544284194395037922".toCharArray(), "0123456789"))); } @Test From 190f9cdd9cbaed0be4026e699aeb86637e87226e Mon Sep 17 00:00:00 2001 From: Benjamin Dod Date: Wed, 13 Nov 2024 12:39:59 -0500 Subject: [PATCH 2/4] Change testDecodeInt() case for reverse behavior This commit replaces an assertion that was dropped in the previous commit. Originally, the assertion was that `decode_int("00101", ...)` yields `101`. With the change in the previous commit to decode the *reverse* of the input, the assertion was changed to be that `decode_int_r("00101", ...)` yields `10100`. However, this is not the same assertion as we are no longer asserting that leading zeroes are not affecting the output of the method. This commit effectively reverts the assertion to be that `decode_int_r("10100", ...)` (i.e. the reverse of "00101") yields `101`. --- src/test/java/com/privacylogistics/FF3CipherTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/privacylogistics/FF3CipherTest.java b/src/test/java/com/privacylogistics/FF3CipherTest.java index 82be250..59efd0a 100644 --- a/src/test/java/com/privacylogistics/FF3CipherTest.java +++ b/src/test/java/com/privacylogistics/FF3CipherTest.java @@ -167,7 +167,7 @@ public void testEncodeBigInt() { public void testDecodeInt() { assertEquals(BigInteger.valueOf(123), (decode_int_r("321".toCharArray(), "0123456789"))); assertEquals(BigInteger.valueOf(101), (decode_int_r("101".toCharArray(), "0123456789"))); - assertEquals(BigInteger.valueOf(10100), (decode_int_r("00101".toCharArray(), "0123456789"))); + assertEquals(BigInteger.valueOf(101), (decode_int_r("10100".toCharArray(), "0123456789"))); assertEquals(BigInteger.valueOf(0x20), (decode_int_r("02".toCharArray(), "0123456789abcdef"))); assertEquals(BigInteger.valueOf(0xAA), (decode_int_r("aa".toCharArray(), "0123456789abcdef"))); assertEquals(new BigInteger("2297305934914824457484538562"), (decode_int_r("2658354847544284194395037922".toCharArray(), "0123456789"))); From ef3e001389c95a7711d184818a2e7f96c69a6262 Mon Sep 17 00:00:00 2001 From: Benjamin Dod Date: Tue, 19 Nov 2024 10:29:58 -0500 Subject: [PATCH 3/4] Replace unnecessary `.substring()` with arr copy --- src/main/java/com/privacylogistics/FF3Cipher.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/privacylogistics/FF3Cipher.java b/src/main/java/com/privacylogistics/FF3Cipher.java index 17bf68e..2313274 100644 --- a/src/main/java/com/privacylogistics/FF3Cipher.java +++ b/src/main/java/com/privacylogistics/FF3Cipher.java @@ -156,8 +156,11 @@ public String encrypt(String plaintext, byte[] tweak) throws BadPaddingException int v = n - u; // Split the message - char[] A = plaintext.substring(0, u).toCharArray(); - char[] B = plaintext.substring(u).toCharArray(); + char[] A = new char[u]; + char[] B = new char[v]; + plaintext.getChars(0, u, A, 0); + plaintext.getChars(u, plaintext.length(), B, 0); + logger.trace("r {} A {} B {}", this.radix, A, B); if ((tweak.length != TWEAK_LEN) && (tweak.length != TWEAK_LEN_NEW)) { From 855ccb9c67efea01dcaa05a3db259bdec69e569f Mon Sep 17 00:00:00 2001 From: Benjamin Dod Date: Tue, 19 Nov 2024 14:24:49 -0500 Subject: [PATCH 4/4] Replace `.substring()` with `getChars`: decrypt --- src/main/java/com/privacylogistics/FF3Cipher.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/privacylogistics/FF3Cipher.java b/src/main/java/com/privacylogistics/FF3Cipher.java index c85480c..02f0c59 100644 --- a/src/main/java/com/privacylogistics/FF3Cipher.java +++ b/src/main/java/com/privacylogistics/FF3Cipher.java @@ -312,8 +312,10 @@ public String decrypt(String ciphertext, byte[] tweak) throws BadPaddingExceptio int v = n - u; // Split the message - char[] A = ciphertext.substring(0, u).toCharArray(); - char[] B = ciphertext.substring(u).toCharArray(); + char[] A = new char[u]; + char[] B = new char[v]; + ciphertext.getChars(0, u, A, 0); + ciphertext.getChars(u, ciphertext.length(), B, 0); if ((tweak.length != TWEAK_LEN) && (tweak.length != TWEAK_LEN_NEW)) { throw new IllegalArgumentException(String.format("tweak length %d is invalid: tweak must be 56 or 64 bits",