@ubay182/hpke-wrapper
v1.0.1
Published
HPKE (Hybrid Public Key Encryption) wrapper for meta framework (nextjs, nuxtjs, sveltekit etc.) applications with end-to-end encryption and obfuscation
Maintainers
Readme
@ubay182/hpke-wrapper
HPKE (Hybrid Public Key Encryption) wrapper for modern web applications (Next.js, SvelteKit, etc.) with end-to-end (E2E) encryption and seal/unseal obfuscation. RFC 9180 compliant.
Demo
Check out the complete implementation examples in the example/ folder:
cd example/next-16 # or example/sveltekit
npm install
npm run devOpen http://localhost:3000 (or http://localhost:5173 for SvelteKit) — to see the end-to-end encryption form using HPKE.
Installation
npm install @ubay182/hpke-wrapperQuick Start (Agnostic)
1. Server-side Initialization
You can create a singleton HPKE server instance that persists keys and rotates them automatically:
import { createHpkeServer } from "@ubay182/hpke-wrapper";
const server = createHpkeServer({
persistKeys: true, // Save keys to .hpke-server-keys.json
rotateKeys: true, // Auto rotate every 24 hours
keysFilePath: "./.keys", // Custom path (optional)
rotationIntervalMs: 24 * 60 * 60 * 1000,
});
await server.init();
// Use these functions in your API routes/endpoints:
const pubKey = server.getPublicKeyBase64();
const decryptedText = await server.decrypt(wrappedCiphertext);
const encryptedResponse = await server.encrypt(message, clientPubKeyB64);2. Client-side Encryption
import {
createHpkeSuite,
generateKeyPair,
seal,
unseal,
} from "@ubay182/hpke-wrapper";
const suite = createHpkeSuite();
const clientKeys = await generateKeyPair();
// 1. Encrypt data to send to the server
const sealedPayload = await seal(
suite,
serverPubKeyB64,
JSON.stringify({
data: "my secret",
_clientPublicKey: clientKeys.publicKey,
}),
);
// ... send sealedPayload to the server via POST, receive encrypted response ...
// 2. Decrypt server response
const decrypted = await unseal(suite, clientKeys.privateKey, encryptedResponse);Next.js Integration Helpers
This package provides specific helper functions for Next.js to make setup easier.
Route Handlers
app/api/hpke/route.ts:
import { createHpkeHandlers } from "@ubay182/hpke-wrapper";
const { GET, POST } = createHpkeHandlers({
persistKeys: true,
rotateKeys: true,
onRequest: async (decrypted) => {
// decrypted: parsed JSON from client (without _clientPublicKey)
// The return value will be encrypted and sent back to the client
return { result: "success", data: decrypted };
},
});
export { GET, POST };Middleware Helper (Send public key via cookie)
middleware.ts:
import { createHpkeServer, createHpkeMiddleware } from "@ubay182/hpke-wrapper";
const server = createHpkeServer({ autoGenerateKeys: true });
server.init().catch(() => {});
export default createHpkeMiddleware(server);
export const config = { matcher: "/:path*" };Note: Middleware runs on Edge Runtime where the
fsmodule is not available. For fully persistent key storage, use the Route Handler approach.
API
Core HPKE
| Function | Description |
| ------------------------------- | --------------------------------- |
| createHpkeSuite() | HPKE suite with AES-128-GCM |
| createHpkeSuiteChaCha20() | HPKE suite with ChaCha20-Poly1305 |
| generateKeyPair() | Generate X25519 key pair |
| exportKeyToBase64(key) | Export public key to base64 |
| importKeyFromBase64(b64) | Import public key from base64 |
| hpkeEncrypt(msg, pubKey) | Encrypt message |
| hpkeDecrypt(ct, enc, privKey) | Decrypt message |
| uint8ArrayToBase64(buf) | Encode Uint8Array to base64 |
| base64ToUint8Array(b64) | Decode base64 to Uint8Array |
Seal / Unseal (Recommended)
| Function | Description |
| ----------------------------------- | ------------------------------------- |
| seal(suite, pubKeyB64, plainText) | Encrypt + obfuscate → single string |
| unseal(suite, privKey, cipher) | Decrypt obfuscated string → plaintext |
The sealed format wraps the HPKE ciphertext with random prefix/suffix, which aims to hide the encryption structure:
"x7k2mSGVsbG8gV29ybG...x7k2m0"Encryption Flow
CLIENT (Browser) SERVER (Node/Next.js/SvelteKit)
───────────────── ─────────────────
1. Page loads Hook/Middleware sets cookie:
Reads cookie: hpke_server_public_key=<b64>
hpke_server_public_key
2. Generate client X25519 key pair
3. seal(suite, serverPubKey, payload)
→ "x7k2m<encrypted>...x7k2m0"
4. POST /api/hpke { data: "..." }
5. server.decrypt(data)
→ unseal → plaintext
6. Extract _clientPublicKey
7. Process Request / Call external API
8. server.encrypt(response, clientPubKey)
→ seal → "..."
9. Return { data: "..." }
10. unseal(suite, clientPrivKey, response)
→ plaintextKey Persistence
If using persistKeys: true, keys will be saved to the .hpke-server-keys.json file. This ensures keys persist across server restarts. Note: This feature uses the fs module (Node.js only) — it is not available on Edge Runtime.
Key Rotation
If using rotateKeys: true, new keys will be generated every rotationIntervalMs interval. Old keys will be kept for a grace period so in-flight encrypted messages can still be decrypted.
Security
- RFC 9180 compliant (X25519 + HKDF-SHA256 + AES-128-GCM)
- Seal/Unseal obfuscation hides encryption metadata structure
- Auto key rotation with grace period
- Public key delivery via cookie (no additional API call required)
For production with sensitive data:
- Use KMS/HSM instead of file-based key storage
- Always use HTTPS
- Add rate limiting and request validation
- Use Redis for servers with multi-instance setups
- Consider ChaCha20-Poly1305 if you have many mobile app clients
License
MIT
