Encrypt/Decrypt with Key Pairs

Encrypt and decrypt data using public/private key pairs and derive symmetric keys from ECC key pairs.


RSA Encryption (OpenSSL)

 1# Generate RSA key pair
 2openssl genrsa -out private.pem 4096
 3openssl rsa -in private.pem -pubout -out public.pem
 4
 5# Encrypt file with public key
 6openssl rsautl -encrypt -pubin -inkey public.pem -in message.txt -out encrypted.bin
 7
 8# Decrypt file with private key
 9openssl rsautl -decrypt -inkey private.pem -in encrypted.bin -out decrypted.txt
10
11# For larger files, use hybrid encryption (RSA + AES)
12# Generate random AES key
13openssl rand -base64 32 > aes.key
14
15# Encrypt file with AES
16openssl enc -aes-256-cbc -salt -in largefile.txt -out largefile.enc -pass file:aes.key
17
18# Encrypt AES key with RSA
19openssl rsautl -encrypt -pubin -inkey public.pem -in aes.key -out aes.key.enc
20
21# Decrypt AES key with RSA
22openssl rsautl -decrypt -inkey private.pem -in aes.key.enc -out aes.key.dec
23
24# Decrypt file with AES
25openssl enc -d -aes-256-cbc -in largefile.enc -out largefile.dec -pass file:aes.key.dec

RSA Encryption (pkeyutl - Modern)

 1# Encrypt with public key
 2openssl pkeyutl -encrypt -pubin -inkey public.pem -in message.txt -out encrypted.bin
 3
 4# Decrypt with private key
 5openssl pkeyutl -decrypt -inkey private.pem -in encrypted.bin -out decrypted.txt
 6
 7# With OAEP padding (recommended)
 8openssl pkeyutl -encrypt -pubin -inkey public.pem -pkeyopt rsa_padding_mode:oaep \
 9  -pkeyopt rsa_oaep_md:sha256 -in message.txt -out encrypted.bin
10
11openssl pkeyutl -decrypt -inkey private.pem -pkeyopt rsa_padding_mode:oaep \
12  -pkeyopt rsa_oaep_md:sha256 -in encrypted.bin -out decrypted.txt

ECDH Key Exchange

 1# Alice generates key pair
 2openssl ecparam -name prime256v1 -genkey -noout -out alice_private.pem
 3openssl ec -in alice_private.pem -pubout -out alice_public.pem
 4
 5# Bob generates key pair
 6openssl ecparam -name prime256v1 -genkey -noout -out bob_private.pem
 7openssl ec -in bob_private.pem -pubout -out bob_public.pem
 8
 9# Alice derives shared secret using Bob's public key
10openssl pkeyutl -derive -inkey alice_private.pem -peerkey bob_public.pem -out alice_shared.bin
11
12# Bob derives shared secret using Alice's public key
13openssl pkeyutl -derive -inkey bob_private.pem -peerkey alice_public.pem -out bob_shared.bin
14
15# Both shared secrets are identical
16diff alice_shared.bin bob_shared.bin  # No output = identical
17
18# Derive AES key from shared secret
19openssl dgst -sha256 -binary alice_shared.bin > aes.key
20
21# Encrypt with derived key
22openssl enc -aes-256-cbc -salt -in message.txt -out encrypted.bin -pass file:aes.key
23
24# Decrypt with derived key
25openssl enc -d -aes-256-cbc -in encrypted.bin -out decrypted.txt -pass file:aes.key

GPG/PGP Encryption

 1# Encrypt for recipient
 2gpg --encrypt --recipient recipient@example.com message.txt
 3# Creates message.txt.gpg
 4
 5# Encrypt (ASCII armor)
 6gpg --encrypt --armor --recipient recipient@example.com message.txt
 7# Creates message.txt.asc
 8
 9# Encrypt for multiple recipients
10gpg --encrypt --recipient alice@example.com --recipient bob@example.com message.txt
11
12# Encrypt and sign
13gpg --encrypt --sign --recipient recipient@example.com message.txt
14
15# Decrypt
16gpg --decrypt message.txt.gpg > decrypted.txt
17
18# Decrypt and verify signature
19gpg --decrypt message.txt.gpg

