每次读取或加载模型数据时,有没有办法强制Django模型将字段传递给MySQL函数?为了澄清我在SQL中的含义,我希望Django模型能够产生如下内容:
在模型加载时:SELECT AES_DECRYPT(fieldname,password)FROM tablename
在模型保存上:INSERT INTO tablename VALUES(AES_ENCRYPT(userinput,password))
答案 0 :(得分:6)
您可以在模型上创建属性,而不是在模型加载上,当访问该属性时,它可以读取数据库:
def _get_foobar(self):
if not hasattr(self, '_foobar'):
cursor = connection.cursor()
self._foobar = cursor.execute('SELECT AES_DECRYPT(fieldname, password) FROM tablename')[0]
return self._foobar
foobar = property(_get_foobar)
现在加载后,您可以引用mything.foobar
,第一次访问将从数据库中检索解密,并保留以供以后访问。
这也有一个好处,如果你的一些代码没有用于解密,它就不会发生。
答案 1 :(得分:4)
我会为您想要加密/解密的列定义custom modelfield。在加载模型时覆盖to_python
方法以运行解密,并在保存时覆盖get_db_prep_value
以运行加密。
请记住将字段的元类设置为models.SubfieldBase
,否则不会调用这些方法。
答案 2 :(得分:3)
这是一个有效的解决方案,部分基于(http://www.djangosnippets.org/snippets/824/):
class Employee(models.Model):
social_security_number = models.CharField(max_length=32)
def _get_ssn(self):
cursor = connection.cursor()
cursor.execute("SELECT AES_DECRYPT(UNHEX(social_security_number), %s) as ssn FROM tablename WHERE id=%s", [settings.SECRET_KEY, self.id])
return cursor.fetchone()[0]
def _set_ssn(self, ssn_value):
cursor = connection.cursor()
cursor.execute("SELECT HEX(AES_ENCRYPT(%s, %s)) as ssn", [ssn_value, settings.SECRET_KEY])
self.social_security_number = cursor.fetchone()[0]
ssn = property(_get_ssn, _set_ssn)
结果:
>>> from foo.bar.models import Employee
>>> p=Employee.objects.create(ssn='123-45-6789')
>>> p.ssn
'123-45-6789'
mysql> select * from foo_employee;
+----+----------------------------------+
| id | social_security_number |
+----+----------------------------------+
| 31 | 41DF2D946C9186BEF77DD3307B85CC8C |
+----+----------------------------------+
1 row in set (0.00 sec)
答案 3 :(得分:1)
这绝对是hackish,但似乎Django现在不会让你以任何其他方式做到这一点。值得注意的是,每次更改python中的值时,除了首次加载时,都会调用to_python。
from django.db import connection, models
import re
class EncryptedField(models.TextField):
__metaclass__ = models.SubfieldBase
def to_python(self, value):
if not re.match('^*some pattern here*$', value):
cursor = connection.cursor()
cursor.execute('SELECT AES_DECRYPT(%s, %s)', [value, settings.SECRET_KEY])
return cursor.fetchone()[0]
return value
def get_db_prep_value(self, value):
cursor = connection.cursor()
cursor.execute('SELECT AES_ENCRYPT(%s, %s)', [value, settings.SECRET_KEY])
return cursor.fetchone()[0]
class Encrypt(models.Model):
encrypted = EncryptedField(max_length = 32)
答案 4 :(得分:1)
深入搜索Django ORM的实现后,
我发现它可以通过以下方式解决:
class EncryptedField(models.BinaryField):
@staticmethod
def _pad(value):
return value + (AES.block_size - len(value) % AES.block_size) * b('\x00')
def _encrypt(self, data):
if not data:
return None
return self.cipher.encrypt(self._pad(data.encode('utf8')))
def _decrypt(self, data):
if not data:
return None
return self.cipher.decrypt(force_bytes(data)).rstrip(b'\x00').decode('utf8')
@property
def cipher(self):
return AES.new(KEY, mode=AES.MODE_CBC, IV=self._iv)
def get_db_prep_value(self, value, connection, prepared=False):
if value is not None:
value = self._encrypt(value)
if value:
value = binascii.hexlify(value)
return value
def get_placeholder(self, value, compiler, connection):
return 'unhex(%s)'
答案 5 :(得分:0)
使用Django signals,您可以在保存模型实例时执行操作,但据我所知,您无法在读取时触发任何内容。
编辑:我的不好,似乎你可以在初始化模型实例时做些事情。