1+ from __future__ import annotations
2+
3+ import os
4+ import tempfile
15from pathlib import Path
26
37from cryptography .hazmat .primitives import serialization
48from cryptography .hazmat .primitives .asymmetric import ec , ed25519 , rsa
59
610
711def generate_rsa_private_key (key_size : int = 3072 ):
8- """
9- Generate an RSA private key.
10-
11- Recommended sizes:
12- - 3072 bits for modern security
13- - 4096 bits for stronger security
14- """
15- return rsa .generate_private_key (
16- public_exponent = 65537 ,
17- key_size = key_size ,
18- )
12+ """Generate an RSA private key."""
13+ if key_size < 2048 :
14+ raise ValueError ("RSA key size must be at least 2048 bits" )
15+ return rsa .generate_private_key (public_exponent = 65537 , key_size = key_size )
1916
2017
2118def generate_ecdsa_private_key ():
22- """
23- Generate an ECDSA private key using curve P-256.
24- """
19+ """Generate an ECDSA private key using curve P-256."""
2520 return ec .generate_private_key (ec .SECP256R1 ())
2621
2722
2823def generate_ed25519_private_key ():
29- """
30- Generate an Ed25519 private key.
31- """
24+ """Generate an Ed25519 private key."""
3225 return ed25519 .Ed25519PrivateKey .generate ()
3326
3427
3528def serialize_private_key (private_key , password : bytes | None = None ) -> bytes :
36- """
37- Serialize a private key to PEM format.
38-
39- If password is provided, the private key is encrypted.
40- """
29+ """Serialize a private key to encrypted or unencrypted PKCS#8 PEM."""
4130 encryption_algorithm = (
4231 serialization .BestAvailableEncryption (password )
4332 if password
4433 else serialization .NoEncryption ()
4534 )
46-
4735 return private_key .private_bytes (
4836 encoding = serialization .Encoding .PEM ,
4937 format = serialization .PrivateFormat .PKCS8 ,
@@ -52,40 +40,48 @@ def serialize_private_key(private_key, password: bytes | None = None) -> bytes:
5240
5341
5442def serialize_public_key (public_key ) -> bytes :
55- """
56- Serialize a public key to PEM format.
57- """
43+ """Serialize a public key to SubjectPublicKeyInfo PEM."""
5844 return public_key .public_bytes (
5945 encoding = serialization .Encoding .PEM ,
6046 format = serialization .PublicFormat .SubjectPublicKeyInfo ,
6147 )
6248
6349
6450def save_pem_file (path : str | Path , data : bytes ) -> None :
65- """
66- Save PEM data to a file.
67- """
51+ """Atomically save PEM data with owner-only permissions."""
6852 file_path = Path (path )
6953 file_path .parent .mkdir (parents = True , exist_ok = True )
70- file_path .write_bytes (data )
54+
55+ descriptor , temporary_name = tempfile .mkstemp (
56+ prefix = f".{ file_path .name } ." ,
57+ dir = file_path .parent ,
58+ )
59+ temporary_path = Path (temporary_name )
60+ try :
61+ os .fchmod (descriptor , 0o600 )
62+ with os .fdopen (descriptor , "wb" ) as stream :
63+ stream .write (data )
64+ stream .flush ()
65+ os .fsync (stream .fileno ())
66+ os .replace (temporary_path , file_path )
67+ try :
68+ file_path .chmod (0o600 )
69+ except OSError :
70+ pass
71+ except Exception :
72+ try :
73+ os .close (descriptor )
74+ except OSError :
75+ pass
76+ temporary_path .unlink (missing_ok = True )
77+ raise
7178
7279
7380def load_private_key (path : str | Path , password : bytes | None = None ):
74- """
75- Load a private key from a PEM file.
76- """
77- data = Path (path ).read_bytes ()
78-
79- return serialization .load_pem_private_key (
80- data ,
81- password = password ,
82- )
81+ """Load a private key from a PEM file."""
82+ return serialization .load_pem_private_key (Path (path ).read_bytes (), password = password )
8383
8484
8585def load_public_key (path : str | Path ):
86- """
87- Load a public key from a PEM file.
88- """
89- data = Path (path ).read_bytes ()
90-
91- return serialization .load_pem_public_key (data )
86+ """Load a public key from a PEM file."""
87+ return serialization .load_pem_public_key (Path (path ).read_bytes ())
0 commit comments