cryptography

Implementing AES Encryption for Data at Rest

AES (Advanced Encryption Standard) is a symmetric block cipher standardized by NIST (FIPS 197) used to protect classified and sensitive data. This skill covers implementing AES-256 encryption in GCM mode for encrypting files and data stores at rest, including proper key derivation, IV/nonce management, and authenticated encryption.

aescryptographydata-at-restencryptionsymmetric-encryption
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

AES (Advanced Encryption Standard) is a symmetric block cipher standardized by NIST (FIPS 197) used to protect classified and sensitive data. This skill covers implementing AES-256 encryption in GCM mode for encrypting files and data stores at rest, including proper key derivation, IV/nonce management, and authenticated encryption.

When to Use

  • When deploying or configuring implementing aes encryption for data at rest capabilities in your environment
  • When establishing security controls aligned to compliance requirements
  • When building or improving security architecture for this domain
  • When conducting security assessments that require this implementation

Prerequisites

  • Familiarity with cryptography concepts and tools
  • Access to a test or lab environment for safe execution
  • Python 3.8+ with required dependencies installed
  • Appropriate authorization for any testing activities

Objectives

  • Implement AES-256-GCM encryption and decryption for files
  • Derive encryption keys from passwords using PBKDF2 and Argon2
  • Manage initialization vectors (IVs) and nonces securely
  • Encrypt and decrypt entire directory trees
  • Implement authenticated encryption to detect tampering
  • Handle large files with streaming encryption

Key Concepts

AES Modes of Operation

Mode Authentication Parallelizable Use Case
GCM Yes (AEAD) Yes Network data, file encryption
CBC No Decrypt only Legacy systems, disk encryption
CTR No Yes Streaming encryption
CCM Yes (AEAD) No IoT, constrained environments

Key Derivation

Never use raw passwords as encryption keys. Always derive keys using:

  • PBKDF2: NIST-approved, widely supported (minimum 600,000 iterations as of 2024)
  • Argon2id: Winner of Password Hashing Competition, memory-hard
  • scrypt: Memory-hard, good alternative to Argon2

Nonce/IV Management

  • GCM requires a 96-bit (12-byte) nonce that must NEVER be reused with the same key
  • Generate nonces using os.urandom() (CSPRNG)
  • Store nonce alongside ciphertext (it is not secret)

Workflow

  1. Install the cryptography library: pip install cryptography
  2. Generate or derive an encryption key
  3. Create a random nonce for each encryption operation
  4. Encrypt data using AES-256-GCM with the key and nonce
  5. Store nonce + ciphertext + authentication tag together
  6. For decryption, extract nonce, verify tag, and decrypt

Encrypted File Format

[salt: 16 bytes][nonce: 12 bytes][ciphertext: variable][tag: 16 bytes]

Security Considerations

  • Always use authenticated encryption (GCM, CCM) to prevent tampering
  • Never reuse a nonce with the same key (catastrophic in GCM)
  • Use at least 256-bit keys for long-term data protection
  • Securely wipe keys from memory after use when possible
  • Rotate encryption keys periodically per organizational policy
  • For disk-level encryption, consider XTS mode (AES-XTS)

Validation Criteria

  • AES-256-GCM encryption produces valid ciphertext
  • Decryption recovers original plaintext exactly
  • Authentication tag detects any ciphertext modification
  • Key derivation uses sufficient iterations/parameters
  • Nonces are never reused for the same key
  • Large files (>1GB) can be processed via streaming
  • Encrypted file format includes all necessary metadata
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 3

api-reference.md1.8 KB

API Reference: Implementing AES Encryption for Data at Rest

cryptography Library - AESGCM

from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import os
 
key = AESGCM.generate_key(bit_length=256)
aesgcm = AESGCM(key)
nonce = os.urandom(12)  # 96-bit nonce, NEVER reuse
 
ciphertext = aesgcm.encrypt(nonce, plaintext, associated_data)
plaintext = aesgcm.decrypt(nonce, ciphertext, associated_data)

Key Derivation - PBKDF2

from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
 
kdf = PBKDF2HMAC(
    algorithm=hashes.SHA256(),
    length=32,               # 256-bit key
    salt=os.urandom(16),
    iterations=600_000,      # NIST 2024 recommendation
)
key = kdf.derive(password.encode())

Encrypted File Format

[salt: 16 bytes][nonce: 12 bytes][ciphertext + tag: variable]
Field Size Purpose
Salt 16 bytes PBKDF2 salt (random per file)
Nonce 12 bytes GCM nonce (random per encryption)
Ciphertext Variable Encrypted data + 16-byte auth tag

AES Modes Comparison

Mode AEAD Nonce Size Use Case
GCM Yes 12 bytes File/network encryption
CBC No 16 bytes Legacy, disk encryption
CTR No 16 bytes Streaming
XTS No 16 bytes Full disk encryption

Fernet (High-Level API)

from cryptography.fernet import Fernet
key = Fernet.generate_key()
f = Fernet(key)
token = f.encrypt(b"data")
plaintext = f.decrypt(token)

References

standards.md3.3 KB

Standards and References - AES Encryption for Data at Rest

Primary Standards

NIST FIPS 197 - Advanced Encryption Standard (AES)

NIST SP 800-38D - Recommendation for Block Cipher Modes: GCM and GMAC

NIST SP 800-132 - Recommendation for Password-Based Key Derivation

NIST SP 800-38A - Recommendation for Block Cipher Modes of Operation

NIST SP 800-57 Part 1 Rev. 5 - Key Management

RFC Standards

RFC 5116 - An Interface and Algorithms for Authenticated Encryption

RFC 5869 - HMAC-based Extract-and-Expand Key Derivation Function (HKDF)

RFC 9106 - Argon2 Memory-Hard Function

Compliance Frameworks

PCI DSS v4.0 - Requirement 3

  • Encrypt stored cardholder data with strong cryptography
  • AES-256 meets the strong cryptography requirement
  • Key management procedures required

HIPAA Security Rule - 45 CFR 164.312(a)(2)(iv)

  • Encryption of ePHI at rest is an addressable implementation specification
  • AES-256 is an acceptable encryption method

GDPR Article 32 - Security of Processing

  • Encryption is listed as an appropriate technical measure
  • AES-256 satisfies encryption requirements for personal data protection

Python Library References

cryptography (pyca/cryptography)

PyCryptodome

workflows.md2.9 KB

Workflows - AES Encryption for Data at Rest

Workflow 1: Single File Encryption

[Input File] --> [Read File Bytes]
                      |
              [Derive Key from Password]
              (PBKDF2 / Argon2id + random salt)
                      |
              [Generate Random Nonce]
              (12 bytes from CSPRNG)
                      |
              [AES-256-GCM Encrypt]
              (key + nonce + plaintext --> ciphertext + tag)
                      |
              [Write Encrypted File]
              (salt || nonce || ciphertext || tag)

Workflow 2: Single File Decryption

[Encrypted File] --> [Parse Header]
                     (extract salt, nonce)
                          |
                  [Derive Key from Password]
                  (same PBKDF2 / Argon2id params + extracted salt)
                          |
                  [AES-256-GCM Decrypt]
                  (key + nonce + ciphertext + tag)
                          |
                  [Verify Authentication Tag]
                  (reject if tag invalid)
                          |
                  [Write Decrypted File]

Workflow 3: Streaming Encryption for Large Files

[Large Input File]
      |
[Read in Chunks] (e.g., 64KB chunks)
      |
[For Each Chunk]:
  - [Encrypt chunk with AES-256-CTR]
  - [Update HMAC with ciphertext chunk]
  - [Write encrypted chunk to output]
      |
[Finalize HMAC]
[Append HMAC tag to output]

Workflow 4: Directory Tree Encryption

[Source Directory]
      |
[Walk Directory Tree]
      |
[For Each File]:
  - [Derive unique file key from master key + file path]
  - [Generate random nonce]
  - [AES-256-GCM encrypt file]
  - [Write encrypted file preserving directory structure]
      |
[Create Manifest File]
(maps original paths to encrypted paths with metadata)

Workflow 5: Key Derivation Pipeline

[User Password]
      |
[Generate Random Salt] (16 bytes)
      |
[PBKDF2-SHA256]
  - iterations: 600,000+
  - dkLen: 32 bytes (256 bits)
      |
[Derived Key (256-bit)]
      |
[Optional: HKDF Expand]
  - Derive multiple subkeys from single derived key
  - info="encryption" --> encryption key
  - info="authentication" --> HMAC key

Workflow 6: Envelope Encryption Pattern

[Master Key] (stored in HSM/KMS)
      |
