Image Encryption System
Live site: https://sebby1770.github.io/image-encryption-system/
A Flask image vault that encrypts photos with AES-256-GCM before they touch
disk. Per-image data keys are wrapped with Scrypt+AES or RSA-OAEP.
End-to-end mode (the default) encrypts in your browser: metadata is stripped,
the image sealed, shared, linked, and decrypted in the page, and the server only
ever stores ciphertext and key wraps it cannot open. Server mode is still there
for TIFF and for clients without JavaScript.
Features
- End-to-end encryption in the browser. WebCrypto AES-256-GCM, a pure
JavaScript Scrypt in a Web Worker, and RSA-OAEP, producing the same
.ies
envelope the server and CLI read. Upload, view, share, capability links, and
passphrase rotation all run in the page; the server refuses to unwrap an
end-to-end key even when handed the right secret.
- Metadata stripped before encryption, in the browser too: EXIF and GPS,
XMP, IPTC, MPF, PNG text and C2PA chunks, JPEG thumbnails, comments, and data
appended after the image, with every pixel kept.
- End-to-end links keep their key in the URL fragment, which browsers never
send; the server counts downloads but cannot open what it serves.
- Re-key an end-to-end image to make revocation stick: it is re-encrypted in
the browser under a new key, so a key a former recipient kept no longer opens
what the server stores.
- Batch and drag-and-drop uploads in end-to-end mode, and a dark mode
that follows your system setting.
- Key fingerprints for comparing a recipient's key out of band before
trusting a share.
- Account private keys sealed with Scrypt + AES-GCM, not OpenSSL's 2,048-round
PBKDF2, and written
0600. Older PEM keys keep working and are resealed at
the next sign-in.
- AES-256-GCM encryption for PNG, JPEG, WEBP, GIF, BMP, and TIFF images, with a
fresh 256-bit data key per image.
- Data keys wrapped with Scrypt + AES-GCM (passphrase mode) or RSA-OAEP-SHA256
(hybrid mode, 3072-bit per-user key pair generated at registration; the
private key is sealed with the account password).
- Share with another username by re-wrapping the same data key with their
RSA public key. Recipients decrypt with their password. Revoke at any time,
with optional expiry (
expires_hours / expires_days).
- Capability links (
/l/<token>) for people without accounts, with optional
expiry and download cap. Only the SHA-256 of the token is stored.
- Rotate the passphrase wrap of an image without rewriting its ciphertext.
- Change password: new hash, RSA private key re-encrypted,
token_version
bumped so other sessions and JWTs stop working.
- Delete account (
POST /account/delete) with password confirmation.
- Envelope version 3 seals the image's owner, asset id, algorithm, wrap type,
MIME type, format, and dimensions into the AES-GCM AAD, so ciphertext cannot
be swapped between records or accounts. Version 1 files stay decryptable.
- Capture metadata is stripped before encryption: EXIF (including GPS), XMP,
IPTC, PNG text chunks, and JPEG/GIF comments.
- Uploads are identified from their header and bounded (format allow-list,
64 MP pixel ceiling across all frames, 8 MB byte limit) before anything
decodes them.
- Decrypted images are served only as the allow-listed type they parse as,
with
no-store, nosniff, and a sandboxing CSP.
- Ciphertext SHA-256 recorded at save time and checked before decrypt.
- CSRF tokens on every HTML POST form; strict CSP (no inline script).
- Server-side session registry: logout and password changes really end
sessions; 30-minute idle and 7-day absolute timeouts.
- Login rate limit (5 / 10 minutes per IP+username) and lockout after 8
failures, persisted in SQLite.
- Throttles beyond the login form: registration per address (each one mints an
RSA-3072 key pair, so it is a CPU amplifier reachable without an account),
decrypt attempts per account, and capability-link requests per address.
- Password policy enforced in storage, so registration, rotation, and the CLI
all clear the same bar; a register-page meter mirrors the same rules.
- Scrypt at
n=2^16 for new passphrase wrappings. The accepted floor (2^14)
is a separate constant, so older files stay readable.
- API tokens carry and require an audience, alongside
exp, iat, iss,
sub, and ver.
- Permissions-Policy, COOP, CORP, and HSTS (on HTTPS requests) alongside the
CSP;
GET /healthz for probes; a non-root Docker image.
- Owner-only audit log, HMAC-chained and verified on every view (web, CSV
export, and
GET /api/audit).
- Encrypted backup zip (ciphertext + metadata, never private keys) and restore.
- Rename, notes, and favorites on vault items.
- JWT API for listing images, sharing, links, and the audit trail.
ies CLI for offline encrypt / decrypt / keygen / inspect / verify / rewrap / hash.
Stack
Python 3.10+, Flask, cryptography (OpenSSL), Pillow, SQLite, PyJWT. PyCrypto is
intentionally not used because it is unmaintained.
Quick Start
git clone https://github.com/Sebby1770/image-encryption-system.git
cd image-encryption-system
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
python run.py
Open http://127.0.0.1:5000, create an account, and upload an image.
CLI
ies encrypt IN.png --passphrase 'a long secret' --out out.bin
ies decrypt out.bin --passphrase 'a long secret' --out restored.png
ies inspect out.bin
ies verify out.bin --passphrase 'a long secret'
ies hash out.bin
ies rewrap out.bin --old-passphrase 'a long secret' --new-passphrase 'rotated' --out rotated.ies
ies keygen --passphrase 'account password' --out-private key.key --out-public pub.pem
ies encrypt IN.png --public-key pub.pem --out photo.ies
ies decrypt photo.ies --private-key key.key --passphrase 'account password' --out restored.png
여기까지가 README 앞부분입니다. 나머지는 원문에서 읽을 수 있습니다. README 전문 보기 ↗