Age Encryption (Modern Alternative)

 1# Install age
 2sudo apt install age
 3
 4# Generate key pair
 5age-keygen -o key.txt
 6# Public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
 7
 8# Encrypt with public key
 9age -r age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p \
10  -o encrypted.age message.txt
11
12# Decrypt with private key
13age -d -i key.txt encrypted.age > decrypted.txt
14
15# Encrypt for multiple recipients
16age -r age1... -r age1... -o encrypted.age message.txt

Python: RSA Encryption

 1from cryptography.hazmat.primitives.asymmetric import rsa, padding
 2from cryptography.hazmat.primitives import hashes, serialization
 3from cryptography.hazmat.backends import default_backend
 4
 5# Generate key pair
 6private_key = rsa.generate_private_key(
 7    public_exponent=65537,
 8    key_size=4096,
 9    backend=default_backend()
10)
11public_key = private_key.public_key()
12
13# Encrypt with public key
14message = b"Hello, World!"
15ciphertext = public_key.encrypt(
16    message,
17    padding.OAEP(
18        mgf=padding.MGF1(algorithm=hashes.SHA256()),
19        algorithm=hashes.SHA256(),
20        label=None
21    )
22)
23
24print(f"Encrypted: {ciphertext.hex()[:64]}...")
25
26# Decrypt with private key
27plaintext = private_key.decrypt(
28    ciphertext,
29    padding.OAEP(
30        mgf=padding.MGF1(algorithm=hashes.SHA256()),
31        algorithm=hashes.SHA256(),
32        label=None
33    )
34)
35
36print(f"Decrypted: {plaintext.decode()}")

Python: ECDH Key Exchange

 1from cryptography.hazmat.primitives.asymmetric import ec
 2from cryptography.hazmat.primitives import hashes, serialization
 3from cryptography.hazmat.primitives.kdf.hkdf import HKDF
 4from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
 5from cryptography.hazmat.backends import default_backend
 6import os
 7
 8# Alice generates key pair
 9alice_private = ec.generate_private_key(ec.SECP256R1(), default_backend())
10alice_public = alice_private.public_key()
11
12# Bob generates key pair
13bob_private = ec.generate_private_key(ec.SECP256R1(), default_backend())
14bob_public = bob_private.public_key()
15
16# Alice derives shared secret
17alice_shared = alice_private.exchange(ec.ECDH(), bob_public)
18
19# Bob derives shared secret
20bob_shared = bob_private.exchange(ec.ECDH(), alice_public)
21
22# Both shared secrets are identical
23assert alice_shared == bob_shared
24
25# Derive AES key from shared secret using HKDF
26def derive_key(shared_secret):
27    return HKDF(
28        algorithm=hashes.SHA256(),
29        length=32,
30        salt=None,
31        info=b'handshake data',
32        backend=default_backend()
33    ).derive(shared_secret)
34
35aes_key = derive_key(alice_shared)
36
37# Encrypt with derived key
38def encrypt_message(key, plaintext):
39    iv = os.urandom(16)
40    cipher = Cipher(
41        algorithms.AES(key),
42        modes.CBC(iv),
43        backend=default_backend()
44    )
45    encryptor = cipher.encryptor()
46    
47    # Pad plaintext to block size
48    pad_length = 16 - (len(plaintext) % 16)
49    padded = plaintext + bytes([pad_length] * pad_length)
50    
51    ciphertext = encryptor.update(padded) + encryptor.finalize()
52    return iv + ciphertext
53
54# Decrypt with derived key
55def decrypt_message(key, ciphertext):
56    iv = ciphertext[:16]
57    actual_ciphertext = ciphertext[16:]
58    
59    cipher = Cipher(
60        algorithms.AES(key),
61        modes.CBC(iv),
62        backend=default_backend()
63    )
64    decryptor = cipher.decryptor()
65    
66    padded = decryptor.update(actual_ciphertext) + decryptor.finalize()
67    
68    # Remove padding
69    pad_length = padded[-1]
70    return padded[:-pad_length]
71
72# Example usage
73message = b"Hello, World!"
74encrypted = encrypt_message(aes_key, message)
75decrypted = decrypt_message(aes_key, encrypted)
76
77print(f"Original: {message}")
78print(f"Encrypted: {encrypted.hex()[:64]}...")
79print(f"Decrypted: {decrypted}")

