CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-cryptography

Cryptographic recipes and primitives for Python developers

Pending
Overview
Eval results
Files

asymmetric-cryptography.mddocs/

Asymmetric Cryptography

Public-key cryptography including RSA, DSA, ECDSA, EdDSA, and key exchange algorithms. Supports key generation, digital signatures, encryption, and key agreement protocols.

Core Imports

from cryptography.hazmat.primitives.asymmetric import rsa, dsa, ec, ed25519, ed448, x25519, x448, dh, padding
from cryptography.hazmat.primitives import hashes, serialization

Capabilities

RSA (Rivest-Shamir-Adleman)

RSA 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"""

Elliptic Curve Cryptography (ECC)

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"

EdDSA (Edwards-curve Digital Signature Algorithm)

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"""

X25519/X448 Key Exchange

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"""

RSA Padding Schemes

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())
        """

Usage Examples

RSA Key Generation and Digital Signatures

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)

RSA Encryption and Decryption

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}")

ECDSA with P-256

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")

Ed25519 Digital Signatures

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)")

X25519 Key Exchange

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()}")

Hybrid Encryption (RSA + AES)

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}")

Digital Certificate Creation with Asymmetric Keys

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")

Security Considerations

  • Key Sizes: Use RSA 2048+ bits, EC 256+ bits for new applications
  • Algorithm Selection: Prefer Ed25519/Ed448 for signatures, X25519/X448 for key exchange
  • Padding: Always use OAEP for RSA encryption, PSS for RSA signatures
  • Randomness: Use cryptographically secure random number generators
  • Key Management: Store private keys securely, never hardcode them
  • Hybrid Encryption: Use asymmetric crypto for key exchange, symmetric for data
  • Signature Verification: Always verify signatures before trusting data
  • Forward Secrecy: Use ephemeral keys for key exchange when possible

Install with Tessl CLI

npx tessl i tessl/pypi-cryptography

docs

aead-ciphers.md

asymmetric-cryptography.md

hash-functions.md

index.md

key-derivation.md

key-serialization.md

message-authentication.md

symmetric-ciphers.md

symmetric-encryption.md

two-factor-auth.md

utilities.md

x509-certificates.md

tile.json