加密
ormar 为您提供了一种仅加密数据库中字段的方法。提供的加密后端允许单向加密(HASH 后端)以及双向加密/解密(FERNET 后端)。
!!!警告 请注意,为了使加密工作,您需要安装可选的加密包。
| You can do it manually `pip install cryptography` or with ormar by `pip install ormar[crypto]`
|
!!!警告注意,添加 encrypt_backend 会将数据库列类型更改为 TEXT,这需要通过迁移(alembic)或手动更改在 db 中反映出来
定义字段加密
要加密字段,您需要至少传递 encrypt_secret 和 encrypt_backend 参数。
1
2
3
4
5
6
7
8
9
10
11
12 | base_ormar_config = ormar.OrmarConfig(
metadata=metadata
database=database
)
class Filter(ormar.Model):
ormar_config = base_ormar_config.copy()
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100,
encrypt_secret="secret123",
encrypt_backend=ormar.EncryptBackends.FERNET)
|
!!!警告您可以加密除primary_key 列和关系列(ForeignKey 和ManyToMany)之外的所有字段类型。检查后端详细信息以获取更多信息。
可用的后端
哈希
HASH 是一种单向哈希(如密码),在检索时永远不会解密
要设置它,请传递适当的后端值。
| ... # rest of model definition
password: str = ormar.String(max_length=128,
encrypt_secret="secret123",
encrypt_backend=ormar.EncryptBackends.HASH)
|
请注意,由于此后端永远不会解密存储的值,因此它仅适用于字符串字段。使用的哈希是 sha512 哈希,因此字段长度必须 >=128。
警告注意,在 HASH 后端中,您可以按完整值进行过滤,但像 contains 这样的过滤器将不起作用,因为对加密值进行比较
!!!note 请注意,提供的 encrypt_secret 首先对其自身进行哈希处理并用作盐,因此为了与存储的字符串进行比较,您需要重新创建此步骤。 order_by 将不起作用,因为比较加密的字符串,因此您无法可靠地排序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | class Hash(ormar.Model):
ormar_config = base_ormar_config.copy(tablename="hashes")
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=128,
encrypt_secret="udxc32",
encrypt_backend=ormar.EncryptBackends.HASH)
await Hash(name='test1').save()
# note the steps to recreate the stored value
# you can use also cryptography package instead of hashlib
secret = hashlib.sha256("udxc32".encode()).digest()
secret = base64.urlsafe_b64encode(secret)
hashed_test1 = hashlib.sha512(secret + 'test1'.encode()).hexdigest()
# full value comparison works
hash1 = await Hash.objects.get(name='test1')
assert hash1.name == hashed_test1
# but partial comparison does not (hashed strings are compared)
with pytest.raises(NoMatch):
await Filter.objects.get(name__icontains='test')
|
费内特
FERNET 是双向加密/解密后端
要设置它,请传递适当的后端值。
| ... # rest of model definition
year: int = ormar.Integer(encrypt_secret="secret123",
encrypt_backend=ormar.EncryptBackends.FERNET)
|
值在到达数据库端的途中被加密,在出去时被解密。可用于所有类型,因为返回值被解析为相应的 python 类型。
!!!警告注意,在 FERNET 后端中,您完全失去了过滤的可能性,因为加密值的一部分是时间戳。 order_by 也是如此,因为比较加密的字符串,因此您无法可靠地排序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | class Filter(ormar.Model):
ormar_config = base_ormar_config.copy()
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100,
encrypt_secret="asd123",
encrypt_backend=ormar.EncryptBackends.FERNET)
await Filter(name='test1').save()
await Filter(name='test1').save()
# values are properly encrypted and later decrypted
filters = await Filter.objects.all()
assert filters[0].name == filters[1].name == 'test1'
# but you cannot filter at all since part of the fernet hash is a timestamp
# which means that even if you encrypt the same string 2 times it will be different
with pytest.raises(NoMatch):
await Filter.objects.get(name='test1')
|
自定义后端
如果您希望支持其他类型的加密(即 AES),您可以提供自己的 EncryptionBackend。
要设置后端,您需要做的就是子类化 ormar.fields.EncryptBackend 类并提供所需的后端。
示例虚拟后端(不执行任何操作)可能如下所示:
| class DummyBackend(ormar.fields.EncryptBackend):
def _initialize_backend(self, secret_key: bytes) -> None:
pass
def encrypt(self, value: Any) -> str:
return value
def decrypt(self, value: Any) -> str:
return value
|
要使用此后端,请将 encrypt_backend 设置为 CUSTOM,并通过 encrypt_custom_backend 提供您的后端作为参数。
| class Filter(ormar.Model):
ormar_config = base_ormar_config.copy()
id: int = ormar.Integer(primary_key=True)
name: str = ormar.String(max_length=100,
encrypt_secret="secret123",
encrypt_backend=ormar.EncryptBackends.CUSTOM,
encrypt_custom_backend=DummyBackend
)
|