Last active
September 27, 2018 07:39
-
-
Save jandk/613583efd4f52f731ce033f7321a70a7 to your computer and use it in GitHub Desktop.
Reversible encoding and stringification of numbers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package be.tjoener.test; | |
@SuppressWarnings("PointlessArithmeticExpression") | |
public final class Feistel { | |
/** | |
* Sets the number of rounds, security/speed trade-off | |
*/ | |
private static final int ROUNDS = 16; | |
/** | |
* Sets the amount of bits you want to map, changes the length of the output | |
* Also limits the maximum value of the input | |
*/ | |
private static final int NUMBER_OF_BITS = 24; | |
// Ignore these values | |
private static final long MAX_VALUE = (1L << NUMBER_OF_BITS) - 1; | |
private static final int BITS = NUMBER_OF_BITS / 2; | |
private static final int MASK = (1 << BITS) - 1; | |
private final int[] keyExpansion; | |
/** | |
* Initialize the keyExpansion according to the MUSL LCG | |
*/ | |
public Feistel(long key) { | |
this.keyExpansion = expandKey(key); | |
} | |
public long encrypt(long n) { | |
if (n < 0 || n > MAX_VALUE) { | |
throw new IllegalArgumentException("Number out of range"); | |
} | |
int l = (int) (n >>> BITS); | |
int r = (int) (n & MASK); | |
for (int round = 0; round < ROUNDS; round += 2) { | |
l ^= f(r, keyExpansion[round + 0]); | |
r ^= f(l, keyExpansion[round + 1]); | |
} | |
return ((long) r << BITS) | (long) l; | |
} | |
public long decrypt(long n) { | |
if (n < 0 || n > MAX_VALUE) { | |
throw new IllegalArgumentException("Number out of range"); | |
} | |
int l = (int) (n & MASK); | |
int r = (int) (n >>> BITS); | |
for (int round = ROUNDS - 2; round >= 0; round -= 2) { | |
r ^= f(l, keyExpansion[round + 1]); | |
l ^= f(r, keyExpansion[round + 0]); | |
} | |
return ((long) l << BITS) | (long) r; | |
} | |
/** | |
* Generate a key expansion, based on the MUSL LCG | |
*/ | |
private static int[] expandKey(long key) { | |
int[] keyExpansion; | |
keyExpansion = new int[ROUNDS]; | |
for (int i = 0; i < ROUNDS; i++) { | |
key = key * 0x5851f42d4c957f2dL + 1L; | |
keyExpansion[i] = ((int) (key >>> 32)) & MASK; | |
} | |
return keyExpansion; | |
} | |
private static int f(int r, int roundKey) { | |
return ((roundKey * r) + roundKey) & MASK; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package be.tjoener.test; | |
import java.util.Arrays; | |
import static java.lang.Math.addExact; | |
import static java.lang.Math.multiplyExact; | |
import static java.util.Objects.requireNonNull; | |
public final class LongEncoding { | |
private static final int TABLESIZE = 128; | |
private final String alphabet; | |
private final int alphabetSize; | |
private final byte[] reverseAlphabet; | |
public LongEncoding(String alphabet) { | |
this.alphabet = requireNonNull(alphabet); | |
this.alphabetSize = alphabet.length(); | |
this.reverseAlphabet = buildReverseAlphabet(alphabet); | |
} | |
public String encodeString(long value) { | |
StringBuilder builder = new StringBuilder(); | |
do { | |
int index = (int) (value % alphabetSize); | |
builder.append(alphabet.charAt(index)); | |
} while ((value /= alphabetSize) > 0); | |
return builder.reverse().toString(); | |
} | |
public long decodeString(String value) { | |
long result = 0; | |
for (char c : value.toCharArray()) { | |
int numericValue = c < TABLESIZE ? reverseAlphabet[c] : -1; | |
if (numericValue < 0) { | |
throw new IllegalArgumentException("Invalid character in string: " + value); | |
} | |
result = addExact(multiplyExact(result, alphabetSize), numericValue); | |
} | |
return result; | |
} | |
private static byte[] buildReverseAlphabet(String alphabet) { | |
byte[] reverse = new byte[TABLESIZE]; | |
Arrays.fill(reverse, (byte) -1); | |
for (int i = 0; i < alphabet.length(); i++) { | |
reverse[alphabet.charAt(i)] = (byte) i; | |
} | |
return reverse; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package be.tjoener.test; | |
import java.util.BitSet; | |
public class Program { | |
private static final int BITS = 24; | |
private static final long MAX_VALUE = (1L << BITS) - 1; | |
public static void main(String[] args) { | |
Feistel feistel = new Feistel(0x1234567890abcdefL); | |
BitSet bitSet = new BitSet((int) MAX_VALUE); | |
for (int i = 0; i < MAX_VALUE; i++) { | |
int l = (int) feistel.encrypt(i); | |
if (bitSet.get(l)) { | |
throw new IllegalStateException("Already set " + l); | |
} | |
bitSet.set(l); | |
long decrypt = feistel.decrypt(l); | |
if(decrypt != i){ | |
String message = String.format("Original (%d) and round-trip (%d) do not match!", i, decrypt); | |
throw new IllegalStateException(message); | |
} | |
} | |
System.out.println(bitSet.cardinality()); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment