2010-07-21 8 views
9

Ich arbeite mit einer API, die möchte, dass ich opake "Referenz-IDs" für Transaktionen mit ihrer API erzeuge, mit anderen Worten, eindeutige Referenzen, die Benutzer nicht erraten oder ableiten können Weg. (Ist die richtige Englisch ‚schließen‘?)Erstellen von Django-Objekten mit einem zufälligen Primärschlüssel

Dies ist, was ich zusammen zur Zeit gehackt haben:

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

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

leider meine Datenbank scheint Transaktionen zur Zeit zu fehlen. Ich habe festgestellt, dass meine Methode nicht besonders threadsicher ist (sagen wir, dass ich denselben django-Code auf mehreren mod_wsgi-Apache-Threads verwende, sie könnten alle denselben randomRef erzeugen!)

Hat jemand einen besseren Trick? Zufalls-Primärschlüssel für mich generieren?

+2

Ja, "infer" ist in Ordnung. – derekerdmann

Antwort

16

Warum verschlüsseln Sie nicht einfach die normalen sequenziellen IDs? Für jemanden, der den Verschlüsselungsschlüssel nicht kennt, erscheinen die IDs genauso zufällig. Sie können einen Wrapper schreiben, der die ID auf dem Weg zur DB automatisch entschlüsselt und auf dem Weg von der DB verschlüsselt.

+2

+1. Auf diese Weise vermeiden Sie Kollisionen. –

+0

das ist eine großartige Idee, danke. Ich überlegte, eine Kombination aus Benutzer-ID und Standard transaction.id Hashing, aber das ist nicht garantiert, dass auch nicht eindeutig sein. Kannst du eine gute Python-Funktion zum Verschlüsseln vorschlagen? Ich habe m2crypto als Voraussetzung für ein anderes Paket installiert, aber noch nicht wirklich genutzt. – rdrey

+0

Sorry @AlexBliskovsky, ich bin ein Krypto n00b. Gibt es eine Möglichkeit, die verschlüsselte Ausgabe auf eine kleine ganze Zahl anzugeben? Ist es konzeptionell möglich (um sicherzustellen, dass es keine Kollision gibt, wenn wir nicht sicherstellen, dass die Domäne des Klartextes kleiner ist als die Domäne des verschlüsselten Texts)? – xster

2

Sie sollten in der Lage sein, die transactionRef-Spalte in Ihrer Datenbank eindeutig zu machen. Auf diese Weise kann die Datenbank keine Transaktionen mit demselben transactionRef-Wert hinzufügen. Eine Möglichkeit ist randomly generate UUIDs - die Wahrscheinlichkeit, dass zufällige UUIDs kollidieren, ist extrem gering.

2

Zufallszahlen sind nicht eindeutig und Primärschlüssel müssen eindeutig sein. Machen Sie das Schlüsselfeld ein char (32) und versuchen Sie stattdessen:

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

UUIDs sehr gut sind Einzigartigkeit bieten.

+1

Zwei UUIDs, die von uuid4 generiert werden, sind nicht garantiert einzigartig, es gibt nur eine extrem kleine Chance (aber nicht Null) Kollision. –

+0

danke, ich habe noch nie von UUIDs gehört, sie scheinen viel schöner als mein jetziger randint. sollte ich noch Dinge durch eine Schleife laufen lassen, um sicherzustellen, dass die UUID nicht verwendet wird? – rdrey

+0

Ihre Datenbankschicht löst eine Ausnahme aus, wenn Sie versuchen, einen nicht eindeutigen Schlüssel einzufügen. Sie können diese Ausnahme abfangen und eine andere UUID erstellen. Obwohl ich, als ich es gepostet habe, dachte, das sei eine gute Antwort, haben sowohl Alex Martelli als auch Amber bessere Vorschläge gemacht, wenn Sie sie umsetzen können. –

2

os.urandom(n) kann "eine Zeichenfolge mit n zufälligen Bytes zurückgeben, die für die Verschlüsselung geeignet sind". Stellen Sie nur sicher, dass n groß genug ist für 2**(8*n) zu weit über das Quadrat der Anzahl der "einzigartigen" Schlüssel, die Sie identifizieren möchten, und Sie können das Risiko einer Kollision so gering wie Sie wollen. Zum Beispiel, wenn Sie denken, dass Sie vielleicht mit einer Milliarde Transaktionen enden (etwa 2**30), könnte n=8 ausreichen (aber gehen Sie auf Nummer sicher und verwenden Sie einen etwas größeren Wert von n ;-).

11

habe ich einen Kern zu dieser Frage zugrunde: https://gist.github.com/735861

Ambers Beratung Folgen, sind die privaten Schlüssel verschlüsselt und entschlüsselt DES verwenden. Der verschlüsselte Schlüssel wird in der Basis 36 dargestellt, aber jede andere zeichenbasierte Darstellung funktioniert, solange die Darstellung eindeutig ist.

Jedes Modell, das diese Art von verschlüsselter Darstellung des privaten Schlüssels benötigt, muss nur von dem im Code gezeigten Modell und Manager erben.

Hier ist das Fleisch des Codes:

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 

Für ein Transaction Objekt namens transaction, transaction.encrypted_pk würde eine verschlüsselte Darstellung des privaten Schlüssels zurück. Transaction.objects.get(encrypted_pk=some_value) würde auf der Basis der verschlüsselten Darstellung des privaten Schlüssels nach Objekten suchen.

Es sollte beachtet werden, dass dieser Code davon ausgeht, dass er nur für private Schlüssel funktioniert, die ordnungsgemäß als lange Werte dargestellt werden können.

6

jbrendel erstellt eine Klasse, die Sie einfach erben können, um eine benutzerdefinierte ID zu erhalten.

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

+0

danke, sieht für jeden anderen mit diesem Problem eines Tages nützlich! – rdrey

+1

Es scheint nicht mit Django Admin zu arbeiten. Es wird mir nicht erlauben, einen neuen Datensatz mit einer leeren ID zu speichern, weil es ein Pflichtfeld ist; Wenn ich eine ID definiere, wird die zufällige nicht generiert. Vielleicht funktionierte das nur in einer früheren Django-Version? – sherbang

+0

@sherbang Um es mit admin arbeiten zu lassen, modifiziere einfach 'if self.id: 'part. Wenn Sie in den Kommentaren genau hinsehen, heißt es: "Anscheinend kennen wir unsere ID schon, also müssen wir hier nichts Besonderes machen." – shreks7

Verwandte Themen