Sometimes it’s nice to have non-consecutive user-facing IDs. E.g. if you don’t want to show away how many items you have in your database. Common approaches are UUIDs or encoding random bytes as base64. The downside is that those need an extra database column. For example, map (0 -> 29319, 1 -> 58998, 2 -> 29598, …).
Symmetric encryption, e.g. AES takes a block of bytes and transforms them to something random-looking so statistical attacks won’t work. The cool thing is that an inverse transform exists that uniquely maps the random looking bytes back to the original text.
AES is the name of the standard. The algorithm is a substitution-permutation network. It works in two steps. First each byte gets substituted by another byte. The substitution table is called sbox and has specific properties. E.g. a bit flip in the input flips roughly half the bits in the output. Generating good quality sboxes is complicated. For our trivial application we can re-use the AES Rijndael sbox.
In the second step we apply a permutation on every bit. This permutation can be understood as the secret key.
In code. First we split a 2-byte input integer into two bytes, apply the sbox, then transform back to a 2-byte integer. The second argument, sbox
is a 256-element array, the Rijndael sbox, containing a replacement byte at each offset.
def apply_sbox(x, sbox):
a = (x & (2**8 - 1 << 0))
b = (x & (2**8 - 1 << 8)) >> 8
return sbox[a] | sbox[b] << 8
Applying a bit permutation looks like this:
def permute(x, permutation):
bits = '{:016b}'.format(x)
new_bits = ''.join(bits[i] for i in permutation)
return int(new_bits, 2)
The sboxes can be copied from many sources. They look like this. Generating a permutation is simple:
import random
permutation = range(16)
random.shuffle(permutation)
We could generate the inverse by adding an argument sort function. Luckily numpy already has one of those:
import numpy
inverse_permutation = numpy.argsort(permutation)
The permutation is your secret key, so you want to generate it only once and remember the output instead of generating a new one every time. Now it is trivial to plug the functions together to generate a complete example.
Number: 0, shuffled: 29319, unshuffled: 0
Number: 1, shuffled: 58998, unshuffled: 1
Number: 2, shuffled: 29598, unshuffled: 2
Number: 3, shuffled: 21568, unshuffled: 3
Number: 4, shuffled: 58307, unshuffled: 4
Number: 5, shuffled: 22081, unshuffled: 5
Number: 6, shuffled: 22368, unshuffled: 6
Number: 7, shuffled: 43366, unshuffled: 7
Number: 8, shuffled: 65178, unshuffled: 8
Number: 9, shuffled: 6751 , unshuffled: 9