DevelopmentNovember 03, 20254 min read

File Encryption Tool using Python

Encrypt and decrypt any file with Python using password-based AES encryption — a command-line tool with salt, key derivation, and safe practices.

Galvan

Galvan

Founder & Creator

Introduction

File encryption has a reputation for being arcane. In practice, modern cryptography libraries compress it to a disciplined recipe: derive a key from a password, encrypt with AES, and store the salt and nonce alongside the ciphertext. This tutorial builds a command-line tool that does exactly that — encrypt file.pdf asks for a password, and the file becomes unreadable without it.

The security lesson matters as much as the code: we use PBKDF2 key derivation with a random salt, a fresh nonce per file, and authenticated encryption — each choice defends against a specific attack. This is the security-serious sibling of the password generator.

Features

  • AES-256 encryption — the modern standard, via the cryptography library.
  • Password-based keys — PBKDF2 with 480,000 iterations.
  • Random salt + nonce — same password, different ciphertext every time.
  • Tamper detection — AESGCM authentication rejects modified files.
  • Any file type — binary-safe: PDFs, images, archives, anything.
  • Prerequisites

  • Python 3.8+ — from python.org.
  • Dependencies:
  • code
    pip install cryptography

    Step 1: Create the Script

    Save as cryptool.py:

    code
    import os
    import sys
    import getpass
    from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
    from cryptography.hazmat.primitives import hashes
    from cryptography.hazmat.primitives.ciphers.aead import AESGCM
    
    SALT_SIZE = 16
    NONCE_SIZE = 12
    ITERATIONS = 480_000
    MAGIC = b"ENC1"
    
    
    def derive_key(password: str, salt: bytes) -> bytes:
        kdf = PBKDF2HMAC(
            algorithm=hashes.SHA256(),
            length=32,
            salt=salt,
            iterations=ITERATIONS,
        )
        return kdf.derive(password.encode())
    
    
    def encrypt_file(path: str, password: str):
        with open(path, "rb") as f:
            plaintext = f.read()
    
        salt = os.urandom(SALT_SIZE)
        nonce = os.urandom(NONCE_SIZE)
        key = derive_key(password, salt)
    
        ciphertext = AESGCM(key).encrypt(nonce, plaintext, None)
    
        with open(path + ".enc", "wb") as f:
            f.write(MAGIC + salt + nonce + ciphertext)
        print(f"Encrypted -> {path}.enc")
    
    
    def decrypt_file(path: str, password: str):
        with open(path, "rb") as f:
            blob = f.read()
    
        if not blob.startswith(MAGIC):
            sys.exit("Not an encrypted file (missing header).")
    
        salt = blob[4:4 + SALT_SIZE]
        nonce = blob[4 + SALT_SIZE:4 + SALT_SIZE + NONCE_SIZE]
        ciphertext = blob[4 + SALT_SIZE + NONCE_SIZE:]
    
        key = derive_key(password, salt)
        try:
            plaintext = AESGCM(key).decrypt(nonce, ciphertext, None)
        except Exception:
            sys.exit("Wrong password or corrupted file.")
    
        out_path = path.replace(".enc", "") + ".dec"
        with open(out_path, "wb") as f:
            f.write(plaintext)
        print(f"Decrypted -> {out_path}")
    
    
    if __name__ == "__main__":
        if len(sys.argv) != 3 or sys.argv[1] not in ("encrypt", "decrypt"):
            sys.exit(f"Usage: python {sys.argv[0]} encrypt|decrypt <file>")
        action, path = sys.argv[1], sys.argv[2]
        password = getpass.getpass("Password: ")
        (encrypt_file if action == "encrypt" else decrypt_file)(path, password)

    Step 2: Run the Tool

    code
    python cryptool.py encrypt report.pdf
    python cryptool.py decrypt report.pdf.enc

    Try decrypting with the wrong password — you get a clean rejection, not garbage output. That is the authenticated encryption working.

    How It Works

    Never encrypt with a password directly — passwords have too little entropy. PBKDF2 stretches the password into a 256-bit key by hashing it 480,000 times with a random salt, making brute-force guessing expensive for attackers while costing you one honest second. The salt guarantees the same password produces different keys on different files, defeating precomputed rainbow tables.

    AESGCM is authenticated encryption: it encrypts *and* produces an authentication tag. Decryption with a wrong password (wrong key) fails the tag check instead of returning garbage — you learn immediately that the password was wrong, rather than silently using a corrupted file. The nonce must be unique per encryption; os.urandom handles that, and reusing a nonce with the same key is the one catastrophic mistake in GCM.

    The file format is deliberately simple: MAGIC + salt + nonce + ciphertext. The magic bytes let the tool recognize its own files; salt and nonce ride in the open — both are designed to be public. Only the ciphertext is secret.

    Common Errors & Fixes

  • `InvalidTag` on decrypt with the right password — the file was modified after encryption (even one byte), or you truncated it during transfer; authentication is doing its job.
  • Slow encryption of large files — AESGCM loads the whole file into RAM here; stream in chunks with ChunkedEOF handling, or accept the memory cost for files under a few hundred MB.
  • `getpass` hides typing entirely — that's correct behavior; the password doesn't echo even for asterisks.
  • Forgot the password — there is no recovery. That is the entire point; keep a password manager entry.
  • Key Concepts

  • Key derivation — passwords to keys via PBKDF2, with salt and iterations.
  • Authenticated encryption — AESGCM detects tampering, not just hides data.
  • Nonce discipline — unique per encryption, public in the file.
  • Header design — magic bytes + public parameters + ciphertext.
  • What to Try Next

  • Add folder mode — walk a directory and encrypt every file with one password.
  • Add a secure delete — overwrite the original with random bytes before removing it.
  • Wrap it in a Tkinter GUI — the paint app's window patterns with a file dialog.
  • Switch to Argon2id (argon2-cffi) — the newer key-derivation standard.
  • FAQ

    Is this actually secure?

    The primitives are the same ones protecting HTTPS and password managers. Real-world failures come from weak passwords and leaked keys — use a strong passphrase.

    Why not just ZIP with a password?

    Classic ZIP encryption is weak or proprietary depending on the variant. AES-encrypted ZIPs exist but depend on the recipient's tooling — this format is under your control.

    Where should I store the salt and nonce?

    In the file, exactly as done — both are designed to be public. Never reuse a salt across files, and never reuse a nonce with the same derived key.