Skip to content

加密

ormar 为您提供了一种仅加密数据库中字段的方法。提供的加密后端允许单向加密(HASH 后端)以及双向加密/解密(FERNET 后端)。

!!!警告 请注意,为了使加密工作,您需要安装可选的加密包。

1
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 是一种单向哈希(如密码),在检索时永远不会解密

要设置它,请传递适当的后端值。

1
2
3
4
... # 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 是双向加密/解密后端

要设置它,请传递适当的后端值。

1
2
3
... # 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 类并提供所需的后端。

示例虚拟后端(不执行任何操作)可能如下所示:

1
2
3
4
5
6
7
8
9
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 提供您的后端作为参数。

1
2
3
4
5
6
7
8
9
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
                             )