0
# Key Serialization
1
2
Serialization and deserialization of cryptographic keys in various formats including PEM, DER, and SSH formats. Essential for key storage, transmission, and interoperability.
3
4
## Core Imports
5
6
```python
7
from cryptography.hazmat.primitives import serialization
8
from cryptography.hazmat.primitives.serialization import ssh, pkcs7, pkcs12
9
```
10
11
## Capabilities
12
13
### Key Loading Functions
14
15
```python { .api }
16
def load_pem_private_key(data: bytes, password: bytes = None, backend=None) -> PrivateKey:
17
"""Load private key from PEM format"""
18
19
def load_der_private_key(data: bytes, password: bytes = None, backend=None) -> PrivateKey:
20
"""Load private key from DER format"""
21
22
def load_pem_public_key(data: bytes, backend=None) -> PublicKey:
23
"""Load public key from PEM format"""
24
25
def load_der_public_key(data: bytes, backend=None) -> PublicKey:
26
"""Load public key from DER format"""
27
28
def load_ssh_private_key(data: bytes, password: bytes = None, backend=None) -> PrivateKey:
29
"""Load SSH private key"""
30
31
def load_ssh_public_key(data: bytes, backend=None) -> PublicKey:
32
"""Load SSH public key"""
33
```
34
35
### Encoding Formats
36
37
```python { .api }
38
class Encoding:
39
PEM: Encoding # Base64 with headers
40
DER: Encoding # Binary ASN.1
41
OpenSSH: Encoding # SSH format
42
Raw: Encoding # Raw bytes
43
X962: Encoding # X9.62 format for EC keys
44
45
class PrivateFormat:
46
PKCS8: PrivateFormat # PKCS#8 (preferred)
47
TraditionalOpenSSL: PrivateFormat # Traditional format
48
Raw: PrivateFormat # Raw key bytes
49
OpenSSH: PrivateFormat # SSH private key format
50
51
class PublicFormat:
52
SubjectPublicKeyInfo: PublicFormat # X.509 SubjectPublicKeyInfo (preferred)
53
PKCS1: PublicFormat # PKCS#1 RSA format
54
OpenSSH: PublicFormat # SSH public key format
55
Raw: PublicFormat # Raw key bytes
56
CompressedPoint: PublicFormat # Compressed EC point
57
UncompressedPoint: PublicFormat # Uncompressed EC point
58
```
59
60
### Encryption Algorithms
61
62
```python { .api }
63
class KeySerializationEncryption:
64
"""Abstract base for key encryption"""
65
66
class NoEncryption(KeySerializationEncryption):
67
"""No encryption for private keys"""
68
69
class BestAvailableEncryption(KeySerializationEncryption):
70
def __init__(self, password: bytes):
71
"""
72
Best available encryption for private keys.
73
74
Args:
75
password (bytes): Password for key encryption
76
"""
77
```
78
79
### SSH Key Support
80
81
```python { .api }
82
def ssh_key_fingerprint(public_key, algorithm=hashes.SHA256()) -> bytes:
83
"""Generate SSH key fingerprint"""
84
85
class SSHCertificate:
86
"""SSH certificate representation"""
87
88
@property
89
def public_key(self):
90
"""Certificate public key"""
91
92
@property
93
def serial(self) -> int:
94
"""Certificate serial number"""
95
```
96
97
## Usage Examples
98
99
### Save and Load RSA Keys
100
101
```python
102
from cryptography.hazmat.primitives.asymmetric import rsa
103
from cryptography.hazmat.primitives import serialization
104
105
# Generate RSA key
106
private_key = rsa.generate_private_key(65537, 2048)
107
public_key = private_key.public_key()
108
109
# Save private key (encrypted)
110
encrypted_private = private_key.private_bytes(
111
encoding=serialization.Encoding.PEM,
112
format=serialization.PrivateFormat.PKCS8,
113
encryption_algorithm=serialization.BestAvailableEncryption(b'mypassword')
114
)
115
116
# Save private key (unencrypted - less secure)
117
unencrypted_private = private_key.private_bytes(
118
encoding=serialization.Encoding.PEM,
119
format=serialization.PrivateFormat.PKCS8,
120
encryption_algorithm=serialization.NoEncryption()
121
)
122
123
# Save public key
124
public_pem = public_key.public_bytes(
125
encoding=serialization.Encoding.PEM,
126
format=serialization.PublicFormat.SubjectPublicKeyInfo
127
)
128
129
# Write to files
130
with open('private_key_encrypted.pem', 'wb') as f:
131
f.write(encrypted_private)
132
133
with open('public_key.pem', 'wb') as f:
134
f.write(public_pem)
135
136
# Load keys back
137
with open('private_key_encrypted.pem', 'rb') as f:
138
loaded_private = serialization.load_pem_private_key(
139
f.read(),
140
password=b'mypassword'
141
)
142
143
with open('public_key.pem', 'rb') as f:
144
loaded_public = serialization.load_pem_public_key(f.read())
145
146
print("Keys loaded successfully")
147
```
148
149
### SSH Key Format
150
151
```python
152
from cryptography.hazmat.primitives.asymmetric import rsa
153
from cryptography.hazmat.primitives import serialization, hashes
154
155
# Generate key
156
private_key = rsa.generate_private_key(65537, 2048)
157
public_key = private_key.public_key()
158
159
# SSH public key format
160
ssh_public = public_key.public_bytes(
161
encoding=serialization.Encoding.OpenSSH,
162
format=serialization.PublicFormat.OpenSSH
163
)
164
165
# SSH private key format
166
ssh_private = private_key.private_bytes(
167
encoding=serialization.Encoding.PEM,
168
format=serialization.PrivateFormat.OpenSSH,
169
encryption_algorithm=serialization.NoEncryption()
170
)
171
172
print("SSH public key:")
173
print(ssh_public.decode())
174
175
# Generate SSH key fingerprint
176
fingerprint = serialization.ssh_key_fingerprint(public_key, hashes.SHA256())
177
print(f"SSH fingerprint: {fingerprint.hex()}")
178
179
# Save SSH keys
180
with open('id_rsa', 'wb') as f:
181
f.write(ssh_private)
182
183
with open('id_rsa.pub', 'wb') as f:
184
f.write(ssh_public)
185
```
186
187
### Ed25519 Key Serialization
188
189
```python
190
from cryptography.hazmat.primitives.asymmetric import ed25519
191
from cryptography.hazmat.primitives import serialization
192
193
# Generate Ed25519 key
194
private_key = ed25519.Ed25519PrivateKey.generate()
195
public_key = private_key.public_key()
196
197
# Raw format (32 bytes each)
198
private_raw = private_key.private_bytes(
199
encoding=serialization.Encoding.Raw,
200
format=serialization.PrivateFormat.Raw,
201
encryption_algorithm=serialization.NoEncryption()
202
)
203
204
public_raw = public_key.public_bytes(
205
encoding=serialization.Encoding.Raw,
206
format=serialization.PublicFormat.Raw
207
)
208
209
print(f"Ed25519 private key (raw): {private_raw.hex()} ({len(private_raw)} bytes)")
210
print(f"Ed25519 public key (raw): {public_raw.hex()} ({len(public_raw)} bytes)")
211
212
# PEM format
213
private_pem = private_key.private_bytes(
214
encoding=serialization.Encoding.PEM,
215
format=serialization.PrivateFormat.PKCS8,
216
encryption_algorithm=serialization.NoEncryption()
217
)
218
219
public_pem = public_key.public_bytes(
220
encoding=serialization.Encoding.PEM,
221
format=serialization.PublicFormat.SubjectPublicKeyInfo
222
)
223
224
print("Ed25519 PEM private key:")
225
print(private_pem.decode())
226
```
227
228
### Key Format Conversion
229
230
```python
231
from cryptography.hazmat.primitives import serialization
232
from cryptography.hazmat.primitives.asymmetric import rsa
233
234
# Load existing key (any format)
235
with open('existing_key.pem', 'rb') as f:
236
private_key = serialization.load_pem_private_key(f.read(), password=None)
237
238
# Convert to different formats
239
formats = {
240
'pkcs8_pem': (serialization.Encoding.PEM, serialization.PrivateFormat.PKCS8),
241
'pkcs8_der': (serialization.Encoding.DER, serialization.PrivateFormat.PKCS8),
242
'traditional_pem': (serialization.Encoding.PEM, serialization.PrivateFormat.TraditionalOpenSSL),
243
'ssh': (serialization.Encoding.PEM, serialization.PrivateFormat.OpenSSH),
244
}
245
246
for name, (encoding, format_type) in formats.items():
247
try:
248
key_bytes = private_key.private_bytes(
249
encoding=encoding,
250
format=format_type,
251
encryption_algorithm=serialization.NoEncryption()
252
)
253
254
with open(f'key_{name}', 'wb') as f:
255
f.write(key_bytes)
256
257
print(f"Converted to {name}")
258
except Exception as e:
259
print(f"Failed to convert to {name}: {e}")
260
```
261
262
### Secure Key Storage Class
263
264
```python
265
from cryptography.hazmat.primitives import serialization
266
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
267
from cryptography.hazmat.primitives import hashes
268
import os
269
import getpass
270
271
class SecureKeyStorage:
272
def save_private_key(self, private_key, filepath: str, password: str = None):
273
"""Save private key with optional password protection"""
274
if password is None:
275
password = getpass.getpass("Enter password for private key: ")
276
277
if password:
278
encryption = serialization.BestAvailableEncryption(password.encode())
279
else:
280
encryption = serialization.NoEncryption()
281
282
key_bytes = private_key.private_bytes(
283
encoding=serialization.Encoding.PEM,
284
format=serialization.PrivateFormat.PKCS8,
285
encryption_algorithm=encryption
286
)
287
288
with open(filepath, 'wb') as f:
289
f.write(key_bytes)
290
291
print(f"Private key saved to {filepath}")
292
293
def load_private_key(self, filepath: str, password: str = None):
294
"""Load private key with password prompt if needed"""
295
with open(filepath, 'rb') as f:
296
key_data = f.read()
297
298
# Try without password first
299
try:
300
return serialization.load_pem_private_key(key_data, password=None)
301
except TypeError:
302
# Key is encrypted, need password
303
if password is None:
304
password = getpass.getpass("Enter password for private key: ")
305
306
return serialization.load_pem_private_key(key_data, password=password.encode())
307
308
def save_public_key(self, public_key, filepath: str, format_type: str = 'pem'):
309
"""Save public key in specified format"""
310
if format_type.lower() == 'ssh':
311
key_bytes = public_key.public_bytes(
312
encoding=serialization.Encoding.OpenSSH,
313
format=serialization.PublicFormat.OpenSSH
314
)
315
else: # PEM
316
key_bytes = public_key.public_bytes(
317
encoding=serialization.Encoding.PEM,
318
format=serialization.PublicFormat.SubjectPublicKeyInfo
319
)
320
321
with open(filepath, 'wb') as f:
322
f.write(key_bytes)
323
324
print(f"Public key saved to {filepath}")
325
326
# Usage
327
from cryptography.hazmat.primitives.asymmetric import rsa
328
329
key_storage = SecureKeyStorage()
330
331
# Generate and save key pair
332
private_key = rsa.generate_private_key(65537, 2048)
333
public_key = private_key.public_key()
334
335
key_storage.save_private_key(private_key, 'my_private_key.pem', 'secure_password')
336
key_storage.save_public_key(public_key, 'my_public_key.pem')
337
key_storage.save_public_key(public_key, 'my_public_key.ssh', format_type='ssh')
338
339
# Load key back
340
loaded_key = key_storage.load_private_key('my_private_key.pem', 'secure_password')
341
print("Key loaded successfully")
342
```
343
344
## Security Considerations
345
346
- **Password Protection**: Always encrypt private keys when storing
347
- **Secure Deletion**: Securely wipe unencrypted key data from memory
348
- **Format Selection**: Use PKCS#8 for interoperability
349
- **Key Validation**: Validate loaded keys before use
350
- **Access Control**: Restrict file permissions on key files (600/400)
351
- **Key Rotation**: Regularly rotate and re-encrypt stored keys
352
353
### PKCS#7/CMS Support
354
355
PKCS#7 (Cryptographic Message Syntax) for packaging and signing certificates and data.
356
357
```python { .api }
358
def load_pem_pkcs7_certificates(data: bytes) -> List[Certificate]:
359
"""
360
Load certificates from PKCS#7 PEM data.
361
362
Args:
363
data (bytes): PEM-encoded PKCS#7 data
364
365
Returns:
366
List[Certificate]: List of certificates from PKCS#7 structure
367
"""
368
369
def load_der_pkcs7_certificates(data: bytes) -> List[Certificate]:
370
"""
371
Load certificates from PKCS#7 DER data.
372
373
Args:
374
data (bytes): DER-encoded PKCS#7 data
375
376
Returns:
377
List[Certificate]: List of certificates from PKCS#7 structure
378
"""
379
380
def serialize_certificates(certificates: List[Certificate], encoding: Encoding) -> bytes:
381
"""
382
Serialize certificates to PKCS#7 format.
383
384
Args:
385
certificates: List of certificates to serialize
386
encoding: Encoding format (PEM or DER)
387
388
Returns:
389
bytes: PKCS#7 encoded certificate data
390
"""
391
392
class PKCS7Options:
393
Text: PKCS7Options # Add text/plain MIME type
394
Binary: PKCS7Options # Don't translate to canonical MIME
395
DetachedSignature: PKCS7Options # Don't embed data in PKCS#7
396
```
397
398
### PKCS#12 Support
399
400
PKCS#12 format for packaging private keys with certificates, commonly used for key/certificate bundles.
401
402
```python { .api }
403
def load_key_and_certificates(data: bytes, password: bytes = None) -> Tuple[PrivateKey, Certificate, List[Certificate]]:
404
"""
405
Load private key and certificates from PKCS#12 data.
406
407
Args:
408
data (bytes): PKCS#12 data
409
password (bytes): Password for PKCS#12 (None if unencrypted)
410
411
Returns:
412
Tuple containing:
413
- PrivateKey: The private key (or None)
414
- Certificate: The certificate (or None)
415
- List[Certificate]: Additional certificates
416
"""
417
418
def load_pkcs12(data: bytes, password: bytes = None) -> PKCS12KeyAndCertificates:
419
"""
420
Load PKCS#12 data into structured object.
421
422
Args:
423
data (bytes): PKCS#12 data
424
password (bytes): Password for PKCS#12
425
426
Returns:
427
PKCS12KeyAndCertificates: Structured PKCS#12 data
428
"""
429
430
def serialize_key_and_certificates(name: bytes, key: PrivateKey, cert: Certificate, cas: List[Certificate] = None, encryption_algorithm=None) -> bytes:
431
"""
432
Serialize key and certificates to PKCS#12 format.
433
434
Args:
435
name (bytes): Friendly name for the key/cert
436
key: Private key to include
437
cert: Certificate to include
438
cas: Additional certificates to include
439
encryption_algorithm: Encryption for the PKCS#12 data
440
441
Returns:
442
bytes: PKCS#12 encoded data
443
"""
444
445
class PKCS12KeyAndCertificates:
446
"""Container for PKCS#12 key and certificate data"""
447
448
@property
449
def key(self) -> PrivateKey:
450
"""Private key from PKCS#12"""
451
452
@property
453
def cert(self) -> PKCS12Certificate:
454
"""Main certificate from PKCS#12"""
455
456
@property
457
def additional_certificates(self) -> List[PKCS12Certificate]:
458
"""Additional certificates from PKCS#12"""
459
460
class PKCS12Certificate:
461
"""Certificate from PKCS#12 with additional metadata"""
462
463
@property
464
def certificate(self) -> Certificate:
465
"""The X.509 certificate"""
466
467
@property
468
def friendly_name(self) -> bytes:
469
"""Friendly name for the certificate"""
470
```
471
472
#### PKCS#7 Usage Example
473
474
```python
475
from cryptography.hazmat.primitives.serialization import pkcs7
476
from cryptography import x509
477
from cryptography.hazmat.primitives import serialization
478
479
# Load certificates from PKCS#7
480
with open('certificates.p7b', 'rb') as f:
481
p7b_data = f.read()
482
483
certificates = pkcs7.load_pem_pkcs7_certificates(p7b_data)
484
print(f"Loaded {len(certificates)} certificates from PKCS#7")
485
486
# Serialize certificates back to PKCS#7
487
pkcs7_data = pkcs7.serialize_certificates(
488
certificates,
489
encoding=serialization.Encoding.PEM
490
)
491
492
with open('output.p7b', 'wb') as f:
493
f.write(pkcs7_data)
494
```
495
496
#### PKCS#12 Usage Example
497
498
```python
499
from cryptography.hazmat.primitives.serialization import pkcs12
500
from cryptography.hazmat.primitives.asymmetric import rsa
501
from cryptography import x509
502
from cryptography.hazmat.primitives import hashes, serialization
503
import datetime
504
505
# Create a key and self-signed certificate
506
private_key = rsa.generate_private_key(65537, 2048)
507
public_key = private_key.public_key()
508
509
# Build certificate
510
builder = x509.CertificateBuilder()
511
builder = builder.subject_name(x509.Name([
512
x509.NameAttribute(x509.NameOID.COMMON_NAME, "Test Certificate")
513
]))
514
builder = builder.issuer_name(x509.Name([
515
x509.NameAttribute(x509.NameOID.COMMON_NAME, "Test Certificate")
516
]))
517
builder = builder.public_key(public_key)
518
builder = builder.serial_number(1)
519
builder = builder.not_valid_before(datetime.datetime.utcnow())
520
builder = builder.not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=365))
521
522
certificate = builder.sign(private_key, hashes.SHA256())
523
524
# Create PKCS#12 bundle
525
p12_data = pkcs12.serialize_key_and_certificates(
526
name=b"My Certificate",
527
key=private_key,
528
cert=certificate,
529
cas=None, # No additional certificates
530
encryption_algorithm=serialization.BestAvailableEncryption(b"password123")
531
)
532
533
# Save PKCS#12 file
534
with open('bundle.p12', 'wb') as f:
535
f.write(p12_data)
536
537
# Load PKCS#12 bundle back
538
with open('bundle.p12', 'rb') as f:
539
p12_data = f.read()
540
541
loaded_key, loaded_cert, additional_certs = pkcs12.load_key_and_certificates(
542
p12_data,
543
password=b"password123"
544
)
545
546
print(f"Loaded private key: {loaded_key}")
547
print(f"Loaded certificate: {loaded_cert.subject}")
548
print(f"Additional certificates: {len(additional_certs)}")
549
```