Secure local encryption and decryption of sensitive data using AES-256-GCM with web and CLI interfaces.
- Overview
- Tech Stack
- Architecture
- Project Structure
- Getting Started
- Running the App
- Available Scripts
- Key Features
- Testing
- Security
- Contributing
- License
What it does — encrypt_decrypt is a local-first tool for encrypting and decrypting sensitive text and files. It provides a Flask web UI and a CLI for automation. In web mode, the Flask server performs cryptographic operations; run it on localhost or behind trusted HTTPS only.
Why it exists — to offer a simple, auditable way to protect secrets (passwords, API keys, config snippets) using strong, standards-based cryptography. Suitable for personal use, scripts, and small teams.
Current status — local-first and security-hardened. Web and CLI interfaces implemented. Test suite covers crypto, validation, file handling, routes, and security headers. CI runs on Python 3.10–3.12.
| Layer | Technology |
|---|---|
| Language | Python 3.10+ |
| Web | Flask 3.1, Flask-WTF (CSRF), Flask-Limiter |
| Cryptography | cryptography 46.x (AES-256-GCM, PBKDF2) |
| CLI | argparse, Click (via setup.py entry) |
| Config | python-dotenv, config classes |
| Testing | pytest 7.4 |
| Linting | Ruff (pyproject.toml) |
| Package | setuptools, pip |
The app exposes two interfaces (web and CLI) that share the same crypto and file utilities. Encryption uses AES-256-GCM with PBKDF2-SHA256 key derivation. Output is a JSON envelope with salt, IV, ciphertext, and metadata.
flowchart LR
subgraph Input
Web["Web UI\n(Flask)"]
CLI["CLI\n(cli.py)"]
end
subgraph Core
Routes["routes.py"]
Crypto["crypto.py\nAES-256-GCM\nPBKDF2"]
File["file.py"]
end
Web --> Routes
CLI --> Crypto
CLI --> File
Routes --> Crypto
Routes --> File
sequenceDiagram
participant User
participant App
participant Crypto
User->>App: plaintext + password
App->>Crypto: encrypt(data, password, iterations)
Crypto->>Crypto: generate salt, IV
Crypto->>Crypto: PBKDF2(password, salt) → key
Crypto->>Crypto: AES-GCM.encrypt(iv, plaintext)
Crypto-->>App: { salt, iv, encrypted, metadata }
App-->>User: JSON or file
encrypt_decrypt/
├── app/
│ ├── __init__.py # Flask app factory, security headers
│ ├── extensions.py # CSRF, rate limiter
│ ├── routes.py # Web routes (encrypt, decrypt)
│ ├── utils/
│ │ ├── crypto.py # AES-256-GCM, PBKDF2
│ │ ├── file.py # Load/save encrypted JSON
│ │ ├── validation.py # Input validation
│ │ └── errors.py # Error handling
│ ├── static/ # CSS, JS
│ └── templates/ # Jinja2 (index.html)
├── tests/
│ ├── conftest.py # Pytest fixtures
│ ├── test_routes.py # Route integration tests
│ └── unit/ # Unit tests (crypto, file, validation)
├── scripts/ # Utility scripts (e.g. setup.sh)
├── config.py # Config classes (dev, test, prod)
├── run.py # Web entry point
├── cli.py # CLI entry point
├── setup.py # Package install, encrypt-decrypt entry
├── Makefile # setup, run, test, clean, install
├── pyproject.toml # Ruff, pytest config
├── requirements.txt # Direct dependencies
└── requirements.lock # Resolved dependency snapshot
- Python >= 3.10
- pip (or
make setupfor venv)
git clone https://github.com/ErikKopcha/crypto-vault.git
cd crypto-vault
python3 -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txtOr use the setup script (creates venv, installs deps, copies .env.example to .env):
./scripts/setup.shOptional — install as a package (provides encrypt-decrypt command):
pip install -e .| Variable | Description | Required |
|---|---|---|
FLASK_ENV |
development, testing, production |
optional (default: development) |
SECRET_KEY |
Flask secret, at least 32 bytes | ✅ (prod) |
RATELIMIT_STORAGE_URI |
Persistent Flask-Limiter storage URI | ✅ (prod) |
HOST |
Bind host (default: 0.0.0.0) |
optional |
PORT |
Bind port (default: 5000) |
optional |
Security note: In production, set
SECRET_KEYto a strong random value:openssl rand -base64 32
# Web (development)
PORT=5001 venv/bin/python run.py
# or
PORT=5001 make runOpen http://localhost:5001 in your browser.
On macOS, port 5000 is often used by AirPlay Receiver. Use PORT=5001 unless
you have explicitly freed port 5000.
# CLI
python cli.py encrypt "Your secret message" "your-password"
python cli.py decrypt encrypted/encrypted_2024-05-23_15-30-12.json "your-password"
# With secure password prompt
python cli.py encrypt "Your secret" --prompt
python cli.py decrypt encrypted/file.json --prompt
# If installed as package
encrypt-decrypt encrypt "Your secret" "your-password"
encrypt-decrypt decrypt encrypted/file.json "your-password"| Command | Description |
|---|---|
make setup |
Create venv and install dependencies |
make run |
Start Flask dev server |
make test |
Run pytest with verbose output |
make lint |
Run Ruff linter |
make clean |
Remove __pycache__ and .pyc files |
make install |
Install package in development mode |
make encrypt data="..." password="..." |
CLI encrypt via Makefile |
make decrypt file="..." password="..." |
CLI decrypt via Makefile |
- AES-256-GCM — authenticated encryption
- PBKDF2-SHA256 — key derivation (default 600,000 iterations, configurable 100K–2M)
- Web UI — encrypt/decrypt via browser, paste or upload
- CLI — scriptable, supports
--promptfor secure password input - Local-first — use the web UI on localhost or trusted HTTPS; CLI never sends data over the network
- JSON envelope — portable format with salt, IV, metadata
FLASK_ENV=testing pytest tests/ -v
# or
make testTests cover:
- Unit: crypto, file I/O, validation, error handling
- Integration: web routes (encrypt, decrypt)
CI runs on Python 3.10, 3.11, 3.12.
- AES-256-GCM — authenticated encryption for confidentiality and integrity
- PBKDF2-HMAC-SHA256 — key derivation (100,000–2,000,000 iterations, default 600K)
- Random salt and IV per encryption operation
- Iterations bounds enforced on decrypt to prevent PBKDF2 DoS
- CSRF protection — Flask-WTF on all POST forms
- Security headers — X-Content-Type-Options, X-Frame-Options, CSP, Referrer-Policy, Permissions-Policy, HSTS in production
- Input limits — 1 MB for data/encrypted JSON, 256 chars for password
- Rate limiting — 120 requests/minute per IP (Flask-Limiter); persistent storage required in production
- File upload limit — 10 MB
- No logging of plaintext or passwords
- Strong
SECRET_KEYrequired in production - Persistent
RATELIMIT_STORAGE_URIrequired in production requirements.lockcommitted for reproducible dependency review.env.examplefor configuration template
This tool is for legitimate security use. Use encryption responsibly and in compliance with applicable laws.
MIT © 2026 Erik K.