DEV Community

Sasiru Mindaka
Sasiru Mindaka

Posted on AI-assisted

Building SecretGate: A Zero-Knowledge Ephemeral Messaging Engine with Native PHP & Web Crypto API

Hey DEV Community! 👋

I recently launched SecretGate—a self-hostable, zero-knowledge ephemeral messaging platform designed to eliminate server-side trust and raw data exposure.

I wanted to build an alternative to traditional anonymous/ephemeral messaging tools (like NGL or Privnote) that actually guarantees privacy through math rather than a "trust us" policy.

Here is a breakdown of the technical decisions, architecture, and security model behind the project.


💡 Why Pure Native PHP & Zero Frameworks?

In a world dominated by massive node_modules trees and heavy framework dependencies, I intentionally built SecretGate using pure native PHP 8+.

  • Zero Supply Chain Risk: No Composer dependencies to keep audited or updated.
  • Minimal Audit Surface: Around 10 PHP files in total that any developer can read top-to-bottom in under an hour.
  • Blazing Fast Performance: Runs seamlessly on standard LAMP/LEMP stacks with minimal resource footprint.

🔐 How Zero-Knowledge Encryption Works

SecretGate uses Hybrid Client-Side Encryption via the W3C Web Crypto API (window.crypto.subtle). The server acts purely as a dumb ciphertext store.

1. Link Creation (Recipient)

  • The browser generates an RSA-OAEP (2048-bit) keypair.
  • The private key is wrapped using an AES-256-GCM key derived from a user password via PBKDF2 (300,000 iterations).
  • The encrypted private key is stored locally in the browser's IndexedDB vault and never transmitted to the server.
  • Only the RSA Public Key (JWK) and an Argon2id password hash are sent to the server.

2. Message Transmission (Sender)

  • When a sender visits a link, their browser fetches the recipient's public key.
  • A single-use AES-256-GCM symmetric key encrypts the payload.
  • The AES key is then encrypted with the recipient's RSA public key.
  • Both ciphertexts are POSTed to the backend. The server stores data it mathematically cannot read.

3. Message Decryption (Recipient)

  • The link owner unlocks their IndexedDB vault locally using their password.
  • Decryption of the private key and subsequent message decryption happens entirely on-device inside the browser.

🛡️ Infrastructure & Backend Hardening

Even though encryption happens client-side, the backend stack is fully hardened against abuse:

  • Database Safety: 100% prepared statements via PDO MySQL (zero raw SQL concatenation).
  • Session & CSRF Security: Per-session CSRF tokens, strict origin matching, and HttpOnly/SameSite=Lax cookie flags.
  • Abuse Prevention: Redis-backed sliding-window rate limiting (auto-falls back to file-based storage if Redis is offline) and temporary IP burning.
  • Execution Isolation: Apache .htaccess rules restrict PHP execution strictly to entry points (index.php, api.php), blocking direct access to .env, .sql, and backend scripts.

🚀 Live Demo & Open Source

SecretGate is completely 100% Open Source under the AGPL-3.0 License.


💬 Feedback & Discussion

I’d love to get feedback from the DEV community!

How do you handle client-side cryptographic key storage in your projects? Any security improvements you'd suggest for the pipeline?

If you find SecretGate interesting or useful for self-hosting, dropping a ⭐ on the GitHub Repo would mean a lot!

Top comments (0)