[Generate Random Data Encryption Key (DEK)]
(32 bytes from CSPRNG)
      |
[Encrypt DEK with Master Key] --> [Encrypted DEK]
      |
[Encrypt Data with DEK] --> [Ciphertext]
      |
[Store: Encrypted DEK + Ciphertext]
[Securely Wipe DEK from Memory]

Error Handling Workflow

[Decryption Attempt]
      |
  [Parse Header] --FAIL--> [Return: Corrupt/invalid file format]
      |
  [Derive Key] --FAIL--> [Return: KDF parameter error]
      |
  [Decrypt + Verify Tag]
      |
  [Tag Valid?]
    YES --> [Return plaintext]
    NO  --> [Return: Authentication failed - data tampered]
            [DO NOT return partial plaintext]

Scripts 2

agent.py5.3 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for implementing AES-256-GCM encryption for data at rest."""

import os
import json
import argparse
from datetime import datetime
from pathlib import Path

from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes


SALT_SIZE = 16
NONCE_SIZE = 12
KEY_SIZE = 32  # 256 bits
TAG_SIZE = 16
PBKDF2_ITERATIONS = 600_000


def derive_key(password, salt=None):
    """Derive AES-256 key from password using PBKDF2-HMAC-SHA256."""
    if salt is None:
        salt = os.urandom(SALT_SIZE)
    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=KEY_SIZE,
        salt=salt,
        iterations=PBKDF2_ITERATIONS,
    )
    key = kdf.derive(password.encode("utf-8"))
    return key, salt


def encrypt_file(input_path, output_path, password):
    """Encrypt a file using AES-256-GCM with PBKDF2 key derivation."""
    key, salt = derive_key(password)
    nonce = os.urandom(NONCE_SIZE)
    aesgcm = AESGCM(key)

    with open(input_path, "rb") as f:
        plaintext = f.read()

    ciphertext = aesgcm.encrypt(nonce, plaintext, None)

    with open(output_path, "wb") as f:
        f.write(salt)
        f.write(nonce)
        f.write(ciphertext)

    return {
        "input": str(input_path),
        "output": str(output_path),
        "original_size": len(plaintext),
        "encrypted_size": SALT_SIZE + NONCE_SIZE + len(ciphertext),
        "algorithm": "AES-256-GCM",
        "kdf": f"PBKDF2-HMAC-SHA256 ({PBKDF2_ITERATIONS} iterations)",
    }


def decrypt_file(input_path, output_path, password):
    """Decrypt an AES-256-GCM encrypted file."""
    with open(input_path, "rb") as f:
        salt = f.read(SALT_SIZE)
        nonce = f.read(NONCE_SIZE)
        ciphertext = f.read()

    key, _ = derive_key(password, salt)
    aesgcm = AESGCM(key)
    plaintext = aesgcm.decrypt(nonce, ciphertext, None)

    with open(output_path, "wb") as f:
        f.write(plaintext)

    return {
        "input": str(input_path),
        "output": str(output_path),
        "decrypted_size": len(plaintext),
    }


def encrypt_directory(dir_path, output_dir, password):
    """Encrypt all files in a directory tree."""
    src = Path(dir_path)
    dst = Path(output_dir)
    dst.mkdir(parents=True, exist_ok=True)
    results = []
    for filepath in src.rglob("*"):
        if filepath.is_file():
            rel = filepath.relative_to(src)
            out = dst / (str(rel) + ".enc")
            out.parent.mkdir(parents=True, exist_ok=True)
            result = encrypt_file(str(filepath), str(out), password)
            results.append(result)
    return results


def generate_random_key():
    """Generate a random AES-256 key."""
    key = os.urandom(KEY_SIZE)
    return {
        "key_hex": key.hex(),
        "key_size_bits": KEY_SIZE * 8,
        "algorithm": "AES-256",
    }


def verify_encryption(original_path, encrypted_path, password):
    """Verify encryption by decrypting and comparing."""
    with open(original_path, "rb") as f:
        original = f.read()

    with open(encrypted_path, "rb") as f:
        salt = f.read(SALT_SIZE)
        nonce = f.read(NONCE_SIZE)
        ciphertext = f.read()

    key, _ = derive_key(password, salt)
    aesgcm = AESGCM(key)
    try:
        decrypted = aesgcm.decrypt(nonce, ciphertext, None)
        match = original == decrypted
        return {"status": "PASS" if match else "FAIL", "content_match": match}
    except Exception as e:
        return {"status": "FAIL", "error": str(e)}


def main():
    parser = argparse.ArgumentParser(description="AES-256-GCM Encryption Agent")
    parser.add_argument("--action", required=True,
                        choices=["encrypt", "decrypt", "encrypt_dir", "genkey", "verify"])
    parser.add_argument("--input", help="Input file or directory")
    parser.add_argument("--output", help="Output file or directory")
    parser.add_argument("--password", help="Encryption password")
    parser.add_argument("--report", default="aes_encryption_report.json")
    args = parser.parse_args()

    report = {"generated_at": datetime.utcnow().isoformat(), "action": args.action}

    if args.action == "encrypt":
        result = encrypt_file(args.input, args.output, args.password)
        report["result"] = result
        print(f"[+] Encrypted: {args.input} -> {args.output}")

    elif args.action == "decrypt":
        result = decrypt_file(args.input, args.output, args.password)
        report["result"] = result
        print(f"[+] Decrypted: {args.input} -> {args.output}")

    elif args.action == "encrypt_dir":
        results = encrypt_directory(args.input, args.output, args.password)
        report["results"] = results
        print(f"[+] Encrypted {len(results)} files")

    elif args.action == "genkey":
        result = generate_random_key()
        report["result"] = result
        print(f"[+] Key: {result['key_hex']}")

    elif args.action == "verify":
        result = verify_encryption(args.input, args.output, args.password)
        report["result"] = result
        print(f"[+] Verification: {result['status']}")

    with open(args.report, "w") as f:
        json.dump(report, f, indent=2, default=str)
    print(f"[+] Report saved to {args.report}")


if __name__ == "__main__":
    main()
process.py11.4 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
AES-256-GCM Encryption for Data at Rest

Implements file and directory encryption using AES-256-GCM with
PBKDF2 key derivation. Supports single file, streaming large file,
and directory tree encryption.

Requirements:
    pip install cryptography

Usage:
    python process.py encrypt --input secret.pdf --output secret.pdf.enc --password "MySecurePass"
    python process.py decrypt --input secret.pdf.enc --output secret.pdf --password "MySecurePass"
    python process.py encrypt-dir --input ./sensitive/ --output ./encrypted/ --password "MySecurePass"
"""

