Skip to content

Commit

Permalink
Created separate int/long versions of all of the major pieces.
Browse files Browse the repository at this point in the history
  • Loading branch information
jchambers committed Jul 28, 2018
1 parent a28bc8e commit 2a07702
Show file tree
Hide file tree
Showing 31 changed files with 731 additions and 196 deletions.
63 changes: 31 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,58 +34,57 @@ final AlphabetCodec codec = new AlphabetCodec(new AlphabetBuilder()
.build());

final MultiplicativeInverseIntegerTransformer inverse =
new MultiplicativeInverseIntegerTransformer(0x1909719a5ee544adL);
new MultiplicativeInverseIntegerTransformer(0x5ee544ad);

final BitRotationIntegerTransformer rotate = new BitRotationIntegerTransformer(22);
final OffsetIntegerTransformer offset = new OffsetIntegerTransformer(0xe45c2f833b2f0474L);
final XorIntegerTransformer xor = new XorIntegerTransformer(0xe41c643d0593242L);

final OffsetIntegerTransformer offset = new OffsetIntegerTransformer(0x3b2f0474);
final XorIntegerTransformer xor = new XorIntegerTransformer(0xd0593242);

final IntegerObfuscationPipeline pipeline =
new IntegerObfuscationPipeline(codec, inverse, rotate, offset, xor);

System.out.println("| ID | Obfuscated ID |");
System.out.println("|----|----------------|");
System.out.println("| ID | Obfuscated ID |");
System.out.println("|----|---------------|");

