kyber/encrypt.py
2026-07-26 19:26:27 +00:00

86 lines
2.3 KiB
Python

#!/usr/bin/env python3
"""
Hybrid encrypt a file using AES-256-CTR + HMAC-SHA256 + Kyber-1024.
Streaming handles files larger than RAM.
Usage:
python3 encrypt.py <filename>
Output: <filename>.enc
Requires: kyber.pub in the same directory.
"""
import os
import sys
import struct
import oqs
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.hmac import HMAC
from cryptography.hazmat.primitives import hashes
KYBER_KEM = "Kyber1024"
IV_LEN = 16
CHUNK_SIZE = 64 * 1024 * 1024 # 64 MB
HMAC_KEY_LEN = 32
def encrypt_file(plaintext_path: str, output_path: str, pub_key: bytes):
iv = os.urandom(IV_LEN)
hmac_key = os.urandom(HMAC_KEY_LEN)
kem = oqs.KeyEncapsulation(KYBER_KEM)
kyber_ct, aes_key = kem.encap_secret(pub_key)
kem.free()
cipher = Cipher(algorithms.AES(aes_key), modes.CTR(iv))
encryptor = cipher.encryptor()
hmac = HMAC(hmac_key, hashes.SHA256())
total_size = os.path.getsize(plaintext_path)
written = 0
with open(output_path, 'wb') as fout:
# Header
fout.write(struct.pack('!I', len(kyber_ct)))
fout.write(kyber_ct)
fout.write(iv)
fout.write(hmac_key)
with open(plaintext_path, 'rb') as fin:
while True:
chunk = fin.read(CHUNK_SIZE)
if not chunk:
break
ct_chunk = encryptor.update(chunk)
hmac.update(ct_chunk)
fout.write(ct_chunk)
written += len(chunk)
print(f" Progress: {written}/{total_size} bytes", end='\r')
fout.write(encryptor.finalize())
fout.write(hmac.finalize())
print(f"\nSuccess: {plaintext_path}{output_path} ({total_size} bytes)")
if __name__ == "__main__":
if len(sys.argv) != 2:
print(__doc__)
sys.exit(1)
infile = sys.argv[1]
if not os.path.exists(infile):
print(f"Error: File not found: {infile}")
sys.exit(1)
pubkey_path = "kyber.pub"
if not os.path.exists(pubkey_path):
print(f"Error: No Kyber public key found. Run gen_kyber.py first.")
sys.exit(1)
with open(pubkey_path, 'rb') as f:
pub_key = f.read()
outfile = infile + ".enc"
encrypt_file(infile, outfile, pub_key)