Skip to content

Instantly share code, notes, and snippets.

@jandk
Last active September 27, 2018 07:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jandk/613583efd4f52f731ce033f7321a70a7 to your computer and use it in GitHub Desktop.
Save jandk/613583efd4f52f731ce033f7321a70a7 to your computer and use it in GitHub Desktop.
Reversible encoding and stringification of numbers
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;
}
}
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;
}
}
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