for (int id = 0; id < 10; id++) {
System.out.format("| %d | %-14s |\n", id, pipeline.obfuscate(id));
System.out.format("| %d | %-13s |\n", id, pipeline.obfuscate(id));
assert id == pipeline.deobfuscate(pipeline.obfuscate(id));
}
```

The example produces the following output:

| ID | Obfuscated ID |
|----|----------------|
| 0 | LWNKM28KZD449J |
| 1 | VWB2HZQ3CLVPBQ |
| 2 | XHQ9QD9M7NZYZC |
| 3 | B7BCPMZB4JFP9X |
| 4 | 2L3878VCZ3NQXM |
| 5 | LXZMVQXBFZ3XYT |
| 6 | VX28X98HFPQ98N |
| 7 | XV2NQYJNV2349F |
| 8 | BV2CKWTDBZKFJC |
| 9 | 29K87FNQLCY9XC |
| ID | Obfuscated ID |
|----|---------------|
| 0 | RZCNV8L |
| 1 | 93ZWMWW |
| 2 | Z93438Y |
| 3 | FLMYVD7 |
| 4 | VQWMNT2 |
| 5 | CLM3NN9 |
| 6 | R44WR9B |
| 7 | 9TB44NX |
| 8 | ZKD3888 |
| 9 | BVJXQL8 |

In the above example, there are three major pieces of the puzzle: transformers, codecs, and a pipeline. We'll discuss each in turn.

### The pipeline

The main point of interaction with ID Obfuscator is the `ObfuscationPipeline`. The pipeline combines a number of transformers and exactly one codec into a coherent tool for obfuscating and deobfuscating numbers. The type, number, and configuration of the transformers and the type and configuration codec all control the behavior of the pipeline. As an example, let's change the order of the transformers in the demo above to `offset, rotate, xor, inverse` (i.e. we swap the positions of `offset` and `rotate`). Now the output looks like this:

| ID | Obfuscated ID |
|----|----------------|
| 0 | LCXJYW72K4QPDN |
| 1 | VC4YV7MKXHXX43 |
| 2 | FDJNDP4KCFPTF4 |
| 3 | BF4QWWQTMHY7HC |
| 4 | 2RYDTDV874Q2RL |
| 5 | L3CJQTYM3M8D4D |
| 6 | VXPNPYN3MMMRFV |
| 7 | FPF77MXDLKCT3Y |
| 8 | BW8H3J9H7W9V8F |
| 9 | 2CF4RWFCFYLNCT |
| ID | Obfuscated ID |
|----|---------------|
| 0 | F8C88BC |
| 1 | ZRHBHMQ |
| 2 | C3VPHKD |
| 3 | 43QZY42 |
| 4 | BDYTNP4 |
| 5 | RCLW873 |
| 6 | FQCYMLP |
| 7 | ZDHXXV7 |
| 8 | CTZ7XZV |
| 9 | 4TQCNTL |

This is, obviously, very different from the original output. We could achieve similar output changes by changing the value of the offset passed to the `OffsetIntegerTransformer`, for example, or changing the random seed passed to the codec. This has two very important consequences:

Expand Down Expand Up @@ -125,4 +124,4 @@ ID Obfuscator comes with `AlphabetCodec`, which uses an alphabet you provide to

## The details

ID Obfuscator is just that: an obfuscator. It makes it difficult for malicious users to figure out how to turn an obfuscated ID into a "real" ID, but not impossible. Under no circumstances should it be used to encode sensitive information like credit card numbers, PINs, or phone numbers. Caveat emptor.
ID Obfuscator is just that: an obfuscator. It makes it difficult for malicious users to figure out how to turn an obfuscated ID into a "real" ID, but not impossible. Under no circumstances should it be used to encode sensitive information like credit card numbers, PINs, or phone numbers. Caveat emptor.
51 changes: 40 additions & 11 deletions src/main/java/com/eatthepath/idobfuscator/AlphabetCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@
*
* @author <a href="https://github.com/jchambers">Jon Chambers</a>
*/
public class AlphabetCodec implements IntegerCodec {
public class AlphabetCodec implements IntegerCodec, LongCodec {

private final char[] alphabet;

private final transient int[] characterValues;

private final transient int maximumStringLength;
private final transient int maximumStringLengthForInt;
private final transient int maximumStringLengthForLong;
private final transient long[] placeValues;

/**
Expand Down Expand Up @@ -74,30 +75,45 @@ public AlphabetCodec(final char... alphabet) {
// string needed to represent any integer value with the given alphabet. With that, we can both perform some
// low-cost error-checking when we try to decode strings and also pre-calculate place values to avoid repeating
// work when decoding.
this.maximumStringLength = (int) Math.ceil(Math.log(Math.pow(2, Long.SIZE)) / Math.log(this.alphabet.length));
this.maximumStringLengthForInt = (int) Math.ceil(Math.log(Math.pow(2, Integer.SIZE)) / Math.log(this.alphabet.length));
this.maximumStringLengthForLong = (int) Math.ceil(Math.log(Math.pow(2, Long.SIZE)) / Math.log(this.alphabet.length));

this.placeValues = new long[this.maximumStringLength];
this.placeValues = new long[this.maximumStringLengthForLong];
this.placeValues[0] = 1;

for (int i = 1; i < this.placeValues.length; i++) {
this.placeValues[i] = this.placeValues[i - 1] * this.alphabet.length;
}
}

@Override
public String encodeIntegerAsString(final int i) {
long workingCopy = i & 0xffffffffL;

final char[] encodedCharacters = new char[this.maximumStringLengthForInt];

for (int j = encodedCharacters.length - 1; j >= 0; j--) {
encodedCharacters[j] = this.alphabet[(int) (workingCopy % this.alphabet.length)];
workingCopy /= this.alphabet.length;
}

return new String(encodedCharacters);
}

/**
* Encodes the given integer as a string using this codec's alphabet.
*
* @param i the integer to encode
* @param l the integer to encode
*
* @return a string representation of the given integer
*
* @throws IllegalArgumentException if the given integer cannot be expressed with {@code nBits} bits
*/
@Override
public String encodeIntegerAsString(final long i) {
long workingCopy = i;
public String encodeLongAsString(final long l) {
long workingCopy = l;

final char[] encodedCharacters = new char[this.maximumStringLength];
final char[] encodedCharacters = new char[this.maximumStringLengthForLong];

for (int j = encodedCharacters.length - 1; j >= 0; j--) {
encodedCharacters[j] = this.alphabet[(int) Long.remainderUnsigned(workingCopy, this.alphabet.length)];
Expand All @@ -107,6 +123,15 @@ public String encodeIntegerAsString(final long i) {
return new String(encodedCharacters);
}

@Override
public int decodeStringAsInteger(final String string) {
final long decoded = this.decodeStringAsLong(string, this.maximumStringLengthForInt);

assert (decoded & 0xffffffff00000000L) == 0;

return (int) decoded;
}

/**
* Decodes the given string as an integer.
*
Expand All @@ -118,10 +143,14 @@ public String encodeIntegerAsString(final long i) {
* alphabet, or if the given string contains characters not in this codec's alphabet
*/
@Override
public long decodeStringAsInteger(final String string) {
if (string.length() > this.maximumStringLength) {
public long decodeStringAsLong(final String string) {
return this.decodeStringAsLong(string, this.maximumStringLengthForLong);
}

private long decodeStringAsLong(final String string, final int maximumStringLength) {
if (string.length() > maximumStringLength) {
throw new IllegalArgumentException(
String.format("String \"%s\" is too long to represent a valid 64-bit integer in this codec's alphabet.", string));
String.format("String \"%s\" is too long to represent a valid integer in this codec's alphabet.", string));
}

final char[] chars = string.toCharArray();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,27 @@ public class BitRotationIntegerTransformer implements IntegerTransformer {
*
* @param distance the number of places by which to rotate integers
*/
public BitRotationIntegerTransformer(final long distance) {
public BitRotationIntegerTransformer(final int distance) {
this.originalDistance = distance;

// Normalize the rotation distance to the range of [0, 64).
this.effectiveDistance = (int) ((distance % Long.SIZE) + (distance < 0 ? Long.SIZE : 0));
// Normalize the rotation distance to the range of [0, 32).
this.effectiveDistance = (int) ((distance % Integer.SIZE) + (distance < 0 ? Integer.SIZE : 0));
}

int getEffectiveDistance() {
return this.effectiveDistance;
}

/**
* Transforms the given number by rotating its bits to the left by a prescribed distance.
* Transforms the given integer by rotating its bits to the left by a prescribed distance.
*
* @param i the integer to transform
*
* @return the rotated integer
*/
@Override
public long transformInteger(final long i) {
return (i << this.effectiveDistance) | (i >>> (Long.SIZE - this.effectiveDistance));
public int transformInteger(final int i) {
return (i << this.effectiveDistance) | (i >>> (Integer.SIZE - this.effectiveDistance));
}

/**
Expand All @@ -46,8 +46,8 @@ public long transformInteger(final long i) {
* @return the original integer
*/
@Override
public long reverseTransformInteger(final long i) {
return (i >>> this.effectiveDistance) | (i << (Long.SIZE -this.effectiveDistance));
public int reverseTransformInteger(final int i) {
return (i >>> this.effectiveDistance) | (i << (Integer.SIZE - this.effectiveDistance));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.eatthepath.idobfuscator;

/**
* Transforms long integers by performing a circular bit shift.
*
* @author <a href="https://github.com/jchambers">Jon Chambers</a>
*/
public class BitRotationLongTransformer implements LongTransformer {

private final long originalDistance;
private transient final int effectiveDistance;

/**
* Constructs a new bit rotation transformer that performs a circular rotation of bits by the given distance.
*
* @param distance the number of places by which to rotate integers
*/
public BitRotationLongTransformer(final int distance) {
this.originalDistance = distance;

// Normalize the rotation distance to the range of [0, 64).
this.effectiveDistance = (int) ((distance % Long.SIZE) + (distance < 0 ? Long.SIZE : 0));
}

int getEffectiveDistance() {
return this.effectiveDistance;
}

/**
* Transforms the given long integer by rotating its bits to the left by a prescribed distance.
*
* @param l the long integer to transform
*
* @return the rotated long integer
*/
@Override
public long transformLong(final long l) {
return (l << this.effectiveDistance) | (l >>> (Long.SIZE - this.effectiveDistance));
}

/**
* Reverses a bit rotation by shifting its bits to the right by a prescribed distance.
*
* @param l the long integer to un-rotate
*
* @return the original long integer
*/
@Override
public long reverseTransformLong(final long l) {
return (l >>> this.effectiveDistance) | (l << (Long.SIZE - this.effectiveDistance));
}

@Override
public String toString() {
return String.format("BitRotationIntegerTransformer [distance=%d]", this.originalDistance);
}
}
22 changes: 9 additions & 13 deletions src/main/java/com/eatthepath/idobfuscator/IntegerCodec.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

/**
* Reversibly transforms integers into strings and vice versa. This is usually the last (and sometimes only) step of an
* obfuscation process.
* obfuscation pipeline.
*
* @author <a href="https://github.com/jchambers">Jon Chambers</a>
*
Expand All @@ -14,23 +14,19 @@ public interface IntegerCodec {
* Encodes the given integer as a string. Encoding must be reversible, and it must be true for all values of
* {@code i} that {@code i == decodeStringAsInteger(encodeIntegerAsString(i))}.
*
* @param i the integer to represent as a string
* @param i the {@code int} to represent as a string
*
* @return a string representation of the given integer
* @return a string representation of the given {@code int}
*/
String encodeIntegerAsString(long i);
String encodeIntegerAsString(int i);

/**
* <p>Decodes the given string as an integer. Decoding must reverse the encoding process, and it must be true for
* all values of {@code i} that {@code i == decodeStringAsInteger(encodeIntegerAsString(i))}.</p>
* <p>Decodes the given string as an integer. Decoding must reverse the encoding process, and it must be true
* for all values of {@code i} that {@code i == decodeStringAsInteger(encodeIntegerAsString(i))}.</p>
*
* <p>Implementations of this method must throw an {@link IllegalArgumentException} if the decoded value cannot be
* expressed with {@code nBits} bits (i.e. it is larger than the maximum value for the given number of bits or is
* smaller than the minimum value).</p>
* @param string the string to decode as an {@code int}
*
* @param string the string to decode as an integer
*
* @return the integer represented by the given encoded string
* @return the {@code int} represented by the given encoded string
*/
long decodeStringAsInteger(String string);
int decodeStringAsInteger(String string);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

/**
* <p>An integer obfuscation pipeline combines any number of {@link IntegerTransformer} instances and exactly one
* {@link IntegerCodec} to obfuscate and deobfuscate integers.</p>
* {@link LongCodec} to obfuscate and deobfuscate integers.</p>
*
* <p>The output of an obfuscation pipeline is determined by the kind, number, order, and configuration of its
* transformers and by the kind and configuration of its codec. Changing the order of transformers, for example may
Expand All @@ -13,6 +13,7 @@
* @author <a href="https://github.com/jchambers">Jon Chambers</a>
*/
public class IntegerObfuscationPipeline {

private final IntegerTransformer[] transformers;
private final IntegerCodec codec;

Expand All @@ -23,8 +24,6 @@ public class IntegerObfuscationPipeline {
* {@code null}
* @param transformers any number of transformers to be applied to integers to be obfuscated or deobfuscated; may be
* empty or {@code null}
*
* @throws IllegalArgumentException if {@code nBits} does not fall between 1 and 64, inclusive
*/
public IntegerObfuscationPipeline(final IntegerCodec codec, final IntegerTransformer... transformers) {
Objects.requireNonNull(codec, "Codec must not be null");
Expand All @@ -45,8 +44,8 @@ public IntegerObfuscationPipeline(final IntegerCodec codec, final IntegerTransfo
*
* @throws IllegalArgumentException if the given integer cannot be expressed with this pipeline's bit size
*/
public String obfuscate(final long i) {
long encodedInteger = i;
public String obfuscate(final int i) {
int encodedInteger = i;

for (final IntegerTransformer obfuscator : this.transformers) {
encodedInteger = obfuscator.transformInteger(encodedInteger);
Expand All @@ -63,8 +62,8 @@ public String obfuscate(final long i) {
*
* @return the deobfuscated integer represented by the given string
*/
public long deobfuscate(final String string) {
long decodedInteger = this.codec.decodeStringAsInteger(string);
public int deobfuscate(final String string) {
int decodedInteger = this.codec.decodeStringAsInteger(string);

for (int i = this.transformers.length - 1; i >= 0; i--) {
decodedInteger = this.transformers[i].reverseTransformInteger(decodedInteger);
Expand Down
Loading

0 comments on commit 2a07702

Please sign in to comment.