views:

88

answers:

4

Hi guys!

I'm working with an API that wants me to generate opaque "reference IDs" for transactions with their API, in other words, unique references that users can't guess or infer in any way. (is 'infer' proper english?)

This is what I've hacked together currently:

randomRef = randint(0, 99999999999999)
while Transaction.objects.filter(transactionRef = randomRef).count():
    randomRef = randint(0, 99999999999999)

Transaction.objects.create(user=user, transactionRef=randomRef, price=999)

unfortunately my database seems to be missing transactions at the moment. I've realized that my method isn't particularly thread safe (say I'm running the same django code on multiple mod_wsgi apache threads, they could all be generating the same randomRef!)

Has anyone got a nicer trick to generate random primary keys for me?

Thanks a lot!

EDIT: All answers were very valid solutions, thanks a lot! I went with marking Amber's as accepted since you guys thought it was the best answer!

+3  A: 

Why not just encrypt the normal sequential ids instead? To someone who doesn't know the encryption key, the ids will seem just as random. You can write a wrapper that automatically decrypts the ID on the way to the DB, and encrypts it on the way from the DB.

Amber
+1. You avoid collisions this way, too.
Alex Bliskovsky
this is a great idea, thanks. I considered hashing a combination of userid and standard transaction.id, but that's not guaranteed to be unique either. can you suggest a good python function to encrypt with? I have installed m2crypto as a prerequisite for another package, but not really used it yet.
rdrey
+1  A: 

You should be able to set the transactionRef column in your database to be unique. That way, the database will not allow transactions to be added with the same transactionRef value. One possibility is to randomly generate UUIDs -- the probability of random UUIDs colliding is extremely small.

Michael Williamson
+1  A: 

Random integers are not unique, and primary keys must be unique. Make the key field a char(32) and try this instead:

from uuid import uuid4 as uuid
randomRef = uuid().hex

UUIDs are very good at providing uniqueness.

Jesse Dhillon
Two UUIDs generated by uuid4 are not guaranteed to be unique, there's just an extremely small chance (but non-zero) chance of a collision.
Michael Williamson
thanks, I have never heard of UUIDs before, they seem much nicer than my current randint. should i still run things through a loop to make sure that the UUID is unused?
rdrey
Your database layer will throw an exception if you try to insert a non-unique key. You can catch that exception and make another UUID. Although when I posted it I thought this was a good answer, both Alex Martelli and Amber have given better suggestions if you can implement them.
Jesse Dhillon
+1  A: 

os.urandom(n) can "Return a string of n random bytes suitable for cryptographic use". Just make sure that n is large enough for 2**(8*n) to be well above the square of the number of "unique" keys you want to identify, and you can make the risk of collision as low as you want. For example, if you think you may end up with maybe a billion transactions (about 2**30), n=8 might suffice (but play it safe and use a somewhat larger n anyway;-).

Alex Martelli