Skip to content

Encryption & Security

Your files are encrypted before they reach cloud storage — not just in transit.

Most cloud storage services encrypt the connection (HTTPS) but store your files in plaintext on their servers. Lenzeye goes further: files are encrypted at rest, with a unique encryption key per user, before they are written to cloud storage.


Encryption at a Glance

Property Value
Algorithm AES-256-CTR (Counter Mode)
Key size 256-bit (32 bytes) per user
IV (Initialization Vector) 128-bit, randomly generated per file
Integrity HMAC-SHA256 (Encrypt-then-MAC)
Key storage Per-user key encrypted with AES-256-GCM master key
Master key location Environment variable — never in code or database

How Encryption Works

flowchart TD
    A[File chunk sent\nto server] --> B[AES-256-CTR\nencryption applied]
    B --> C[Encrypted chunk\nuploaded to Wasabi S3]
    C --> D[HMAC tag\naccumulated]
    D --> E[HMAC stored\nin S3 metadata]
    E --> F[File fully encrypted\nat rest on cloud]
  1. Each file gets a unique IV — a 128-bit random value generated at upload start. This ensures two identical files produce different ciphertext.
  2. AES-256-CTR encrypts each chunk — the stream cipher processes each 10 MB chunk at its correct byte offset, independently.
  3. HMAC-SHA256 is computed — an integrity tag is accumulated chunk-by-chunk during upload, then stored in S3 object metadata.
  4. On download, HMAC is verified first — if the file was tampered with in storage, the HMAC check fails and the download is rejected before a single decrypted byte is returned.

Why AES-256-CTR?

AES-CTR (Counter Mode) is chosen specifically because it is seekable:

  • Any byte position in the encrypted file can be decrypted independently
  • This allows each upload chunk to be encrypted at its correct offset without sequential dependency
  • Decryption can be streamed — the entire file never needs to be in memory at once

This is the same approach used by TLS, SSH, and most modern encrypted file systems.


Key Management

Lenzeye uses a two-tier key architecture:

flowchart LR
    A[Master Key\nenv variable] --> B[Wraps per-user key\nwith AES-256-GCM]
    B --> C[Encrypted user key\nstored in database]
    C --> D[Decrypted at upload/download\ntime only, in memory]
  • Master key — A single 256-bit application key stored as an environment variable on the server. Never in code, never in the database.
  • Per-user key — Each user gets a unique 256-bit key, which is itself encrypted with the master key before being stored in the database.
  • Key versioning — Old key versions are retained so previously encrypted files remain decryptable even after a key rotation.

This means: even if someone gained read access to the database, they would only find encrypted user keys — which are useless without the master key.


Integrity Verification (HMAC-SHA256)

Lenzeye uses Encrypt-then-MAC — the industry standard approach:

  1. Data is encrypted first
  2. HMAC is computed over the ciphertext (not plaintext)
  3. HMAC is stored in S3 metadata
  4. On download, HMAC is verified before decryption begins
  5. If verification fails → InvalidSignature exception → download rejected

This detects any tampering with the stored ciphertext — whether accidental (storage corruption) or deliberate (an attacker modifying bytes in S3).


What Is NOT Encrypted

For transparency:

  • File names — stored as S3 object keys (e.g., user@email.com/folder/filename.jpg). File names are visible in S3 metadata.
  • Upload guidelines — plain text, no encryption needed.
  • User account data — stored in PostgreSQL, protected by database-level access controls.

Security in Transit

All communication between browser, Lenzeye server, and Wasabi S3 uses HTTPS/TLS. Presigned URLs are time-limited (expiry enforced by S3) and single-use in practice.