Cryptographic recipes and primitives for Python developers
—
Public-key cryptography including RSA, DSA, ECDSA, EdDSA, and key exchange algorithms. Supports key generation, digital signatures, encryption, and key agreement protocols.
from cryptography.hazmat.primitives.asymmetric import rsa, dsa, ec, ed25519, ed448, x25519, x448, dh, padding
from cryptography.hazmat.primitives import hashes, serializationRSA public-key cryptosystem supporting both encryption and digital signatures.
def generate_private_key(public_exponent: int, key_size: int, backend=None) -> RSAPrivateKey:
"""
Generate RSA private key.
Args:
public_exponent (int): Public exponent (typically 65537)
key_size (int): Key size in bits (2048, 3072, or 4096 recommended)
backend: Cryptographic backend (usually None)
Returns:
RSAPrivateKey: Generated private key
"""
class RSAPrivateKey:
def public_key(self) -> RSAPublicKey:
"""Get corresponding public key"""
def sign(self, data: bytes, padding_algo, algorithm) -> bytes:
"""
Sign data with private key.
Args:
data (bytes): Data to sign
padding_algo: Padding algorithm (PSS() or PKCS1v15())
algorithm: Hash algorithm (e.g., hashes.SHA256())
Returns:
bytes: Digital signature
"""
def decrypt(self, ciphertext: bytes, padding_algo) -> bytes:
"""
Decrypt data with private key.
Args:
ciphertext (bytes): Encrypted data
padding_algo: Padding algorithm (OAEP() or PKCS1v15())
Returns:
bytes: Decrypted plaintext
"""
def private_bytes(self, encoding, format, encryption_algorithm) -> bytes:
"""Serialize private key to bytes"""
@property
def key_size(self) -> int:
"""Key size in bits"""
class RSAPublicKey:
def verify(self, signature: bytes, data: bytes, padding_algo, algorithm) -> None:
"""
Verify signature.
Args:
signature (bytes): Signature to verify
data (bytes): Original data that was signed
padding_algo: Same padding used for signing
algorithm: Same hash algorithm used for signing
Raises:
InvalidSignature: If signature verification fails
"""
def encrypt(self, plaintext: bytes, padding_algo) -> bytes:
"""
Encrypt data with public key.
Args:
plaintext (bytes): Data to encrypt
padding_algo: Padding algorithm (OAEP() recommended)
Returns:
bytes: Encrypted ciphertext
"""
def public_bytes(self, encoding, format) -> bytes:
"""Serialize public key to bytes"""
@property
def key_size(self) -> int:
"""Key size in bits"""ECDSA for digital signatures and ECDH for key exchange.
def generate_private_key(curve, backend=None) -> EllipticCurvePrivateKey:
"""
Generate EC private key.
Args:
curve: Elliptic curve (SECP256R1(), SECP384R1(), SECP521R1(), etc.)
backend: Cryptographic backend
Returns:
EllipticCurvePrivateKey: Generated private key
"""
class EllipticCurvePrivateKey:
def public_key(self) -> EllipticCurvePublicKey:
"""Get corresponding public key"""
def sign(self, data: bytes, algorithm) -> bytes:
"""
Sign data with ECDSA.
Args:
data (bytes): Data to sign
algorithm: Hash algorithm (e.g., hashes.SHA256())
Returns:
bytes: DER-encoded ECDSA signature
"""
def exchange(self, peer_public_key: EllipticCurvePublicKey) -> bytes:
"""
Perform ECDH key exchange.
Args:
peer_public_key: Other party's public key
Returns:
bytes: Shared secret
"""
def private_bytes(self, encoding, format, encryption_algorithm) -> bytes:
"""Serialize private key"""
class EllipticCurvePublicKey:
def verify(self, signature: bytes, data: bytes, algorithm) -> None:
"""
Verify ECDSA signature.
Raises:
InvalidSignature: If verification fails
"""
def public_bytes(self, encoding, format) -> bytes:
"""Serialize public key"""
# Common elliptic curves
class SECP256R1:
"""NIST P-256 curve (256-bit)"""
name = "secp256r1"
class SECP384R1:
"""NIST P-384 curve (384-bit)"""
name = "secp384r1"
class SECP521R1:
"""NIST P-521 curve (521-bit)"""
name = "secp521r1"
class SECP256K1:
"""Bitcoin curve (256-bit)"""
name = "secp256k1"Modern signature algorithms using Edwards curves.
class Ed25519PrivateKey:
@classmethod
def generate(cls) -> 'Ed25519PrivateKey':
"""Generate Ed25519 private key"""
@classmethod
def from_private_bytes(cls, data: bytes) -> 'Ed25519PrivateKey':
"""Load from 32-byte private key"""
def public_key(self) -> Ed25519PublicKey:
"""Get corresponding public key"""
def sign(self, data: bytes) -> bytes:
"""
Sign data with Ed25519.
Args:
data (bytes): Data to sign (no hashing needed)
Returns:
bytes: 64-byte signature
"""
def private_bytes(self, encoding, format, encryption_algorithm) -> bytes:
"""Serialize private key"""
class Ed25519PublicKey:
@classmethod
def from_public_bytes(cls, data: bytes) -> 'Ed25519PublicKey':
"""Load from 32-byte public key"""
def verify(self, signature: bytes, data: bytes) -> None:
"""
Verify Ed25519 signature.
Args:
signature (bytes): 64-byte signature
data (bytes): Original data
Raises:
InvalidSignature: If verification fails
"""
def public_bytes(self, encoding, format) -> bytes:
"""Serialize public key"""
class Ed448PrivateKey:
@classmethod
def generate(cls) -> 'Ed448PrivateKey':
"""Generate Ed448 private key"""
def public_key(self) -> Ed448PublicKey:
"""Get corresponding public key"""
def sign(self, data: bytes) -> bytes:
"""Sign with Ed448 (114-byte signature)"""
class Ed448PublicKey:
def verify(self, signature: bytes, data: bytes) -> None:
"""Verify Ed448 signature"""Elliptic Curve Diffie-Hellman using Curve25519 and Curve448.
class X25519PrivateKey:
@classmethod
def generate(cls) -> 'X25519PrivateKey':
"""Generate X25519 private key"""
@classmethod
def from_private_bytes(cls, data: bytes) -> 'X25519PrivateKey':
"""Load from 32-byte private key"""
def public_key(self) -> X25519PublicKey:
"""Get corresponding public key"""
def exchange(self, peer_public_key: X25519PublicKey) -> bytes:
"""
Perform X25519 key exchange.
Args:
peer_public_key: Other party's public key
Returns:
bytes: 32-byte shared secret
"""
class X25519PublicKey:
@classmethod
def from_public_bytes(cls, data: bytes) -> 'X25519PublicKey':
"""Load from 32-byte public key"""
class X448PrivateKey:
@classmethod
def generate(cls) -> 'X448PrivateKey':
"""Generate X448 private key"""
def exchange(self, peer_public_key: X448PublicKey) -> bytes:
"""Perform X448 key exchange (56-byte shared secret)"""
class X448PublicKey:
@classmethod
def from_public_bytes(cls, data: bytes) -> 'X448PublicKey':
"""Load from 56-byte public key"""class PKCS1v15:
"""PKCS#1 v1.5 padding (legacy, use OAEP for encryption)"""
class PSS:
def __init__(self, mgf, salt_length: int):
"""
PSS padding for RSA signatures.
Args:
mgf: Mask generation function (usually MGF1())
salt_length: Salt length (PSS.MAX_LENGTH for maximum)
"""
MAX_LENGTH: int = -1 # Use maximum salt length
class OAEP:
def __init__(self, mgf, algorithm, label: bytes = None):
"""
OAEP padding for RSA encryption.
Args:
mgf: Mask generation function (MGF1())
algorithm: Hash algorithm
label: Optional label (usually None)
"""
class MGF1:
def __init__(self, algorithm):
"""
MGF1 mask generation function.
Args:
algorithm: Hash algorithm (e.g., hashes.SHA256())
"""from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.exceptions import InvalidSignature
# Generate RSA key pair
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048 # Use 3072 or 4096 for higher security
)
public_key = private_key.public_key()
# Sign data
message = b"Important document content"
signature = private_key.sign(
message,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
print(f"Signature: {signature.hex()}")
# Verify signature
try:
public_key.verify(
signature,
message,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
print("Signature valid")
except InvalidSignature:
print("Signature invalid")
# Save keys
private_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)
public_pem = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
with open('private_key.pem', 'wb') as f:
f.write(private_pem)
with open('public_key.pem', 'wb') as f:
f.write(public_pem)from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes
# Generate keys
private_key = rsa.generate_private_key(65537, 2048)
public_key = private_key.public_key()
# Encrypt small message with public key
message = b"Secret message to encrypt"
ciphertext = public_key.encrypt(
message,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
print(f"Encrypted: {ciphertext.hex()}")
# Decrypt with private key
plaintext = private_key.decrypt(
ciphertext,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
print(f"Decrypted: {plaintext}")from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes
from cryptography.exceptions import InvalidSignature
# Generate ECDSA key pair
private_key = ec.generate_private_key(ec.SECP256R1())
public_key = private_key.public_key()
# Sign data
data = b"Data to sign with ECDSA"
signature = private_key.sign(data, hashes.SHA256())
print(f"ECDSA signature: {signature.hex()}")
# Verify signature
try:
public_key.verify(signature, data, hashes.SHA256())
print("ECDSA signature valid")
except InvalidSignature:
print("ECDSA signature invalid")from cryptography.hazmat.primitives.asymmetric import ed25519
from cryptography.exceptions import InvalidSignature
# Generate Ed25519 key pair
private_key = ed25519.Ed25519PrivateKey.generate()
public_key = private_key.public_key()
# Sign data (no hash algorithm needed)
message = b"Message to sign with Ed25519"
signature = private_key.sign(message)
print(f"Ed25519 signature: {signature.hex()}")
print(f"Signature length: {len(signature)} bytes")
# Verify signature
try:
public_key.verify(signature, message)
print("Ed25519 signature valid")
except InvalidSignature:
print("Ed25519 signature invalid")
# Serialize keys (raw bytes)
private_bytes = private_key.private_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PrivateFormat.Raw,
encryption_algorithm=serialization.NoEncryption()
)
public_bytes = public_key.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw
)
print(f"Private key: {private_bytes.hex()} ({len(private_bytes)} bytes)")
print(f"Public key: {public_bytes.hex()} ({len(public_bytes)} bytes)")from cryptography.hazmat.primitives.asymmetric import x25519
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
# Alice generates her key pair
alice_private = x25519.X25519PrivateKey.generate()
alice_public = alice_private.public_key()
# Bob generates his key pair
bob_private = x25519.X25519PrivateKey.generate()
bob_public = bob_private.public_key()
# Alice performs key exchange
alice_shared_secret = alice_private.exchange(bob_public)
# Bob performs key exchange
bob_shared_secret = bob_private.exchange(alice_public)
# Verify both parties have same shared secret
assert alice_shared_secret == bob_shared_secret
print(f"Shared secret: {alice_shared_secret.hex()}")
# Derive encryption key from shared secret
derived_key = HKDF(
algorithm=hashes.SHA256(),
length=32,
salt=None,
info=b'key exchange session',
).derive(alice_shared_secret)
print(f"Derived key: {derived_key.hex()}")from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives import hashes
import os
class HybridEncryption:
def __init__(self, rsa_public_key):
self.rsa_public_key = rsa_public_key
def encrypt(self, data: bytes) -> dict:
"""Encrypt large data using hybrid encryption"""
# Generate random AES key
aes_key = AESGCM.generate_key(256)
aesgcm = AESGCM(aes_key)
# Encrypt data with AES
nonce = os.urandom(12)
ciphertext = aesgcm.encrypt(nonce, data, None)
# Encrypt AES key with RSA
encrypted_key = self.rsa_public_key.encrypt(
aes_key,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
return {
'encrypted_key': encrypted_key,
'nonce': nonce,
'ciphertext': ciphertext
}
def decrypt(self, encrypted_data: dict, rsa_private_key):
"""Decrypt hybrid encrypted data"""
# Decrypt AES key with RSA
aes_key = rsa_private_key.decrypt(
encrypted_data['encrypted_key'],
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
# Decrypt data with AES
aesgcm = AESGCM(aes_key)
plaintext = aesgcm.decrypt(
encrypted_data['nonce'],
encrypted_data['ciphertext'],
None
)
return plaintext
# Usage
# Generate RSA key pair
rsa_private = rsa.generate_private_key(65537, 2048)
rsa_public = rsa_private.public_key()
# Create hybrid encryption instance
hybrid = HybridEncryption(rsa_public)
# Encrypt large data
large_data = b"This is a large document that would be too big for RSA encryption alone. " * 100
encrypted = hybrid.encrypt(large_data)
print(f"Encrypted key size: {len(encrypted['encrypted_key'])} bytes")
print(f"Ciphertext size: {len(encrypted['ciphertext'])} bytes")
# Decrypt
decrypted = hybrid.decrypt(encrypted, rsa_private)
print(f"Decryption successful: {decrypted == large_data}")from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes, serialization
import datetime
# Generate key pair for certificate
private_key = rsa.generate_private_key(65537, 2048)
public_key = private_key.public_key()
# Create self-signed certificate
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "California"),
x509.NameAttribute(NameOID.LOCALITY_NAME, "San Francisco"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "My Organization"),
x509.NameAttribute(NameOID.COMMON_NAME, "example.com"),
])
cert = x509.CertificateBuilder().subject_name(
subject
).issuer_name(
issuer
).public_key(
public_key
).serial_number(
x509.random_serial_number()
).not_valid_before(
datetime.datetime.utcnow()
).not_valid_after(
datetime.datetime.utcnow() + datetime.timedelta(days=365)
).add_extension(
x509.BasicConstraints(ca=False, path_length=None),
critical=True,
).sign(private_key, hashes.SHA256())
# Save certificate
cert_pem = cert.public_bytes(serialization.Encoding.PEM)
with open('self_signed_cert.pem', 'wb') as f:
f.write(cert_pem)
print("Self-signed certificate created")Install with Tessl CLI
npx tessl i tessl/pypi-cryptography