import os
import sys
import json
import struct
import hashlib
import argparse
import logging
from pathlib import Path
from typing import Optional, Tuple

from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)

# Constants
SALT_LENGTH = 16          # 128-bit salt
NONCE_LENGTH = 12         # 96-bit nonce (recommended for GCM)
TAG_LENGTH = 16           # 128-bit authentication tag
KEY_LENGTH = 32           # 256-bit key
PBKDF2_ITERATIONS = 600_000  # OWASP 2024 recommendation
CHUNK_SIZE = 64 * 1024    # 64KB chunks for streaming
MAGIC_BYTES = b"AES256GCM"  # File format identifier
VERSION = 1


def derive_key(password: str, salt: bytes, iterations: int = PBKDF2_ITERATIONS) -> bytes:
    """Derive a 256-bit encryption key from a password using PBKDF2-SHA256."""
    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256(),
        length=KEY_LENGTH,
        salt=salt,
        iterations=iterations,
        backend=default_backend(),
    )
    return kdf.derive(password.encode("utf-8"))


def encrypt_bytes(plaintext: bytes, password: str) -> bytes:
    """
    Encrypt plaintext bytes using AES-256-GCM with PBKDF2 key derivation.

    Output format:
        MAGIC (9 bytes) || VERSION (1 byte) || SALT (16 bytes) || NONCE (12 bytes) || CIPHERTEXT+TAG (variable)

    The authentication tag is appended to ciphertext by AESGCM.
    """
    salt = os.urandom(SALT_LENGTH)
    nonce = os.urandom(NONCE_LENGTH)
    key = derive_key(password, salt)

    aesgcm = AESGCM(key)
    ciphertext = aesgcm.encrypt(nonce, plaintext, associated_data=None)

    header = MAGIC_BYTES + struct.pack("B", VERSION)
    return header + salt + nonce + ciphertext


