-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathencryption.py
More file actions
116 lines (84 loc) · 3.41 KB
/
encryption.py
File metadata and controls
116 lines (84 loc) · 3.41 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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
##: name = encryption.py
##: description = Flask utility for encrypting and decrypting sensitive data using Fernet # noqa: E501
##: category = security
##: usage = Import and use in Flask applications
##: behavior = Provides encryption and decryption of string values using keys from Flask config # noqa: E501
##: inputs = String values to encrypt/decrypt, Flask app config with LEDGERBASE_SECRET_KEYS # noqa: E501
##: outputs = Encrypted/decrypted string values
##: dependencies = Flask, cryptography.fernet
##: author = LedgerBase Team
##: last_modified = 2023-11-15
##: changelog = Initial version
from typing import TypedDict, cast
from cryptography.fernet import Fernet, InvalidToken
from flask import current_app
"""Flask utility for encrypting and decrypting sensitive data.
This module provides a class for encrypting and decrypting string values
using Fernet symmetric encryption with keys stored in the Flask application
configuration. It supports key rotation by allowing multiple keys and
attempting decryption with each key in sequence.
"""
class AppConfig(TypedDict):
"""TypedDict for Flask app configuration."""
LEDGERBASE_SECRET_KEYS: list[str]
class DecryptionError(ValueError):
"""Custom exception for decryption errors."""
DEFAULT_MESSAGE = "Decryption failed: Invalid token or unknown encryption key."
class Encryptor:
"""Encrypts and decrypts string values using Fernet keys from app config."""
CONFIG_ERROR_MSG = (
"Configuration error: 'LEDGERBASE_SECRET_KEYS' must be a non-empty "
"list in Flask config."
)
def __init__(self) -> None:
"""Initialize the Encryptor with encryption keys from the app config.
Raises
------
ValueError
If no LEDGERBASE_SECRET_KEYS are found in the config or
if the keys list is empty.
"""
config = cast("AppConfig", current_app.config)
keys: list[str] = config.get("LEDGERBASE_SECRET_KEYS", [])
if not keys:
raise ValueError(self.CONFIG_ERROR_MSG)
self.primary_cipher: Fernet = Fernet(keys[0].encode("utf-8"))
self.secondary_ciphers: list[Fernet] = [
Fernet(k.encode("utf-8")) for k in keys[1:]
]
def encrypt(self, value: str) -> str:
"""Encrypt a string value using the primary key.
Parameters
----------
value : str
The string value to encrypt.
Returns
-------
str
The encrypted string.
"""
encrypted_bytes: bytes = self.primary_cipher.encrypt(value.encode("utf-8"))
return encrypted_bytes.decode("utf-8")
def decrypt(self, token: str) -> str:
"""Decrypt a token using the primary key, then fall back to secondary keys.
Parameters
----------
token : str
The encrypted string token.
Returns
-------
str
The original decrypted string.
Raises
------
DecryptionError
If decryption fails with all known keys.
"""
for cipher in [self.primary_cipher, *self.secondary_ciphers]:
try:
decrypted_bytes = cipher.decrypt(token.encode("utf-8"))
return decrypted_bytes.decode("utf-8")
except InvalidToken:
continue
error_message = DecryptionError.DEFAULT_MESSAGE
raise DecryptionError(error_message)