Last time we showed how you can take any two coprime positive integers
m and compute a third positive integer
y with the property that
(x * y) % m == 1, and therefore that
(x * z * y) % m == z % m for any positive integer
z. That is, there always exists a "multiplicative inverse", that "undoes" the results of multiplying by
m. That's pretty neat, but of what use is it?
Well, here's a question I occasionally get:
My service has an array of n objects, obviously indexed from 0 to n-1, each of which contains state that is relevant to some client. I don't want to pass the object to the client directly; instead I want to give them an integer token that they can use to query the server about the state of the object. The obvious token is the index of the array, but I am worried that the client developer will take an unwarranted dependency on the fact that the token is a small integer, rather than treating it as a meaningless opaque handle.1 I want to obfuscate the token I pass back. I could generate a sequence of unique random numbers and maintain a two-way dictionary mapping indices to tokens and vice versa, but that sounds like (1) work, and (2) possible bugs, and (3) extra time and memory.
I issue a few hundred invoices a year, each with an invoice number. For various reasons I would rather not send out sequential invoice numbers. (For instance: it is too easy to make confusing transposition or off-by-one errors with sequential numbers.) But yet I want to be able to easily sort my invoices by the actual order in which they were produced.
Or any number of other equivalent statements of the problem.
This problem is easily solved with multiplicative inverses. Start by picking a large number
m, much larger than the number of tokens you're ever going to need to generate. Let's say 1000000000. Now pick any number
x coprime to that; lets say 387420489.2 Now compute the multiplicative inverse of
x using our large integer library, which turns out to be 513180409.
Now we're all set. We encode the numbers 0 through n-1 via:
Integer encoded = invoiceNumber * i387420489 % i1000000000;
This gives us a unique random-seeming number between 0 and 999999999. We know the number will be different for every input.
We decode it with the inverse:
Integer decoded = encoded * i513180409 % i1000000000;
Pretty neat, and very cheap. Not cryptographically strong, but any two sequential tokens look very different from each other.
- Note that I am assuming that in this scenario the client is not actively hostile towards the server, but rather potentially buggy. ↩
- We know at a glance that these numbers are coprime because 1000000000 has only factors that are divisible by 2 or 5, but a number ending in a 9 cannot possibly be divisible by 2 or 5; numbers divisible by 2 or 5 end in 0, 2, 4, 5, 6 or 8. ↩