def decrypt_bytes(data: bytes, password: str) -> bytes:
    """
    Decrypt AES-256-GCM encrypted data.

    Raises:
        ValueError: If file format is invalid or authentication fails.
    """
    magic_len = len(MAGIC_BYTES)
    min_length = magic_len + 1 + SALT_LENGTH + NONCE_LENGTH + TAG_LENGTH

    if len(data) < min_length:
        raise ValueError("Data too short to be a valid encrypted file")

    magic = data[:magic_len]
    if magic != MAGIC_BYTES:
        raise ValueError(f"Invalid file format: expected magic bytes {MAGIC_BYTES!r}, got {magic!r}")

    version = struct.unpack("B", data[magic_len : magic_len + 1])[0]
    if version != VERSION:
        raise ValueError(f"Unsupported version: {version}")

    offset = magic_len + 1
    salt = data[offset : offset + SALT_LENGTH]
    offset += SALT_LENGTH
    nonce = data[offset : offset + NONCE_LENGTH]
    offset += NONCE_LENGTH
    ciphertext = data[offset:]

    key = derive_key(password, salt)
    aesgcm = AESGCM(key)

    try:
        plaintext = aesgcm.decrypt(nonce, ciphertext, associated_data=None)
    except Exception as e:
        raise ValueError(
            "Decryption failed: authentication tag verification failed. "
            "Either the password is wrong or the data has been tampered with."
        ) from e

    return plaintext


def encrypt_file(input_path: str, output_path: str, password: str) -> dict:
    """Encrypt a single file."""
    input_file = Path(input_path)
    if not input_file.exists():
        raise FileNotFoundError(f"Input file not found: {input_path}")

    plaintext = input_file.read_bytes()
    original_size = len(plaintext)
    original_hash = hashlib.sha256(plaintext).hexdigest()

    ciphertext = encrypt_bytes(plaintext, password)

    output_file = Path(output_path)
    output_file.parent.mkdir(parents=True, exist_ok=True)
    output_file.write_bytes(ciphertext)

    encrypted_size = len(ciphertext)
    logger.info(f"Encrypted {input_path} -> {output_path} ({original_size} -> {encrypted_size} bytes)")

    return {
        "input": str(input_path),
        "output": str(output_path),
        "original_size": original_size,
        "encrypted_size": encrypted_size,
        "original_sha256": original_hash,
        "algorithm": "AES-256-GCM",
        "kdf": "PBKDF2-SHA256",
        "kdf_iterations": PBKDF2_ITERATIONS,
    }


def decrypt_file(input_path: str, output_path: str, password: str) -> dict:
    """Decrypt a single file."""
    input_file = Path(input_path)
    if not input_file.exists():
        raise FileNotFoundError(f"Input file not found: {input_path}")

    data = input_file.read_bytes()
    plaintext = decrypt_bytes(data, password)

    output_file = Path(output_path)
    output_file.parent.mkdir(parents=True, exist_ok=True)
    output_file.write_bytes(plaintext)

    recovered_hash = hashlib.sha256(plaintext).hexdigest()
    logger.info(f"Decrypted {input_path} -> {output_path} ({len(plaintext)} bytes)")

    return {
        "input": str(input_path),
        "output": str(output_path),
        "decrypted_size": len(plaintext),
        "recovered_sha256": recovered_hash,
    }


def encrypt_directory(input_dir: str, output_dir: str, password: str) -> dict:
    """Encrypt all files in a directory tree, preserving structure."""
    input_path = Path(input_dir)
    output_path = Path(output_dir)

    if not input_path.is_dir():
        raise NotADirectoryError(f"Input is not a directory: {input_dir}")

    output_path.mkdir(parents=True, exist_ok=True)

    manifest = {
        "algorithm": "AES-256-GCM",
        "kdf": "PBKDF2-SHA256",
        "kdf_iterations": PBKDF2_ITERATIONS,
        "files": [],
    }

    file_count = 0
    total_original = 0
    total_encrypted = 0

    for file in sorted(input_path.rglob("*")):
        if file.is_file():
            relative = file.relative_to(input_path)
            encrypted_name = str(relative) + ".enc"
            dest = output_path / encrypted_name

            result = encrypt_file(str(file), str(dest), password)
            manifest["files"].append({
                "original_path": str(relative),
                "encrypted_path": encrypted_name,
                "original_sha256": result["original_sha256"],
                "original_size": result["original_size"],
            })

            file_count += 1
            total_original += result["original_size"]
            total_encrypted += result["encrypted_size"]

    manifest_path = output_path / "manifest.json"
    manifest_encrypted = encrypt_bytes(json.dumps(manifest, indent=2).encode(), password)
    (output_path / "manifest.json.enc").write_bytes(manifest_encrypted)

    logger.info(
        f"Encrypted {file_count} files from {input_dir} -> {output_dir} "
        f"({total_original} -> {total_encrypted} bytes)"
    )

    return {
        "files_encrypted": file_count,
        "total_original_bytes": total_original,
        "total_encrypted_bytes": total_encrypted,
        "output_directory": str(output_path),
    }


