Skip to content

File Encryption Guide

qcrypto provides streaming file encryption that handles files of any size without loading them entirely into memory.

Basic File Encryption

Encrypt a File

from qcrypto import KyberKEM, encrypt_file

# Load or generate keys
kem = KyberKEM("Kyber768")
keys = kem.generate_keypair()

# Encrypt
encrypt_file(
    public_key=keys.public_key,
    input_path="secret_document.pdf",
    output_path="secret_document.pdf.enc"
)

Decrypt a File

from qcrypto import decrypt_file

decrypt_file(
    private_key=keys.private_key,
    input_path="secret_document.pdf.enc",
    output_path="secret_document.pdf"
)

Using the CLI

# Generate keys (one-time)
qcrypto gen-key --armored --pass

# Encrypt a file
qcrypto encrypt --in report.xlsx --out report.xlsx.enc

# Decrypt a file
qcrypto decrypt --pass --in report.xlsx.enc --out report.xlsx

Streaming Architecture

qcrypto processes files in 64 KiB chunks by default:

┌─────────────────────────────────────────────────────────────┐
│                    File Encryption                           │
├─────────────────────────────────────────────────────────────┤
│  1. Generate Kyber ciphertext + shared secret               │
│  2. Derive AES key via HKDF                                 │
│  3. Write header + Kyber CT + nonce                         │
│  4. Stream file through AES-GCM encryptor                   │
│  5. Append GCM tag                                          │
└─────────────────────────────────────────────────────────────┘

This means:

  • ✅ 10 GB files work fine
  • ✅ Memory usage stays constant (~64 KiB)
  • ✅ Compatible with in-memory encrypt()/decrypt()

Custom Chunk Size

For very large files or memory-constrained systems:

encrypt_file(
    public_key=pub,
    input_path="huge_backup.tar",
    output_path="huge_backup.tar.enc",
    chunk_size=16 * 1024  # 16 KiB chunks
)

Error Handling

from qcrypto import encrypt_file, decrypt_file

try:
    decrypt_file(priv, "corrupted.enc", "output.txt")
except ValueError as e:
    if "Corrupted header" in str(e):
        print("File header is damaged")
    elif "truncated" in str(e):
        print("File was cut off")
    elif "tag" in str(e).lower():
        print("File was modified (authentication failed)")

File Format

Encrypted files use the same format as encrypt():

[8 bytes]    Header (version, algo, length, checksum)
[1088 bytes] Kyber768 ciphertext
[12 bytes]   AES-GCM nonce
[variable]   AES-GCM ciphertext (same size as plaintext)
[16 bytes]   AES-GCM authentication tag

Overhead: ~1,124 bytes regardless of file size.

Best Practices

Batch Encryption

from pathlib import Path
from qcrypto import encrypt_file, KyberKEM

pub = KyberKEM.load_public_key("backup.pub")

for file in Path("sensitive/").glob("*.docx"):
    encrypt_file(pub, str(file), f"{file}.enc")
    file.unlink()  # Remove original after encryption

Secure Deletion

After encrypting, consider secure deletion of originals:

# macOS/Linux
srm original.pdf  # or: shred -u original.pdf

# Or just encrypt in-place workflow
qcrypto encrypt --in data.db --out data.db.enc && rm data.db

Verify Before Deleting

from qcrypto import encrypt_file, decrypt_file
import tempfile
import filecmp

# Encrypt
encrypt_file(pub, "important.xlsx", "important.xlsx.enc")

# Verify by decrypting to temp
with tempfile.NamedTemporaryFile() as tmp:
    decrypt_file(priv, "important.xlsx.enc", tmp.name)
    if filecmp.cmp("important.xlsx", tmp.name):
        print("Verified! Safe to delete original.")