-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcrypto.py
More file actions
55 lines (43 loc) · 1.91 KB
/
Copy pathcrypto.py
File metadata and controls
55 lines (43 loc) · 1.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
"""Field-level encryption at rest using Fernet (AES-128-CBC + HMAC-SHA256).
The operator supplies an arbitrary secret string via ``DB_ENCRYPTION_KEY``; we
derive a stable 32-byte Fernet key from it with SHA-256. When the variable is
unset, encryption is disabled gracefully and values pass through as plaintext,
so the application keeps working without a key configured.
"""
from __future__ import annotations
import base64
import hashlib
import os
from cryptography.fernet import Fernet, InvalidToken
__all__ = ["get_fernet", "encrypt_field", "decrypt_field"]
def get_fernet() -> Fernet | None:
"""Build a Fernet from ``DB_ENCRYPTION_KEY``, or return None if it is unset."""
key = os.environ.get("DB_ENCRYPTION_KEY")
if not key:
return None
# SHA-256 the operator string to a fixed 32 bytes, then base64-url encode it
# into the format Fernet expects.
digest = hashlib.sha256(key.encode("utf-8")).digest()
return Fernet(base64.urlsafe_b64encode(digest))
def encrypt_field(fernet: Fernet | None, value):
"""Encrypt a value for storage.
``None`` passes through unchanged, and when ``fernet`` is None (encryption
disabled) the original value is returned so callers need no branching.
"""
if fernet is None or value is None:
return value
if not isinstance(value, str):
value = str(value)
return fernet.encrypt(value.encode("utf-8")).decode("ascii")
def decrypt_field(fernet: Fernet | None, value):
"""Decrypt a stored value.
``None`` passes through. When encryption is disabled, or the value predates
encryption (i.e. it was stored as plaintext), the value is returned
unchanged — this keeps mixed plaintext/ciphertext data readable.
"""
if fernet is None or value is None:
return value
try:
return fernet.decrypt(value.encode("utf-8")).decode("utf-8")
except (InvalidToken, ValueError, TypeError):
return value