我正在使用一个API,它希望我为使用API的事务生成不透明的“引用ID”,换句话说,就是用户无法以任何方式猜测或推断的唯一引用。 (是'推断'适当的英语?)
这是我目前一起入侵的地方:
randomRef = randint(0, 99999999999999)
while Transaction.objects.filter(transactionRef = randomRef).count():
randomRef = randint(0, 99999999999999)
Transaction.objects.create(user=user, transactionRef=randomRef, price=999)
不幸的是,我的数据库目前似乎缺少交易。我已经意识到我的方法并不特别是线程安全(假设我在多个mod_wsgi apache线程上运行相同的django代码,它们都可以生成相同的randomRef!)
有没有人有更好的技巧为我生成随机主键?
答案 0 :(得分:16)
为什么不加密正常的顺序ID呢?对于不知道加密密钥的人来说,ID似乎是随机的。您可以编写一个包装器,在发送到DB的路上自动解密ID,并在数据库的路上对其进行加密。
答案 1 :(得分:11)
我根据这个问题创建了一个要点:https://gist.github.com/735861
根据Amber的建议,使用DES对私钥进行加密和解密。加密密钥在基数36中表示,但只要表示是唯一的,任何其他基于字符的表示都将起作用。
任何需要这种加密私钥表示的模型只需要从代码中显示的模型和管理器继承。
以下是代码的内容:
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
对于名为Transaction
的{{1}}对象,transaction
将返回私钥的加密表示。 transaction.encrypted_pk
将根据加密的私钥表示搜索对象。
应该注意的是,此代码仅适用于可以正确表示为长值的私钥。
答案 2 :(得分:6)
jbrendel创建了一个类,您只需继承该类即可获取自定义ID。
答案 3 :(得分:2)
您应该能够将数据库中的transactionRef列设置为唯一。这样,数据库将不允许使用相同的transactionRef值添加事务。一种可能性是randomly generate UUIDs - 随机UUID碰撞的概率非常小。
答案 4 :(得分:2)
随机整数不是唯一的,主键必须是唯一的。将关键字段设为char(32)并改为尝试:
from uuid import uuid4 as uuid
randomRef = uuid().hex
UUIDs非常善于提供独特性。
答案 5 :(得分:2)
os.urandom(n)可以“返回一串适合加密使用的n个随机字节”。只需确保n
足够大,2**(8*n)
远远超过您要识别的“唯一”键数量的平方,您就可以冒险碰撞低至你想要的。例如,如果您认为最终可能会有十亿笔交易(大约2**30
),n=8
可能就足够了(但是请保持安全,并使用更大的n
;-)