Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating django objects with a random primary key

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?

like image 486
rdrey Avatar asked Jul 21 '10 00:07

rdrey


2 Answers

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.

like image 114
Amber Avatar answered Sep 20 '22 21:09

Amber


I created a gist based on this question: https://gist.github.com/735861

Following Amber's advice, the private keys are encrypted and decrypted using DES. The encrypted key is represented in base 36, but any other character-based representation will work as long as the representation is unique.

Any model that would need this kind of encrypted private key representation only needs to inherit from the model and manager shown in the code.

Here's the meat of the code:

import struct
from Crypto.Cipher import DES
from django.db import models

class EncryptedPKModelManager(models.Manager):
    """Allows models to be identified based on their encrypted_pk value."""
    def get(self, *args, **kwargs):
        encrypted_pk = kwargs.pop('encrypted_pk', None)
        if encrypted_pk:
            kwargs['pk'] = struct.unpack('<Q', self.model.encryption.decrypt(
                struct.pack('<Q', encrypted_pk)
            ))[0]
        return super(EncryptedPKModelManager, self).get(*args, **kwargs)


class EncryptedPKModel(models.Model):
    """Adds encrypted_pk property to children."""
    encryption = DES.new('8charkey') # Change this 8 character secret key

    def _encrypted_pk(self):
        return struct.unpack('<Q', self.encryption_obj.encrypt(
            str(struct.pack('<Q', self.pk))
        ))[0]

    encrypted_pk = property(_encrypted_pk)

    class Meta:
        abstract = True

For a Transaction object called transaction, transaction.encrypted_pk would return an encrypted representation of the private key. Transaction.objects.get(encrypted_pk=some_value) would search for objects based on the encrypted private key representation.

It should be noted that this code assumes only works for private keys that can be represented properly as long values.

like image 35
Trey Hunner Avatar answered Sep 20 '22 21:09

Trey Hunner