def decrypt_directory(input_dir: str, output_dir: str, password: str) -> dict:
    """Decrypt all .enc files in a directory tree."""
    input_path = Path(input_dir)
    output_path = Path(output_dir)

    if not input_path.is_dir():
        raise NotADirectoryError(f"Input is not a directory: {input_dir}")

    output_path.mkdir(parents=True, exist_ok=True)

    file_count = 0
    total_decrypted = 0

    for file in sorted(input_path.rglob("*.enc")):
        if file.name == "manifest.json.enc":
            continue
        if file.is_file():
            relative = file.relative_to(input_path)
            decrypted_name = str(relative).removesuffix(".enc")
            dest = output_path / decrypted_name

            result = decrypt_file(str(file), str(dest), password)
            file_count += 1
            total_decrypted += result["decrypted_size"]

    logger.info(f"Decrypted {file_count} files from {input_dir} -> {output_dir}")

    return {
        "files_decrypted": file_count,
        "total_decrypted_bytes": total_decrypted,
        "output_directory": str(output_path),
    }


def verify_roundtrip(test_data: bytes, password: str) -> bool:
    """Verify encryption/decryption roundtrip integrity."""
    encrypted = encrypt_bytes(test_data, password)
    decrypted = decrypt_bytes(encrypted, password)
    return decrypted == test_data


def main():
    parser = argparse.ArgumentParser(description="AES-256-GCM File Encryption Tool")
    subparsers = parser.add_subparsers(dest="command", help="Command to execute")

    # Encrypt command
    enc = subparsers.add_parser("encrypt", help="Encrypt a file")
    enc.add_argument("--input", "-i", required=True, help="Input file path")
    enc.add_argument("--output", "-o", required=True, help="Output file path")
    enc.add_argument("--password", "-p", required=True, help="Encryption password")

    # Decrypt command
    dec = subparsers.add_parser("decrypt", help="Decrypt a file")
    dec.add_argument("--input", "-i", required=True, help="Input file path")
    dec.add_argument("--output", "-o", required=True, help="Output file path")
    dec.add_argument("--password", "-p", required=True, help="Decryption password")

    # Encrypt directory command
    encdir = subparsers.add_parser("encrypt-dir", help="Encrypt a directory")
    encdir.add_argument("--input", "-i", required=True, help="Input directory path")
    encdir.add_argument("--output", "-o", required=True, help="Output directory path")
    encdir.add_argument("--password", "-p", required=True, help="Encryption password")

    # Decrypt directory command
    decdir = subparsers.add_parser("decrypt-dir", help="Decrypt a directory")
    decdir.add_argument("--input", "-i", required=True, help="Input directory path")
    decdir.add_argument("--output", "-o", required=True, help="Output directory path")
    decdir.add_argument("--password", "-p", required=True, help="Decryption password")

    # Verify command
    subparsers.add_parser("verify", help="Run roundtrip verification test")

    args = parser.parse_args()

    if args.command == "encrypt":
        result = encrypt_file(args.input, args.output, args.password)
        print(json.dumps(result, indent=2))
    elif args.command == "decrypt":
        result = decrypt_file(args.input, args.output, args.password)
        print(json.dumps(result, indent=2))
    elif args.command == "encrypt-dir":
        result = encrypt_directory(args.input, args.output, args.password)
        print(json.dumps(result, indent=2))
    elif args.command == "decrypt-dir":
        result = decrypt_directory(args.input, args.output, args.password)
        print(json.dumps(result, indent=2))
    elif args.command == "verify":
        test_data = b"The quick brown fox jumps over the lazy dog. " * 100
        password = "test_password_123!"
        success = verify_roundtrip(test_data, password)
        print(f"Roundtrip verification: {'PASSED' if success else 'FAILED'}")
        if not success:
            sys.exit(1)
    else:
        parser.print_help()


if __name__ == "__main__":
    main()

Assets 1

template.mdtext/markdown · 2.9 KB
Keep exploring