2010-07-21 16 views
9

Estoy trabajando con una API que quiere que genere "ID de referencia" opacos para transacciones con su API, en otras palabras, referencias únicas que los usuarios no pueden adivinar o inferir en ningún camino. (Se 'deducir' Inglés adecuada?)Creando objetos django con una clave primaria aleatoria

Esto es lo que he hackeado la actualidad:

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

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

por desgracia mi base de datos parece que falta transacciones en el momento. Me he dado cuenta de que mi método no es especialmente seguro (digo que estoy ejecutando el mismo código django en varios hilos apache mod_wsgi, todos podrían estar generando el mismo randomRef!)

¿Alguien ha conseguido un truco más agradable para generar claves primarias al azar para mí?

+2

Sí, "infer" is fine. – derekerdmann

Respuesta

16

¿Por qué no cifrar los identificadores secuenciales normales en su lugar? Para alguien que no conoce la clave de cifrado, los identificadores parecerán aleatorios. Puede escribir un contenedor que descifra automáticamente el ID en el camino al DB y lo cifra en el camino desde el DB.

+2

+1. También evitas colisiones de esta manera. –

+0

esta es una gran idea, gracias. Consideré mezclar una combinación de ID de usuario y transaction.id estándar, pero tampoco está garantizado que sea único. ¿Puedes sugerir una buena función de python para encriptar? He instalado m2crypto como un requisito previo para otro paquete, pero aún no lo he usado realmente. – rdrey

+0

Lo siento @AlexBliskovsky, soy un crypto n00b. ¿Hay alguna manera de especificar el resultado cifrado en un entero pequeño? ¿Es conceptualmente posible (para garantizar que no haya colisión a menos que nos aseguremos de que el dominio del texto plano sea más pequeño que el dominio del texto encriptado)? – xster

2

Debería poder establecer la columna transactionRef en su base de datos para que sea única. De esta forma, la base de datos no permitirá que se agreguen transacciones con el mismo valor de transactionRef. Una posibilidad es randomly generate UUIDs: la probabilidad de que los UUID aleatorios colisionen es extremadamente pequeña.

2

Los enteros aleatorios no son únicos, y las claves primarias deben ser únicas. Hacer que el campo clave de un char (32) y probar este lugar:

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

UUIDs son muy buena en proveer singularidad.

+1

No se garantiza que dos UUID generados por uuid4 sean únicos, existe una probabilidad extremadamente pequeña (pero no nula) de que colisión. –

+0

gracias, nunca antes había oído hablar de UUID, parecen mucho mejores que mi randint actual. ¿Debo seguir ejecutando las cosas a través de un ciclo para asegurarme de que el UUID no se utiliza? – rdrey

+0

Su capa de base de datos generará una excepción si intenta insertar una clave que no sea única. Puede atrapar esa excepción y hacer otro UUID. Aunque cuando lo publiqué pensé que esta era una buena respuesta, tanto Alex Martelli como Amber han dado mejores sugerencias si pueden implementarlos. –

2

os.urandom(n) puede "Devolver una cadena de n bytes aleatorios adecuados para el uso criptográfico". Solo asegúrese de que n sea lo suficientemente grande para 2**(8*n) como bien por encima de el cuadrado del número de claves "únicas" que desea identificar, y puede asumir el riesgo de colisión tan bajo como lo desee. Por ejemplo, si crees que puedes terminar con un billón de transacciones (aproximadamente 2**30), n=8 podría ser suficiente (pero juega a lo seguro y usa un n algo más grande ;-).

11

creé una esencia en base a esta pregunta: https://gist.github.com/735861

Siguiendo el consejo de ámbar, las claves privadas se cifran y descifran utilizando DES. La clave cifrada se representa en la base 36, pero cualquier otra representación basada en caracteres funcionará siempre que la representación sea única.

Cualquier modelo que necesite este tipo de representación de clave privada cifrada solo debe heredarse del modelo y el administrador que se muestran en el código.

Aquí está la carne del código:

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 

Para un objeto Transaction llamada transaction, transaction.encrypted_pk volvería una representación cifrada de la clave privada. Transaction.objects.get(encrypted_pk=some_value) buscaría objetos en función de la representación de clave privada encriptada.

Cabe señalar que este código asume que solo funciona para claves privadas que se pueden representar correctamente como valores largos.

6

jbrendel creó una clase que simplemente puede heredar para obtener una ID personalizada.

https://github.com/jbrendel/django-randomprimary

+0

gracias, parece útil para cualquier otra persona con este problema un día! – rdrey

+1

Parece que no funciona con django admin. No me permitirá guardar un nuevo registro con una identificación en blanco porque es un campo obligatorio; si defino una identificación, entonces la aleatoria no se genera. Quizás esto solo funcionó en una versión anterior de Django? – sherbang

+0

@sherbang Para que funcione con admin, solo modifica la parte 'if self.id:'. Si observa detenidamente los comentarios, dice "# Aparentemente, ya conocemos nuestra identificación, por lo que no debemos # hacer nada especial aquí". – shreks7

Cuestiones relacionadas