-
-
Save sherbang/5061524 to your computer and use it in GitHub Desktop.
Encrypt and decrypt Django model primary key values (useful for publicly viewable unique identifiers) Depends on pycrypto
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This code is under the MIT license. | |
# Inspired by this StackOverflow question: | |
# http://stackoverflow.com/questions/3295405/creating-django-objects-with-a-random-primary-key | |
import struct | |
from Crypto.Cipher import DES | |
from django.db import models | |
def base36encode(number): | |
"""Encode number to string of alphanumeric characters (0 to z). (Code taken from Wikipedia).""" | |
if not isinstance(number, (int, long)): | |
raise TypeError('number must be an integer') | |
if number < 0: | |
raise ValueError('number must be positive') | |
alphabet = '0123456789abcdefghijklmnopqrstuvwxyz' | |
base36 = '' | |
while number: | |
number, i = divmod(number, 36) | |
base36 = alphabet[i] + base36 | |
return base36 or alphabet[0] | |
def base36decode(numstr): | |
"""Convert a base-36 string (made of alphanumeric characters) to its numeric value.""" | |
return int(numstr,36) | |
class EncryptedPKModelQuerySet(models.query.QuerySet): | |
"""This queryset 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: | |
# If found, decrypt encrypted_pk argument and set pk argument to the appropriate value | |
encryption_obj = DES.new(self.model.PK_SECRET_KEY) # This 8 character secret key MUST be changed! | |
kwargs['pk'] = struct.unpack('<Q', encryption_obj.decrypt( | |
struct.pack('<Q', base36decode(encrypted_pk)) | |
))[0] | |
return super(EncryptedPKModelQuerySet, self).get(*args, **kwargs) | |
class EncryptedPKModelManager(models.Manager): | |
"""This manager allows models to be identified based on their encrypted_pk value.""" | |
def get_query_set(self): | |
return EncryptedPKModelQuerySet(self.model) | |
class EncryptedPKModel(models.Model): | |
"""Adds encrypted_pk property to children which returns the encrypted value of the primary key.""" | |
def __init__(self, *args, **kwargs): | |
super(EncryptedPKModel, self).__init__(*args, **kwargs) | |
setattr( | |
self.__class__, | |
"encrypted_%s" % (self._meta.pk.name,), | |
property(self.__class__._encrypted_pk) | |
) | |
def _encrypted_pk(self): | |
if self.pk == None: return None | |
encryption_obj = DES.new(self.PK_SECRET_KEY) # This 8 character secret key should be changed! | |
return base36encode(struct.unpack('<Q', encryption_obj.encrypt( | |
str(struct.pack('<Q', self.pk)) | |
))[0]) | |
encrypted_pk = property(_encrypted_pk) | |
objects = EncryptedPKModelManager() | |
class Meta: | |
abstract = True | |
class ExampleModel(EncryptedPKModel): | |
PK_SECRET_KEY = '8charkey' | |
example_field = models.CharField(max_length=32) | |
# Example usage: | |
# example_instance = ExampleModel.objects.get(pk=1) | |
# url_pk = example_instance.encrypted_pk | |
# ExampleModel.objects.get(encrypted_pk=url_pk) | |
# # or | |
# ExampleModel.objects.all().get(encrypted_pk=url_pk) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment