Reputation: 32933
I have an input string with this form:
<single char><int starting at 1, going up>
and want to produce, for each:
<the same single char><a unique 8 digit number>
I.e.:
"s1" => "s78138782"
"t1" => "t18743682"
"t2" => "t49274876"
Not allowed - we've already used that 8-digit number:
"t1" => "t78138782"
This doesn't need to be reversible, i.e., I don't need to be able to convert "s78138782
" back into s1
algorithmically. I'm saving them both in the database so I can look it up. I just want the numbers to look random as I step up through t1
, t2
, t3
, etc., and to not be repeated.
Obviously the 8-character number will place a limit on the number of unique starting strings I can have: I'm fine with that at this stage.
Can anyone give me an algorithm for this? I'm doing this in Ruby so Ruby would be ideal, and ideally using the standard Ruby/Rails gems.
Upvotes: 1
Views: 193
Reputation: 241671
A common solution to this problem, which has the benefit of being reversible, is to select a random encryption key for the database, and then encrypt each input string. In order to make the target strings the right size, you first pad the input strings with some character which cannot be part of the string, such as a space.
Note that this procedure is not secure, but it is pseudo-random (or at least, it looks random) and it avoids any chance of collision.
Unfortunately, I don't know anything about Ruby, but I threw together this example in Python:
import Crypto.Cipher.Blowfish
import re
import struct
parse_id = re.compile("^(\D+)(\d+)$")
cipher = Crypto.Cipher.Blowfish.new("badsecret",
Crypto.Cipher.Blowfish.MODE_ECB)
def randomize(id):
pfx, integer = parse_id.match(id).groups()
return "%c%d" % (
pfx,
struct.unpack("!Q",
cipher.encrypt(pfx
+ struct.pack("!Q",
int(integer))[len(pfx):]))[0])
Then I tested it:
>>> for i in range(8): print ("t" + str(i), randomize("t" + str(i)))
...
('t0', 't8812720357104479300')
('t1', 't14570648240240394176')
('t2', 't13775280166960833565')
('t3', 't6391672674195357485')
('t4', 't3595757360042384213')
('t5', 't10728238663553328366')
('t6', 't888684936954575988')
('t7', 't9447169127882289438')
>>> for i in range(8): print ("s" + str(i), randomize("s" + str(i)))
...
('s0', 's9209414168426526439')
('s1', 's5452467189798635654')
('s2', 's10995755223696930463')
('s3', 's1237785964853872245')
('s4', 's4976813073866522017')
('s5', 's17045636624557288261')
('s6', 's14217087933089289315')
('s7', 's3504968071130220057')
Making the numbers shorter requires finding a block cipher with a smaller block or using a stream cipher. I don't know what Ruby has to offer in terms of crypto libraries. (Indeed, I barely knew anything about Python's crypto support before I edited this answer.)
Upvotes: 1
Reputation: 717
If you are just looking for a pseudo random looking numberI would just generate a random number using the rand() function and add the index to the random number that way you don't have to worry about collisions.
Upvotes: 0