0
# Symmetric Encryption (Fernet)
1
2
High-level symmetric encryption using authenticated encryption. Fernet provides secure, simple encryption/decryption with built-in authentication, timestamp verification, and key rotation support. It uses AES-128 in CBC mode with HMAC-SHA256 for authentication.
3
4
## Core Imports
5
6
```python
7
from cryptography.fernet import Fernet, MultiFernet, InvalidToken
8
```
9
10
## Capabilities
11
12
### Basic Fernet Encryption
13
14
The `Fernet` class provides symmetric authenticated encryption with a simple API that handles all cryptographic details automatically.
15
16
```python { .api }
17
class Fernet:
18
def __init__(self, key: bytes | str, backend: typing.Any = None):
19
"""
20
Initialize Fernet with a 32-byte base64url-encoded key.
21
22
Args:
23
key (bytes | str): 32-byte base64url-encoded key from generate_key()
24
backend (typing.Any, optional): Cryptographic backend (usually None)
25
"""
26
27
@classmethod
28
def generate_key(cls) -> bytes:
29
"""
30
Generate a fresh fernet key for encryption.
31
32
Returns:
33
bytes: 32-byte base64url-encoded key suitable for Fernet()
34
"""
35
36
def encrypt(self, data: bytes) -> bytes:
37
"""
38
Encrypt data with current timestamp.
39
40
Args:
41
data (bytes): Plaintext data to encrypt
42
43
Returns:
44
bytes: Encrypted token with embedded timestamp
45
"""
46
47
def encrypt_at_time(self, data: bytes, current_time: int) -> bytes:
48
"""
49
Encrypt data with a specific timestamp.
50
51
Args:
52
data (bytes): Plaintext data to encrypt
53
current_time (int): Unix timestamp to embed in token
54
55
Returns:
56
bytes: Encrypted token with specified timestamp
57
"""
58
59
def decrypt(self, token: bytes | str, ttl: int | None = None) -> bytes:
60
"""
61
Decrypt a token, optionally verifying age.
62
63
Args:
64
token (bytes): Encrypted token from encrypt()
65
ttl (int, optional): Maximum age in seconds
66
67
Returns:
68
bytes: Decrypted plaintext data
69
70
Raises:
71
InvalidToken: If token is invalid, expired, or too old
72
"""
73
74
def decrypt_at_time(self, token: bytes | str, ttl: int, current_time: int) -> bytes:
75
"""
76
Decrypt a token at a specific time with TTL verification.
77
78
Args:
79
token (bytes): Encrypted token from encrypt()
80
ttl (int): Maximum age in seconds
81
current_time (int): Unix timestamp to use as current time
82
83
Returns:
84
bytes: Decrypted plaintext data
85
86
Raises:
87
InvalidToken: If token is invalid, expired, or too old
88
"""
89
90
def extract_timestamp(self, token: bytes | str) -> int:
91
"""
92
Extract timestamp from a token without decrypting.
93
94
Args:
95
token (bytes): Encrypted token
96
97
Returns:
98
int: Unix timestamp when token was created
99
100
Raises:
101
InvalidToken: If token format is invalid
102
"""
103
```
104
105
### Key Rotation with MultiFernet
106
107
The `MultiFernet` class enables key rotation by maintaining multiple Fernet instances, encrypting with the first key and attempting decryption with all keys.
108
109
```python { .api }
110
class MultiFernet:
111
def __init__(self, fernets: List[Fernet]):
112
"""
113
Initialize with multiple Fernet instances for key rotation.
114
115
Args:
116
fernets (List[Fernet]): List of Fernet instances, first used for encryption
117
"""
118
119
def encrypt(self, msg: bytes) -> bytes:
120
"""
121
Encrypt message using the first Fernet instance.
122
123
Args:
124
msg (bytes): Plaintext data to encrypt
125
126
Returns:
127
bytes: Encrypted token
128
"""
129
130
def encrypt_at_time(self, msg: bytes, current_time: int) -> bytes:
131
"""
132
Encrypt message at specific time using first Fernet instance.
133
134
Args:
135
msg (bytes): Plaintext data to encrypt
136
current_time (int): Unix timestamp to embed
137
138
Returns:
139
bytes: Encrypted token with specified timestamp
140
"""
141
142
def decrypt(self, msg: bytes, ttl: int = None) -> bytes:
143
"""
144
Decrypt message trying each Fernet instance until successful.
145
146
Args:
147
msg (bytes): Encrypted token
148
ttl (int, optional): Maximum age in seconds
149
150
Returns:
151
bytes: Decrypted plaintext data
152
153
Raises:
154
InvalidToken: If no Fernet instance can decrypt the token
155
"""
156
157
def decrypt_at_time(self, msg: bytes, ttl: int, current_time: int) -> bytes:
158
"""
159
Decrypt at specific time trying each Fernet instance.
160
161
Args:
162
msg (bytes): Encrypted token
163
ttl (int): Maximum age in seconds
164
current_time (int): Unix timestamp as current time
165
166
Returns:
167
bytes: Decrypted plaintext data
168
169
Raises:
170
InvalidToken: If no Fernet instance can decrypt the token
171
"""
172
173
def rotate(self, msg: bytes) -> bytes:
174
"""
175
Re-encrypt message with the first (newest) key.
176
177
Args:
178
msg (bytes): Token encrypted with any of the Fernet instances
179
180
Returns:
181
bytes: Token re-encrypted with first Fernet instance
182
183
Raises:
184
InvalidToken: If message cannot be decrypted with any key
185
"""
186
187
def extract_timestamp(self, msg: bytes) -> int:
188
"""
189
Extract timestamp from token using any available key.
190
191
Args:
192
msg (bytes): Encrypted token
193
194
Returns:
195
int: Unix timestamp when token was created
196
197
Raises:
198
InvalidToken: If no key can validate the token format
199
"""
200
```
201
202
### Exception Handling
203
204
```python { .api }
205
class InvalidToken(Exception):
206
"""
207
Raised when a token is invalid, malformed, expired, or fails authentication.
208
This includes cases where:
209
- Token format is incorrect
210
- Authentication check fails
211
- Token is older than specified TTL
212
- Token timestamp is in the future (with clock skew tolerance)
213
"""
214
```
215
216
## Usage Examples
217
218
### Basic Encryption/Decryption
219
220
```python
221
from cryptography.fernet import Fernet
222
223
# Generate a key
224
key = Fernet.generate_key()
225
fernet = Fernet(key)
226
227
# Encrypt sensitive data
228
sensitive_data = b"user_id:12345,session:abc123"
229
token = fernet.encrypt(sensitive_data)
230
231
# Later, decrypt the data
232
try:
233
decrypted_data = fernet.decrypt(token)
234
print(decrypted_data) # b"user_id:12345,session:abc123"
235
except InvalidToken:
236
print("Invalid or expired token")
237
```
238
239
### Time-based Token Validation
240
241
```python
242
from cryptography.fernet import Fernet, InvalidToken
243
import time
244
245
key = Fernet.generate_key()
246
fernet = Fernet(key)
247
248
# Create a token
249
data = b"temporary data"
250
token = fernet.encrypt(data)
251
252
# Decrypt with TTL - only valid for 60 seconds
253
try:
254
decrypted = fernet.decrypt(token, ttl=60)
255
print("Token is fresh:", decrypted)
256
except InvalidToken:
257
print("Token expired or invalid")
258
259
# Check token age without decrypting
260
timestamp = fernet.extract_timestamp(token)
261
age = time.time() - timestamp
262
print(f"Token age: {age} seconds")
263
```
264
265
### Key Rotation
266
267
```python
268
from cryptography.fernet import Fernet, MultiFernet
269
270
# Current encryption key
271
new_key = Fernet.generate_key()
272
new_fernet = Fernet(new_key)
273
274
# Previous keys for decryption
275
old_key1 = Fernet.generate_key() # Previous key
276
old_key2 = Fernet.generate_key() # Even older key
277
old_fernet1 = Fernet(old_key1)
278
old_fernet2 = Fernet(old_key2)
279
280
# MultiFernet with new key first (for encryption)
281
multi_fernet = MultiFernet([
282
new_fernet, # Used for encryption
283
old_fernet1, # Can decrypt old tokens
284
old_fernet2 # Can decrypt even older tokens
285
])
286
287
# Encrypt with new key
288
token = multi_fernet.encrypt(b"data")
289
290
# Can decrypt tokens encrypted with any key
291
decrypted = multi_fernet.decrypt(token)
292
293
# Re-encrypt old token with new key
294
old_token = old_fernet1.encrypt(b"old data")
295
rotated_token = multi_fernet.rotate(old_token) # Now encrypted with new_fernet
296
```
297
298
### Secure Session Management
299
300
```python
301
from cryptography.fernet import Fernet, InvalidToken
302
import json
303
import time
304
305
class SecureSession:
306
def __init__(self, secret_key):
307
self.fernet = Fernet(secret_key)
308
309
def create_session_token(self, user_id, session_data):
310
"""Create encrypted session token"""
311
payload = {
312
'user_id': user_id,
313
'data': session_data,
314
'created': time.time()
315
}
316
json_payload = json.dumps(payload).encode()
317
return self.fernet.encrypt(json_payload)
318
319
def validate_session(self, token, max_age=3600):
320
"""Validate and extract session data"""
321
try:
322
# Decrypt with TTL check
323
decrypted = self.fernet.decrypt(token, ttl=max_age)
324
payload = json.loads(decrypted.decode())
325
return payload
326
except InvalidToken:
327
return None
328
329
# Usage
330
session_key = Fernet.generate_key()
331
session_manager = SecureSession(session_key)
332
333
# Create session
334
token = session_manager.create_session_token(
335
user_id=12345,
336
session_data={'role': 'admin', 'permissions': ['read', 'write']}
337
)
338
339
# Validate session (within 1 hour)
340
session_data = session_manager.validate_session(token, max_age=3600)
341
if session_data:
342
print(f"Valid session for user {session_data['user_id']}")
343
else:
344
print("Invalid or expired session")
345
```
346
347
## Security Considerations
348
349
- **Key Management**: Store Fernet keys securely, never hardcode them
350
- **Token Storage**: Treat encrypted tokens as sensitive data
351
- **TTL Usage**: Always use TTL for time-sensitive data
352
- **Key Rotation**: Regularly rotate keys using MultiFernet
353
- **Clock Skew**: Fernet allows 60 seconds of clock skew tolerance
354
- **Token Size**: Encrypted tokens are larger than original data (base64 overhead + metadata)