如何创建加密的django字段,在从数据库中检索数据时转换数据?

Ove*_*d D 14 python django pycrypto

我有一个自定义的EncryptedCharField,我希望在连接UI时基本上显示为CharField,但在数据库中存储/检索之前,它会加密/解密它.

自定义字段的文件说要:

  1. __metaclass__ = models.SubfieldBase
  2. 覆盖to_python以将数据从其原始存储转换为所需的格式
  3. 覆盖get_prep_value以在存储db之前转换该值.

因此,您认为这很容易 - 只需要解密该值,然后再加密它.

松散地基于django片段,该字段的文档如下:

class EncryptedCharField(models.CharField):
  """Just like a char field, but encrypts the value before it enters the database, and    decrypts it when it
  retrieves it"""
  __metaclass__ = models.SubfieldBase
  def __init__(self, *args, **kwargs):
    super(EncryptedCharField, self).__init__(*args, **kwargs)
    cipher_type = kwargs.pop('cipher', 'AES')
    self.encryptor = Encryptor(cipher_type)

  def get_prep_value(self, value):
     return encrypt_if_not_encrypted(value, self.encryptor)

  def to_python(self, value):
    return decrypt_if_not_decrypted(value, self.encryptor)


def encrypt_if_not_encrypted(value, encryptor):
  if isinstance(value, EncryptedString):
    return value
  else:
    encrypted = encryptor.encrypt(value)
    return EncryptedString(encrypted)

def decrypt_if_not_decrypted(value, encryptor):
  if isinstance(value, DecryptedString):
    return value
  else:
    encrypted = encryptor.decrypt(value)
    return DecryptedString(encrypted)


class EncryptedString(str):
  pass

class DecryptedString(str):
  pass
Run Code Online (Sandbox Code Playgroud)

加密器看起来像:

class Encryptor(object):
  def __init__(self, cipher_type):
    imp = __import__('Crypto.Cipher', globals(), locals(), [cipher_type], -1)
    self.cipher = getattr(imp, cipher_type).new(settings.SECRET_KEY[:32])

  def decrypt(self, value):
    #values should always be encrypted no matter what!
    #raise an error if tthings may have been tampered with
    return self.cipher.decrypt(binascii.a2b_hex(str(value))).split('\0')[0]

  def encrypt(self, value):
    if value is not None and not isinstance(value, EncryptedString):
      padding  = self.cipher.block_size - len(value) % self.cipher.block_size
      if padding and padding < self.cipher.block_size:
        value += "\0" + ''.join([random.choice(string.printable) for index in range(padding-1)])
      value = EncryptedString(binascii.b2a_hex(self.cipher.encrypt(value)))
    return value
Run Code Online (Sandbox Code Playgroud)

保存模型时,由于尝试解密已经解密的字符串,会发生错误,奇数长度字符串.在调试时,看起来to_python最终被调用两次,第一次带有加密值,第二次带有解密值,但实际上不是Decrypted类型,而是作为原始字符串,导致错误.此外,永远不会调用get_prep_value.

我究竟做错了什么?

这应该不是那么难 - 有没有其他人认为这个Django字段代码写得很差,特别是在涉及自定义字段时,而不是那么可扩展?简单的可覆盖的pre_save和post_fetch方法可以轻松解决此问题.

mau*_*k13 8

我认为问题是,当您为自定义字段分配值时也会调用to_python(因为验证的一部分可能是基于此链接).所以问题是在以下情况下区分to_python调用:

  1. 当Django将数据库中的值分配给字段时(那时您要解密该值)
  2. 手动为自定义字段分配值时,例如record.field = value

你可以使用的一个hack是为值字符串添加前缀或后缀并检查它而不是进行isinstance检查.

我打算写一个例子,但我找到了这个(甚至更好:)).

检查BaseEncryptedField:https: //github.com/django-extensions/django-extensions/blob/master/django_extensions/db/fields/encrypted.py

来源: Django自定义字段:只对DB的值运行to_python()?