Python: Hybrid Encryption (RSA + AES)

 1from cryptography.hazmat.primitives.asymmetric import rsa, padding
 2from cryptography.hazmat.primitives import hashes, serialization
 3from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
 4from cryptography.hazmat.backends import default_backend
 5import os
 6
 7class HybridEncryption:
 8    def __init__(self):
 9        # Generate RSA key pair
10        self.private_key = rsa.generate_private_key(
11            public_exponent=65537,
12            key_size=4096,
13            backend=default_backend()
14        )
15        self.public_key = self.private_key.public_key()
16    
17    def encrypt(self, plaintext: bytes) -> tuple:
18        """Encrypt using hybrid encryption (RSA + AES)"""
19        # Generate random AES key
20        aes_key = os.urandom(32)
21        iv = os.urandom(16)
22        
23        # Encrypt data with AES
24        cipher = Cipher(
25            algorithms.AES(aes_key),
26            modes.CBC(iv),
27            backend=default_backend()
28        )
29        encryptor = cipher.encryptor()
30        
31        # Pad plaintext
32        pad_length = 16 - (len(plaintext) % 16)
33        padded = plaintext + bytes([pad_length] * pad_length)
34        
35        ciphertext = encryptor.update(padded) + encryptor.finalize()
36        
37        # Encrypt AES key with RSA
38        encrypted_key = self.public_key.encrypt(
39            aes_key,
40            padding.OAEP(
41                mgf=padding.MGF1(algorithm=hashes.SHA256()),
42                algorithm=hashes.SHA256(),
43                label=None
44            )
45        )
46        
47        return encrypted_key, iv, ciphertext
48    
49    def decrypt(self, encrypted_key: bytes, iv: bytes, ciphertext: bytes) -> bytes:
50        """Decrypt using hybrid encryption"""
51        # Decrypt AES key with RSA
52        aes_key = self.private_key.decrypt(
53            encrypted_key,
54            padding.OAEP(
55                mgf=padding.MGF1(algorithm=hashes.SHA256()),
56                algorithm=hashes.SHA256(),
57                label=None
58            )
59        )
60        
61        # Decrypt data with AES
62        cipher = Cipher(
63            algorithms.AES(aes_key),
64            modes.CBC(iv),
65            backend=default_backend()
66        )
67        decryptor = cipher.decryptor()
68        
69        padded = decryptor.update(ciphertext) + decryptor.finalize()
70        
71        # Remove padding
72        pad_length = padded[-1]
73        return padded[:-pad_length]
74
75# Example usage
76hybrid = HybridEncryption()
77
78message = b"This is a long message that needs hybrid encryption!"
79encrypted_key, iv, ciphertext = hybrid.encrypt(message)
80
81print(f"Original: {message}")
82print(f"Encrypted key: {encrypted_key.hex()[:64]}...")
83print(f"IV: {iv.hex()}")
84print(f"Ciphertext: {ciphertext.hex()[:64]}...")
85
86decrypted = hybrid.decrypt(encrypted_key, iv, ciphertext)
87print(f"Decrypted: {decrypted}")

ECDH Flow Diagram


Key Exchange Mathematics

For ECDH on curve ( E ) with generator ( G ):

Alice:

  • Private key: ( a \in \mathbb{Z}_n )
  • Public key: ( A = aG )

Bob:

  • Private key: ( b \in \mathbb{Z}_n )
  • Public key: ( B = bG )

Shared Secret:

$$ \text{Alice computes: } S = aB = a(bG) = abG $$

$$ \text{Bob computes: } S = bA = b(aG) = abG $$

$$ \therefore S_{\text{Alice}} = S_{\text{Bob}} $$


Best Practices

  1. RSA Encryption:

    • Use OAEP padding (not PKCS#1 v1.5)
    • Use 4096-bit keys minimum
    • Use hybrid encryption for large data
    • Never encrypt same data twice
  2. ECDH:

    • Use strong curves (P-256, P-384, Curve25519)
    • Use HKDF to derive keys from shared secret
    • Include context info in KDF
    • Use authenticated encryption (GCM)
  3. General:

    • Always use authenticated encryption
    • Generate new IV for each encryption
    • Use secure random number generator
    • Verify recipient's public key

Related Snippets