crypto-secure
v1.2.2
Published
Hybrid encryption (RSA + AES-GCM) with Express middleware for automatic request/response encryption
Maintainers
Readme
crypto-secure
Hybrid encryption library (RSA-2048 + AES-256-GCM) with Express middleware and browser client for automatic end-to-end encrypted communication.
Drop it into your Express backend + React/Vue/any frontend and get end-to-end encryption with 4 lines of code — no crypto expertise required.
Why crypto-secure? (The Problem It Solves)
In today's era of mass surveillance, data breaches, and API sniffing, HTTPS alone is not enough:
| Threat | HTTPS Protection | crypto-secure Protection | |---|---|---| | TLS termination at load balancer | ❌ — plaintext inside your network | ✅ — end-to-end encrypted | | Cloud provider / hosting access | ❌ — can read your data | ✅ — only your keys can decrypt | | Man-in-the-middle at proxy layer | ❌ — TLS is terminated | ✅ — RSA + AES-GCM envelope | | Database leak (server-side) | ❌ — logged after decryption | ✅ — never stored in plaintext | | Insider access to server memory | ❌ — plaintext in process | ✅ — encrypted until handled |
What makes it unique: Unlike libraries that just provide raw crypto functions, crypto-secure gives you drop-in middleware that makes every request and response automatically encrypted — your route handlers don't change at all.
Features
- 🔐 Hybrid encryption — RSA-2048 OAEP SHA-256 wraps AES-256 keys, AES-256-GCM encrypts payloads. Best of both worlds: fast symmetric encryption with convenient asymmetric key exchange.
- ✅ Authenticated encryption (AEAD) — AES-GCM prevents tampering, padding oracle attacks, and chosen-ciphertext attacks (unlike AES-CBC).
- 📦 Payload compression — Plaintext is gzip-compressed before encryption, reducing wire size by 50–85% for typical API payloads without any security tradeoff.
- 🔄 Express middleware —
cs.middleware({ privateKey })auto-decrypts incomingreq.bodyand auto-encrypts outgoingres.json(). Zero changes to your route handlers. - 🌐 Browser client — Full Web Crypto API implementation. Works with React, Vue, Angular, vanilla JS. No
node-forgeneeded on the client. - 🪝 Axios interceptors — Built-in request/response interceptors for transparent encryption. Set once, forget about it.
- 🔑 Fresh keys per message — Every encryption generates a new random AES-256 key and 12-byte IV. Perfect forward secrecy at the message level.
- 📎 AAD (Additional Authenticated Data) — Bind context metadata (user ID, API version, timestamp) to the ciphertext so it can't be replayed in a different context.
- 💾 Key persistence —
generateKeyPair("./keys")writes.pemfiles to disk automatically. - 🍪 Cookie-based key caching — Browser client caches the server's public key in a
SameSite=Laxcookie for 24 hours, avoiding re-fetch on page reload. - 🔒 Route-level encryption — Simple base64 route/value encryption for URL params, tokens, and short strings.
- 📦 Zero extra dependencies in Node — Only
node-forgeat runtime; compression uses Node's built-inzlib. Browsers use nativeCompressionStreamAPI. - 🧩 Framework-agnostic frontend — ES Module export works with any bundler (Vite, Webpack, esbuild, etc.).
Comparison: Traditional vs crypto-secure
| Aspect | Traditional Approach | crypto-secure |
|---|---|---|
| Setup time | Days (select algos, implement key exchange, write middleware) | Minutes |
| Lines of code | 30–100+ per project | 4 lines (backend) / 6 lines (frontend) |
| Encryption type | Manual per-endpoint | Automatic via middleware |
| Algorithm | AES-CBC (vulnerable) or raw RSA (slow) | AES-256-GCM (authenticated, fast) |
| Key exchange | Hardcoded keys or complex PKI | RSA-2048 OAEP SHA-256 |
| Tamper detection | None (CBC) | Built-in (GCM authentication tag) |
| Wire size | Full plaintext + padding | Compressed + encrypted (50–85% smaller for typical payloads) |
| Frontend support | Usually none provided | Web Crypto API (React, Vue, etc.) |
| Key storage | Manual file management | Auto-save .pem or in-memory |
| Security rating | ~6/10 (CBC, no auth, weak MGF) | ~9/10 (GCM, OAEP SHA-256, AAD) |
How It Works (Protocol)
Wire Format
Every encrypted payload on the wire uses this JSON envelope:
{
"encryptedAESKey": "<base64 RSA-wrapped AES-256 key>",
"delta": "<base64 12-byte IV>",
"tag": "<base64 16-byte GCM authentication tag>",
"encryptedData": "<base64 AES-GCM ciphertext>",
"aad": "<base64 additional authenticated data (optional)>"
}Encryption Flow
Plaintext (any JSON)
│
▼
JSON.stringify()
│
▼
gzip compress (NEW — reduces wire size 50–85%)
│
▼
AES-256-GCM encrypt with random key + IV
│
├── ciphertext (→ encryptedData)
└── auth tag (→ tag)
│
AES key → RSA-2048 OAEP encrypt with recipient's public key (→ encryptedAESKey)
│
▼
Base64-encode all binary fields → JSON envelopeDecryption Flow
JSON envelope
│
▼
Base64-decode all fields
│
▼
RSA-2048 OAEP decrypt → recover AES key
│
▼
AES-256-GCM decrypt + verify authentication tag
│
▼
gunzip decompress
│
▼
JSON.parse → original payloadKey Exchange Flow
Client (Browser) Server (Express)
┌─────────────────┐ ┌────────────────────────┐
│ generateKeyPair()│ │ generateKeyPair() │
│ → client keys │ │ → server keys │
└────────┬────────┘ └───────────┬────────────┘
│ │
│ GET /.well-known/encryption-key │
│ ←────────────────────────────────── publicKey
│ │
│ POST /api/data │
│ Header: x-encryption-key (← client pub) │
│ Body: encrypted envelope │
│ ─────────────────────────────────────────→ │
│ │ decrypt with
│ │ server privateKey
│ │ → req.body
│ │
│ Response: encrypted envelope │
│ ←───────────────────────────────────────── │ encrypt with
│ │ client publicKey
│ decrypt with │
│ client privateKey │
│ → response.data │Backend Usage (Express)
Install
npm install crypto-secureMinimum Setup (4 lines)
const cs = require("crypto-secure");
const keypair = cs.generateKeyPair("./keys");
app.use(express.json());
app.use(cs.middleware({ privateKey: keypair.privateKey }));Your existing routes stay completely unchanged:
app.post("/api/data", (req, res) => {
// req.body is already decrypted automatically
console.log(req.body);
res.json({ received: req.body, ok: true });
// response is auto-encrypted with client's public key
});Production Setup (key persistence)
const fs = require("fs");
const cs = require("crypto-secure");
const keyFile = "./keys/server-key.json";
let keypair;
if (fs.existsSync(keyFile)) {
keypair = JSON.parse(fs.readFileSync(keyFile, "utf8"));
} else {
keypair = cs.generateKeyPair("./keys");
fs.writeFileSync(keyFile, JSON.stringify(keypair));
}
app.use(express.json());
app.use(cs.middleware({ privateKey: keypair.privateKey }));
// Expose your public key for clients
app.get("/.well-known/encryption-key", (req, res) => {
res.json({ publicKey: keypair.publicKey });
});Frontend Usage (React / Browser)
Install
npm install crypto-secureSetup (6 lines)
import * as cs from "crypto-secure/client";
const serverKey = await cs.fetchServerPublicKey("/.well-known/encryption-key");
const clientKeys = await cs.generateKeyPair();
axios.defaults.headers.common["x-encryption-key"] =
cs.getClientHeader(clientKeys.publicKey)["x-encryption-key"];Encrypt a request
const encrypted = await cs.encrypt(payload, serverKey);
// → { encryptedAESKey, delta, tag, encryptedData }
axios.post("/api/data", encrypted);Decrypt a response
const res = await axios.post("/api/data", payload);
const decrypted = await cs.decrypt(res.data, clientKeys.privateKey);Automatic encryption via Axios interceptors
axios.interceptors.request.use(cs.createAxiosRequestInterceptor(serverKey));
axios.interceptors.response.use(
...cs.createAxiosResponseInterceptor(clientKeys.privateKey)
);
// All requests/responses are now automatically encrypted — zero manual workRoute-level encryption (URL params, tokens, etc.)
const enc = cs.encryptRoute("sensitive-value"); // base64 encoded
const dec = cs.decryptRoute(enc); // back to originalAPI Reference
Server-side (crypto-secure)
| Function | Description |
|---|---|
| generateKeyPair(saveTo?) | Generate RSA-2048 keypair. If saveTo is a path, writes public.pem and private.pem |
| encrypt(payload, publicKey, aad?) | Hybrid encrypt with RSA + AES-GCM. Payload is gzip-compressed before encryption |
| decrypt(encryptedPayload, privateKey) | Hybrid decrypt. Ciphertext is gunzip-decompressed after decryption |
| isEncrypted(obj) | Check if an object is an encrypted payload (duck-type check on envelope fields) |
| generateAESKey() | Generate a random 32-byte AES-256 key |
| generateIV() | Generate a random 12-byte IV for GCM |
| middleware(options) | Express middleware factory. Options: { privateKey, publicKeyHeader, decryptBody, encryptResponse } |
| Client | Node.js client class for programmatic encryption/decryption with key management |
Browser-side (crypto-secure/client)
| Function | Description |
|---|---|
| generateKeyPair() | Generate RSA-2048 keypair using Web Crypto API |
| getClientHeader(publicKeyPem) | Returns { "x-encryption-key": "..." } header (base64 DER without PEM framing) |
| fetchServerPublicKey(url) | Fetch server's public key from endpoint. Cached in memory + cookie for 24h |
| clearServerPublicKeyCache() | Clear in-memory and cookie cache |
| encrypt(payload, serverPublicKey) | Hybrid encrypt for requests (with gzip compression) |
| decrypt(encryptedPayload, clientPrivateKey) | Hybrid decrypt for responses (with gunzip decompression) |
| encryptRoute(value) | Base64-encode a route/param value (padding-stripped) |
| decryptRoute(value) | Base64-decode a route/param value |
| createAxiosRequestInterceptor(serverKey) | Axios request interceptor — auto-encrypts all outgoing data |
| createAxiosResponseInterceptor(clientPrivateKey) | Axios response interceptor — auto-decrypts all incoming responses |
Middleware Options
| Option | Type | Default | Description |
|---|---|---|---|
| privateKey | string (PEM) | required | Server's RSA private key for decrypting request bodies |
| publicKeyHeader | string | "x-encryption-key" | HTTP header where client sends its public key |
| decryptBody | boolean | true | Auto-decrypt req.body if it's an encrypted envelope |
| encryptResponse | boolean | true | Auto-encrypt res.json() responses with client's public key |
Payload Compression Impact
Since the library now gzip-compresses plaintext before encryption, here's the expected reduction in encryptedData size:
| JSON Payload Size | Before (base64) | After (compressed + base64) | Reduction | |---|---|---|---| | 100 bytes | ~133 chars | ~133 chars | ~0% (gzip overhead) | | 1 KB | ~1,365 chars | ~410 chars | ~70% | | 10 KB | ~13,650 chars | ~2,730 chars | ~80% | | 100 KB | ~136,500 chars | ~20,500 chars | ~85% | | 1 MB | ~1.37M chars | ~137K chars | ~90% |
Note: The fixed envelope overhead (
encryptedAESKey+delta+tag≈ 384 base64 chars) is constant. Compression provides the most benefit for payloads above 500 bytes. Very small or already-compressed payloads see minimal gain.
Security Details
Algorithms
| Component | Algorithm | Parameters | |---|---|---| | Symmetric encryption | AES-256-GCM | 256-bit key, 96-bit IV, 128-bit authentication tag | | Key wrapping | RSA-OAEP | 2048-bit key, SHA-256 hash, SHA-256 MGF1 | | Compression | gzip (deflate) | Applied before encryption, zero security impact |
Security Properties
- AEAD (Authenticated Encryption with Associated Data) — GCM provides both confidentiality and integrity. Any tampering with ciphertext is detected on decryption.
- Randomness per message — Every
encrypt()call generates a fresh AES key and IV via cryptographic RNG. - Key encapsulation — The AES key is never transmitted in plaintext; it's wrapped with RSA-2048 OAEP.
- Padding oracle immunity — GCM is not vulnerable to padding oracle attacks (unlike CBC).
- AAD binding — Optional additional data is cryptographically bound to the ciphertext. Modifying AAD invalidates the tag.
- No plaintext in transit — The only data on the wire is the RSA-wrapped AES key, IV, authentication tag, and encrypted ciphertext.
What crypto-secure does NOT protect against
- Side-channel attacks (timing, power analysis, cache) — not designed for HSM-level security
- Quantum computing — RSA-2048 and AES-256 are not quantum-resistant. Post-quantum migration is a future concern
- Compromised private keys — if your private key is stolen, all past traffic can be decrypted (no forward secrecy at the RSA level)
- Client-side XSS — an attacker with JS execution can read plaintext after decryption
Architecture
┌──────────────────────────────────┐
│ Shared Protocol │
│ RSA-2048 OAEP SHA-256 │
│ AES-256-GCM (authenticated) │
│ gzip compression before encrypt│
│ Base64 wire encoding │
└──────────────────────────────────┘
↕
┌──────────────────────────┴──────────────────────────┐
▼ ▼
┌─────────────────────────┐ ┌──────────────────────────┐
│ Node.js (server) │ │ Browser (client) │
│ │ │ │
│ crypto-secure.js │ │ client-browser.mjs │
│ └─ Core crypto engine │ │ └─ Web Crypto API │
│ └─ node-forge backend │ │ └─ CompressionStream │
│ └─ zlib compression │ │ └─ Cookie caching │
│ │ │ │
│ middleware.js │ │ Axios interceptors │
│ └─ Express middleware │ │ └─ Request auto-encrypt │
│ └─ Auto decrypt req │ │ └─ Response auto-decrypt│
│ └─ Auto encrypt res │ │ │
│ │ │ encryptRoute() │
│ client.js │ │ decryptRoute() │
│ └─ Node.js client class│ │ │
└─────────────────────────┘ └──────────────────────────┘Core Files
| File | Purpose |
|---|---|
| crypto-secure.js | Core hybrid encryption engine. UMD format — works in Node.js, AMD, and browser globals. Uses node-forge for RSA, AES-GCM, and PEM encoding, and zlib for gzip compression. |
| middleware.js | Express middleware factory. Decrypts req.body, encrypts res.json(), handles PEM key reconstruction from HTTP headers. |
| client.js | Node.js client class with key pair management and helper methods for request/response encryption. |
| client-browser.mjs | Browser client as an ES Module. Uses Web Crypto API (crypto.subtle) and CompressionStream for native browser compression. Includes Axios interceptor factories and cookie-based key caching. |
| index.js | Package entry point. Aggregates all modules and attaches .middleware and .Client to the main export. |
Package Contents
crypto-secure/
├── index.js # Package entry (CommonJS)
├── crypto-secure.js # Core crypto engine (UMD)
├── middleware.js # Express middleware (CommonJS)
├── client.js # Node.js client (CommonJS)
├── client-browser.mjs # Browser client (ES Module)
├── CONTRIBUTING.md # Contribution guidelines
├── CODE_OF_CONDUCT.md # Code of conduct
├── LICENSE # MIT license
├── package.json
└── README.mdRequirements
- Node.js ≥ 12.x (uses
Buffer,zlib,require) - Express 4.x or 5.x (peer dependency, only needed for middleware)
- Browsers: Chrome 80+, Firefox 110+, Safari 16.4+, Edge 80+ (requires
CompressionStreamand Web Crypto API support)
License
MIT License — see LICENSE for details.
