0
# AEAD Ciphers
1
2
Authenticated Encryption with Associated Data (AEAD) providing encryption and authentication in a single operation. AEAD ciphers ensure both confidentiality and authenticity of data while allowing additional authenticated data that remains unencrypted.
3
4
## Core Imports
5
6
```python
7
from cryptography.hazmat.primitives.ciphers.aead import AESGCM, ChaCha20Poly1305, AESCCM, AESSIV, AESOCB3, AESGCMSIV
8
```
9
10
## Capabilities
11
12
### AES-GCM (Galois/Counter Mode)
13
14
AES in Galois/Counter Mode providing authenticated encryption with excellent performance.
15
16
```python { .api }
17
class AESGCM:
18
def __init__(self, key: bytes):
19
"""
20
Initialize AES-GCM with key.
21
22
Args:
23
key (bytes): 128, 192, or 256-bit key (16, 24, or 32 bytes)
24
"""
25
26
@classmethod
27
def generate_key(cls, bit_length: int) -> bytes:
28
"""
29
Generate random key for AES-GCM.
30
31
Args:
32
bit_length (int): Key length in bits (128, 192, or 256)
33
34
Returns:
35
bytes: Random key of specified length
36
"""
37
38
def encrypt(self, nonce: bytes, data: bytes, associated_data: bytes = None) -> bytes:
39
"""
40
Encrypt data with authentication.
41
42
Args:
43
nonce (bytes): 96-bit (12 bytes) nonce. Must be unique per key.
44
data (bytes): Data to encrypt
45
associated_data (bytes, optional): Additional data to authenticate but not encrypt
46
47
Returns:
48
bytes: Encrypted data with authentication tag appended
49
50
Note:
51
Never reuse nonce with same key - this breaks security completely
52
"""
53
54
def decrypt(self, nonce: bytes, data: bytes, associated_data: bytes = None) -> bytes:
55
"""
56
Decrypt and verify authenticated data.
57
58
Args:
59
nonce (bytes): Same nonce used for encryption
60
data (bytes): Encrypted data with authentication tag
61
associated_data (bytes, optional): Same associated data from encryption
62
63
Returns:
64
bytes: Decrypted plaintext data
65
66
Raises:
67
InvalidTag: If authentication verification fails
68
"""
69
```
70
71
### ChaCha20-Poly1305
72
73
Modern stream cipher with Poly1305 MAC for authenticated encryption.
74
75
```python { .api }
76
class ChaCha20Poly1305:
77
def __init__(self, key: bytes):
78
"""
79
Initialize ChaCha20-Poly1305 with key.
80
81
Args:
82
key (bytes): 256-bit key (32 bytes)
83
"""
84
85
@classmethod
86
def generate_key(cls) -> bytes:
87
"""
88
Generate random 256-bit key.
89
90
Returns:
91
bytes: 32-byte random key
92
"""
93
94
def encrypt(self, nonce: bytes, data: bytes, associated_data: bytes = None) -> bytes:
95
"""
96
Encrypt data with ChaCha20-Poly1305.
97
98
Args:
99
nonce (bytes): 96-bit (12 bytes) nonce. Must be unique per key.
100
data (bytes): Data to encrypt
101
associated_data (bytes, optional): Additional authenticated data
102
103
Returns:
104
bytes: Encrypted data with Poly1305 tag appended
105
"""
106
107
def decrypt(self, nonce: bytes, data: bytes, associated_data: bytes = None) -> bytes:
108
"""
109
Decrypt and verify ChaCha20-Poly1305 data.
110
111
Args:
112
nonce (bytes): Same nonce used for encryption
113
data (bytes): Encrypted data with authentication tag
114
associated_data (bytes, optional): Same associated data from encryption
115
116
Returns:
117
bytes: Decrypted plaintext
118
119
Raises:
120
InvalidTag: If Poly1305 authentication fails
121
"""
122
```
123
124
### AES-CCM (Counter with CBC-MAC)
125
126
AES Counter mode with CBC-MAC for authenticated encryption.
127
128
```python { .api }
129
class AESCCM:
130
def __init__(self, key: bytes, tag_length: int = 16):
131
"""
132
Initialize AES-CCM with key and tag length.
133
134
Args:
135
key (bytes): 128, 192, or 256-bit AES key
136
tag_length (int): Authentication tag length (4, 6, 8, 10, 12, 14, 16 bytes)
137
"""
138
139
@classmethod
140
def generate_key(cls, bit_length: int) -> bytes:
141
"""Generate random AES key"""
142
143
def encrypt(self, nonce: bytes, data: bytes, associated_data: bytes = None) -> bytes:
144
"""
145
Encrypt with AES-CCM.
146
147
Args:
148
nonce (bytes): 7-15 byte nonce (longer nonce = shorter counter)
149
data (bytes): Data to encrypt
150
associated_data (bytes, optional): Additional authenticated data
151
152
Returns:
153
bytes: Encrypted data with CBC-MAC tag
154
"""
155
156
def decrypt(self, nonce: bytes, data: bytes, associated_data: bytes = None) -> bytes:
157
"""
158
Decrypt and verify AES-CCM data.
159
160
Raises:
161
InvalidTag: If CBC-MAC verification fails
162
"""
163
```
164
165
### AES-SIV (Synthetic Initialization Vector)
166
167
Misuse-resistant authenticated encryption that's secure even with nonce reuse.
168
169
```python { .api }
170
class AESSIV:
171
def __init__(self, key: bytes):
172
"""
173
Initialize AES-SIV.
174
175
Args:
176
key (bytes): 256, 384, or 512-bit key (32, 48, or 64 bytes)
177
Key length determines AES variant (AES-128, AES-192, AES-256)
178
"""
179
180
@classmethod
181
def generate_key(cls, bit_length: int) -> bytes:
182
"""Generate random key for AES-SIV"""
183
184
def encrypt(self, data: bytes, associated_data: List[bytes] = None) -> bytes:
185
"""
186
Encrypt with AES-SIV (no nonce required).
187
188
Args:
189
data (bytes): Data to encrypt
190
associated_data (List[bytes], optional): List of associated data items
191
192
Returns:
193
bytes: SIV (16 bytes) + encrypted data
194
195
Note:
196
AES-SIV is nonce-misuse resistant - safe even with repeated inputs
197
"""
198
199
def decrypt(self, data: bytes, associated_data: List[bytes] = None) -> bytes:
200
"""
201
Decrypt AES-SIV data.
202
203
Args:
204
data (bytes): SIV + encrypted data from encrypt()
205
associated_data (List[bytes], optional): Same associated data from encryption
206
207
Returns:
208
bytes: Decrypted plaintext
209
210
Raises:
211
InvalidTag: If SIV verification fails
212
"""
213
```
214
215
### AES-OCB3 (Offset Codebook Mode)
216
217
High-performance authenticated encryption mode.
218
219
```python { .api }
220
class AESOCB3:
221
def __init__(self, key: bytes):
222
"""
223
Initialize AES-OCB3.
224
225
Args:
226
key (bytes): 128, 192, or 256-bit AES key
227
"""
228
229
@classmethod
230
def generate_key(cls, bit_length: int) -> bytes:
231
"""Generate random AES key"""
232
233
def encrypt(self, nonce: bytes, data: bytes, associated_data: bytes = None) -> bytes:
234
"""
235
Encrypt with AES-OCB3.
236
237
Args:
238
nonce (bytes): 1-15 byte nonce, must be unique per key
239
data (bytes): Data to encrypt
240
associated_data (bytes, optional): Additional authenticated data
241
242
Returns:
243
bytes: Encrypted data with authentication tag
244
"""
245
246
def decrypt(self, nonce: bytes, data: bytes, associated_data: bytes = None) -> bytes:
247
"""
248
Decrypt AES-OCB3 data.
249
250
Raises:
251
InvalidTag: If authentication verification fails
252
"""
253
```
254
255
### AES-GCM-SIV (Galois/Counter Mode with SIV)
256
257
Misuse-resistant variant of AES-GCM that's secure with nonce reuse.
258
259
```python { .api }
260
class AESGCMSIV:
261
def __init__(self, key: bytes):
262
"""
263
Initialize AES-GCM-SIV.
264
265
Args:
266
key (bytes): 128 or 256-bit key (16 or 32 bytes)
267
"""
268
269
@classmethod
270
def generate_key(cls, bit_length: int) -> bytes:
271
"""Generate random key for AES-GCM-SIV"""
272
273
def encrypt(self, nonce: bytes, data: bytes, associated_data: bytes = None) -> bytes:
274
"""
275
Encrypt with AES-GCM-SIV.
276
277
Args:
278
nonce (bytes): 96-bit (12 bytes) nonce
279
data (bytes): Data to encrypt
280
associated_data (bytes, optional): Additional authenticated data
281
282
Returns:
283
bytes: Encrypted data with authentication tag
284
285
Note:
286
Secure even with nonce reuse, but unique nonces still recommended
287
"""
288
289
def decrypt(self, nonce: bytes, data: bytes, associated_data: bytes = None) -> bytes:
290
"""
291
Decrypt AES-GCM-SIV data.
292
293
Raises:
294
InvalidTag: If authentication verification fails
295
"""
296
```
297
298
## Usage Examples
299
300
### Basic AES-GCM Encryption
301
302
```python
303
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
304
from cryptography.exceptions import InvalidTag
305
import os
306
307
# Generate key and nonce
308
key = AESGCM.generate_key(256) # 256-bit key
309
aesgcm = AESGCM(key)
310
311
# Encrypt data
312
nonce = os.urandom(12) # 96-bit nonce
313
plaintext = b"Secret message for authenticated encryption"
314
associated_data = b"public metadata"
315
316
ciphertext = aesgcm.encrypt(nonce, plaintext, associated_data)
317
print(f"Encrypted: {ciphertext.hex()}")
318
319
# Decrypt data
320
try:
321
decrypted = aesgcm.decrypt(nonce, ciphertext, associated_data)
322
print(f"Decrypted: {decrypted}")
323
except InvalidTag:
324
print("Authentication failed - data may be tampered")
325
```
326
327
### ChaCha20-Poly1305 for High-Performance Applications
328
329
```python
330
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
331
import os
332
333
# Initialize ChaCha20-Poly1305
334
key = ChaCha20Poly1305.generate_key()
335
chacha = ChaCha20Poly1305(key)
336
337
# Encrypt with associated data
338
nonce = os.urandom(12)
339
message = b"High-speed encrypted message"
340
metadata = b"timestamp:2024-01-01,user:alice"
341
342
encrypted = chacha.encrypt(nonce, message, metadata)
343
344
# Decrypt and verify
345
try:
346
decrypted = chacha.decrypt(nonce, encrypted, metadata)
347
print(f"Message: {decrypted}")
348
print("Authentication successful")
349
except InvalidTag:
350
print("Message authentication failed")
351
```
352
353
### Secure File Encryption with AES-GCM
354
355
```python
356
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
357
import os
358
import json
359
360
class SecureFileManager:
361
def __init__(self, key=None):
362
self.key = key or AESGCM.generate_key(256)
363
self.aesgcm = AESGCM(self.key)
364
365
def encrypt_file(self, input_path, output_path, metadata=None):
366
"""Encrypt file with optional metadata"""
367
with open(input_path, 'rb') as f:
368
plaintext = f.read()
369
370
nonce = os.urandom(12)
371
associated_data = json.dumps(metadata or {}).encode() if metadata else None
372
373
ciphertext = self.aesgcm.encrypt(nonce, plaintext, associated_data)
374
375
# Store nonce + ciphertext + metadata length + metadata
376
with open(output_path, 'wb') as f:
377
f.write(nonce) # First 12 bytes
378
f.write(len(associated_data or b"").to_bytes(4, 'big')) # Metadata length
379
if associated_data:
380
f.write(associated_data) # Metadata
381
f.write(ciphertext) # Encrypted content
382
383
def decrypt_file(self, input_path, output_path):
384
"""Decrypt file and return metadata"""
385
with open(input_path, 'rb') as f:
386
nonce = f.read(12)
387
metadata_len = int.from_bytes(f.read(4), 'big')
388
associated_data = f.read(metadata_len) if metadata_len > 0 else None
389
ciphertext = f.read()
390
391
try:
392
plaintext = self.aesgcm.decrypt(nonce, ciphertext, associated_data)
393
394
with open(output_path, 'wb') as f:
395
f.write(plaintext)
396
397
metadata = json.loads(associated_data.decode()) if associated_data else {}
398
return metadata
399
400
except InvalidTag:
401
raise ValueError("File decryption failed - file may be corrupted or tampered")
402
403
# Usage
404
file_manager = SecureFileManager()
405
406
# Encrypt file with metadata
407
file_manager.encrypt_file(
408
'document.pdf',
409
'document.pdf.enc',
410
metadata={'author': 'Alice', 'classification': 'confidential'}
411
)
412
413
# Decrypt file
414
metadata = file_manager.decrypt_file('document.pdf.enc', 'document_decrypted.pdf')
415
print(f"File metadata: {metadata}")
416
```
417
418
### Misuse-Resistant Encryption with AES-SIV
419
420
```python
421
from cryptography.hazmat.primitives.ciphers.aead import AESSIV
422
423
# AES-SIV is safe even with nonce reuse
424
key = AESSIV.generate_key(256) # 256-bit key for AES-128-SIV
425
aessiv = AESSIV(key)
426
427
# Encrypt same data multiple times (normally dangerous)
428
data = b"Repeated message"
429
associated_data = [b"context1", b"context2"]
430
431
# These encryptions are safe even though input is identical
432
ciphertext1 = aessiv.encrypt(data, associated_data)
433
ciphertext2 = aessiv.encrypt(data, associated_data)
434
435
print(f"Encryption 1: {ciphertext1.hex()}")
436
print(f"Encryption 2: {ciphertext2.hex()}")
437
print(f"Same result: {ciphertext1 == ciphertext2}") # True - deterministic
438
439
# Decrypt both
440
decrypted1 = aessiv.decrypt(ciphertext1, associated_data)
441
decrypted2 = aessiv.decrypt(ciphertext2, associated_data)
442
443
print(f"Decrypted: {decrypted1}")
444
```
445
446
### Multi-Algorithm AEAD Wrapper
447
448
```python
449
from cryptography.hazmat.primitives.ciphers.aead import AESGCM, ChaCha20Poly1305, AESSIV
450
from cryptography.exceptions import InvalidTag
451
import os
452
import struct
453
454
class UniversalAEAD:
455
ALGORITHMS = {
456
'AES-GCM': AESGCM,
457
'ChaCha20-Poly1305': ChaCha20Poly1305,
458
'AES-SIV': AESSIV
459
}
460
461
def __init__(self, algorithm='AES-GCM', key_size=256):
462
self.algorithm = algorithm
463
self.key_size = key_size
464
465
if algorithm == 'AES-GCM':
466
self.key = AESGCM.generate_key(key_size)
467
self.cipher = AESGCM(self.key)
468
elif algorithm == 'ChaCha20-Poly1305':
469
self.key = ChaCha20Poly1305.generate_key()
470
self.cipher = ChaCha20Poly1305(self.key)
471
elif algorithm == 'AES-SIV':
472
self.key = AESSIV.generate_key(key_size)
473
self.cipher = AESSIV(self.key)
474
475
def encrypt(self, data, associated_data=None):
476
"""Encrypt with algorithm identification"""
477
if self.algorithm == 'AES-SIV':
478
# AES-SIV doesn't use nonce
479
ciphertext = self.cipher.encrypt(data, [associated_data] if associated_data else None)
480
nonce = b''
481
else:
482
# Nonce-based algorithms
483
nonce = os.urandom(12)
484
ciphertext = self.cipher.encrypt(nonce, data, associated_data)
485
486
# Format: algorithm_id (1 byte) + nonce_len (1 byte) + nonce + ciphertext
487
algorithm_id = list(self.ALGORITHMS.keys()).index(self.algorithm)
488
header = struct.pack('BB', algorithm_id, len(nonce))
489
490
return header + nonce + ciphertext
491
492
def decrypt(self, encrypted_data, associated_data=None):
493
"""Decrypt with automatic algorithm detection"""
494
algorithm_id, nonce_len = struct.unpack('BB', encrypted_data[:2])
495
algorithm = list(self.ALGORITHMS.keys())[algorithm_id]
496
497
nonce = encrypted_data[2:2+nonce_len]
498
ciphertext = encrypted_data[2+nonce_len:]
499
500
if algorithm == 'AES-SIV':
501
return self.cipher.decrypt(ciphertext, [associated_data] if associated_data else None)
502
else:
503
return self.cipher.decrypt(nonce, ciphertext, associated_data)
504
505
# Usage
506
# Try different algorithms
507
for alg in ['AES-GCM', 'ChaCha20-Poly1305', 'AES-SIV']:
508
aead = UniversalAEAD(alg)
509
510
data = b"Test message for " + alg.encode()
511
metadata = b"algorithm:" + alg.encode()
512
513
encrypted = aead.encrypt(data, metadata)
514
decrypted = aead.decrypt(encrypted, metadata)
515
516
print(f"{alg}: {decrypted}")
517
```
518
519
## Security Considerations
520
521
- **Nonce Management**: Never reuse nonces with the same key (except AES-SIV/GCM-SIV)
522
- **Key Rotation**: Regularly rotate encryption keys
523
- **Associated Data**: Use associated data for context binding
524
- **Algorithm Selection**:
525
- AES-GCM: Fastest, requires careful nonce management
526
- ChaCha20-Poly1305: Fast, good for software implementations
527
- AES-SIV: Misuse-resistant but slower
528
- **Authentication**: Always verify authentication tags before processing decrypted data
529
- **Side-Channel Protection**: AEAD algorithms resist timing attacks
530
- **Nonce Sizes**: Respect algorithm-specific nonce requirements