### Introduction to Hybrid Encryption Hybrid encryption combines the strengths of symmetric and asymmetric cryptography. Symmetric algorithms (like ChaCha20) are fast for bulk data encryption, while asymmetric algorithms (like Kyber) are secure for key exchange. This cheatsheet outlines the development of a tool using ChaCha20 (256-bit) for data encryption and Kyber (1024-bit) for key encapsulation mechanism (KEM). #### Why Hybrid? - **Efficiency:** Symmetric encryption is much faster for large files. - **Security:** Asymmetric encryption provides secure key exchange, addressing the key distribution problem of symmetric schemes. - **Post-Quantum Security:** Kyber is a post-quantum cryptographic (PQC) algorithm, offering resistance against attacks from quantum computers. ### ChaCha20 (256-bit) Overview ChaCha20 is a stream cipher designed by Daniel J. Bernstein. It's known for its speed and security, making it suitable for encrypting large files. #### Key Components - **Key:** A 256-bit (32-byte) secret key. - **Nonce:** A unique, non-repeating value (typically 96-bit or 12-byte) used once per encryption with a given key. It prevents identical plaintext from producing identical ciphertext. - **Counter:** A 32-bit integer, initialized to 0 or 1, and incremented for each block of ciphertext. - **Poly1305 (MAC):** Often combined with ChaCha20 to provide authenticated encryption (ChaCha20-Poly1305). This ensures data integrity and authenticity. #### Encryption Process 1. Generate a random 256-bit encryption key (file key). 2. Generate a unique 96-bit nonce. 3. Encrypt the file data using ChaCha20 with the file key and nonce. 4. If using Poly1305, compute and append the MAC. ### Kyber (1024-bit) Overview Kyber is a Learning With Errors (LWE) based Key Encapsulation Mechanism (KEM) selected by NIST for standardization. It provides post-quantum secure key exchange. Kyber-1024 corresponds to NIST security level 5. #### Key Components - **Public Key (PK):** Used to encapsulate a shared secret. - **Secret Key (SK):** Used to decapsulate the shared secret. - **Ciphertext (CT):** The encapsulated shared secret. - **Shared Secret (SS):** The symmetric key derived by both parties. #### Key Pair Generation 1. Generate a Kyber-1024 public/secret key pair. #### Key Encapsulation (Sender) 1. Generate a random symmetric key (Kyber's "shared secret"). 2. Encapsulate this shared secret using the recipient's Kyber public key, producing a ciphertext. 3. The shared secret generated here is then used as the file key for ChaCha20 encryption. #### Key Decapsulation (Recipient) 1. Receive the Kyber ciphertext. 2. Decapsulate the ciphertext using the recipient's Kyber secret key to recover the shared secret (which is the ChaCha20 file key). ### Tool Architecture The tool will perform the following steps for encryption and decryption. #### Encryption Flow 1. **Generate Symmetric File Key:** Generate a random 256-bit key (`file_key`) for ChaCha20. 2. **Kyber Key Pair (Optional):** If not already available, generate a Kyber-1024 `public_key` and `secret_key` for the recipient. 3. **Encapsulate File Key:** Use the recipient's Kyber `public_key` to encapsulate the `file_key`, generating a Kyber `ciphertext`. 4. **Encrypt File Data:** Generate a random ChaCha20 `nonce`. Encrypt the original file using ChaCha20-Poly1305 with `file_key` and `nonce`. 5. **Output Encrypted File:** The output file will contain: - Kyber `ciphertext` - ChaCha20 `nonce` - ChaCha20-Poly1305 `ciphertext` (including MAC) #### Decryption Flow 1. **Read Encrypted File:** Parse the encrypted file to extract the Kyber `ciphertext`, ChaCha20 `nonce`, and ChaCha20-Poly1305 `ciphertext`. 2. **Decapsulate File Key:** Use the recipient's Kyber `secret_key` to decapsulate the Kyber `ciphertext`, recovering the original `file_key`. 3. **Decrypt File Data:** Decrypt the ChaCha20-Poly1305 `ciphertext` using the recovered `file_key` and `nonce`. Verify the MAC. 4. **Output Decrypted File:** Write the decrypted data to a new file. ### Implementation Considerations #### Libraries - **Python:** `cryptography` library (for ChaCha20-Poly1305) and `pqclean` or `fips203` (for Kyber). - **C/C++:** `libsodium` (for ChaCha20-Poly1305) and `pqclean` or `liboqs` (for Kyber). #### File Format A simple header for the encrypted file could look like: ``` [Kyber Ciphertext Length (4 bytes)] [Kyber Ciphertext (variable length, ~1088 bytes for Kyber-1024)] [ChaCha20 Nonce (12 bytes)] [ChaCha20-Poly1305 Ciphertext + Tag (variable length)] ``` This allows easy parsing during decryption. #### Error Handling - **Key Decapsulation Failure:** If Kyber decapsulation fails, the `file_key` cannot be recovered, and decryption should halt. - **MAC Verification Failure:** If the Poly1305 tag does not verify, the data has been tampered with or corrupted. Decryption should fail. - **File I/O Errors:** Handle exceptions for reading/writing files. #### Security Best Practices - **Randomness:** Use cryptographically secure random number generators for keys and nonces. - **Key Storage:** Kyber secret keys should be stored securely (e.g., encrypted on disk, hardware security module). - **Nonce Uniqueness:** Ensure nonces are never reused with the same key. - **Side Channels:** Be aware of potential side-channel attacks, especially when implementing cryptographic primitives. Using well-vetted libraries significantly mitigates this risk. ### Example Code Structure (Python - Pseudocode) ```python from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, hmac import os # Assuming a Kyber library like `pqclean.kyber` exists # --- Configuration --- KYBER_SECURITY_LEVEL = 1024 # Kyber-1024 CHACHA20_KEY_LENGTH = 32 # 256-bit key CHACHA20_NONCE_LENGTH = 12 # 96-bit nonce # --- Kyber Functions (Pseudocode) --- def kyber_generate_keypair(): # pk, sk = pqclean.kyber.generate_keypair(KYBER_SECURITY_LEVEL) # return pk, sk pass # Placeholder def kyber_encapsulate(pk, shared_secret_bytes): # ciphertext_kyber, encapsulated_key = pqclean.kyber.encapsulate(pk, shared_secret_bytes) # return ciphertext_kyber, encapsulated_key pass # Placeholder def kyber_decapsulate(sk, ciphertext_kyber): # shared_secret_bytes = pqclean.kyber.decapsulate(sk, ciphertext_kyber) # return shared_secret_bytes pass # Placeholder # --- ChaCha20-Poly1305 Functions --- def chacha20_encrypt(key, nonce, data): cipher = Cipher(algorithms.ChaCha20Poly1305(key, nonce), mode=None, backend=default_backend()) encryptor = cipher.encryptor() ciphertext = encryptor.update(data) + encryptor.finalize() tag = encryptor.tag return ciphertext, tag def chacha20_decrypt(key, nonce, ciphertext, tag): cipher = Cipher(algorithms.ChaCha20Poly1305(key, nonce), mode=None, backend=default_backend()) decryptor = cipher.decryptor() decryptor.authenticate_associated_data(b'') # No AAD for simplicity plaintext = decryptor.update(ciphertext) + decryptor.finalize() # Tag verification happens automatically on finalize() if .tag is set return plaintext # --- Hybrid Encryption Tool --- def encrypt_file(input_filepath, output_filepath, recipient_kyber_pk): # 1. Generate ChaCha20 file key file_key = os.urandom(CHACHA20_KEY_LENGTH) # 2. Encapsulate file key with Kyber # For actual Kyber, `shared_secret_bytes` would be the random key Kyber generates internally. # Here, we're using Kyber to encapsulate our pre-generated file_key. # This might require a slight adaptation depending on the Kyber library's API. # Assuming `kyber_encapsulate` takes the PK and the key to encapsulate. ciphertext_kyber, _ = kyber_encapsulate(recipient_kyber_pk, file_key) # Pseudocode # 3. Encrypt file data with ChaCha20-Poly1305 nonce = os.urandom(CHACHA20_NONCE_LENGTH) with open(input_filepath, 'rb') as f_in: plaintext_data = f_in.read() ciphertext_chacha, tag_chacha = chacha20_encrypt(file_key, nonce, plaintext_data) # 4. Write encrypted data to output file with open(output_filepath, 'wb') as f_out: f_out.write(len(ciphertext_kyber).to_bytes(4, 'big')) f_out.write(ciphertext_kyber) f_out.write(nonce) f_out.write(ciphertext_chacha) f_out.write(tag_chacha) # Append tag print(f"File encrypted to {output_filepath}") def decrypt_file(input_filepath, output_filepath, recipient_kyber_sk): with open(input_filepath, 'rb') as f_in: # 1. Read Kyber ciphertext length and data kyber_ct_len = int.from_bytes(f_in.read(4), 'big') ciphertext_kyber = f_in.read(kyber_ct_len) # 2. Read ChaCha20 nonce and ciphertext + tag nonce = f_in.read(CHACHA20_NONCE_LENGTH) # Read remaining data as ciphertext+tag full_chacha_data = f_in.read() # Assume Poly1305 tag is 16 bytes tag_chacha = full_chacha_data[-16:] ciphertext_chacha = full_chacha_data[:-16] # 3. Decapsulate file key with Kyber file_key = kyber_decapsulate(recipient_kyber_sk, ciphertext_kyber) # Pseudocode # 4. Decrypt file data with ChaCha20-Poly1305 try: plaintext_data = chacha20_decrypt(file_key, nonce, ciphertext_chacha, tag_chacha) with open(output_filepath, 'wb') as f_out: f_out.write(plaintext_data) print(f"File decrypted to {output_filepath}") except Exception as e: # More specific exception handling needed for MAC failure print(f"Decryption failed: {e}") # --- Main execution example --- if __name__ == "__main__": # Generate Kyber key pair for recipient (or load from file) # recipient_pk, recipient_sk = kyber_generate_keypair() # Pseudocode # Example placeholders for actual keys/data recipient_pk_placeholder = b"..." # Replace with actual Kyber public key bytes recipient_sk_placeholder = b"..." # Replace with actual Kyber secret key bytes # Create a dummy file with open("test_file.txt", "w") as f: f.write("This is a test file for hybrid encryption.\n") f.write("It contains some sensitive data.\n") # Encrypt encrypt_file("test_file.txt", "test_file.enc", recipient_pk_placeholder) # Decrypt decrypt_file("test_file.enc", "test_file.dec", recipient_sk_placeholder) ```