JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 43
  • Score
    100M100P100Q60770F
  • License MIT

Zero-dependency JavaScript-based AES-256-GCM encryption for text, files & WebRTC E2EE. Includes symmetric (password) and asymmetric (RSA-4096 hybrid) modes. Streaming, quantum-resistant key derivation, pure Web Crypto API.

Package Exports

  • webcrypt
  • webcrypt/webcrypt
  • webcrypt/webcrypt-asym
  • webcrypt/webcrypt-pqc

Readme

WebCrypt

Zero-dependency end-to-end encryption for the modern web.

npm version license tests

AES-256-GCM symmetric encryption, RSA-4096 hybrid asymmetric encryption, ECDH key exchange, digital signatures, HMAC, and streaming file encryption — all powered by the native Web Crypto API with zero runtime dependencies.


Quick Start

npm install webcrypt

Encrypt and decrypt text:

import { WebCrypt } from "webcrypt";
const wc = new WebCrypt();

const encrypted = await wc.encryptText("Secret message", "my-password");
const decrypted = await wc.decryptText(encrypted, "my-password");

Encrypt a file:

const { blob, filename } = await wc.encryptFile(file, "my-password");

Public-key encryption (RSA-4096):

import { WebCryptAsym } from "webcrypt";
const wca = new WebCryptAsym();

const keys = await wca.generateKeyPair();
const encrypted = await wca.encryptText("Secret", keys.publicKey);
const decrypted = await wca.decryptText(encrypted, keys.privateKey);

Table of Contents


Features

Feature Status Details
Text encryption ✅ Done AES-256-GCM, returns base64 string
File encryption ✅ Done Streaming — handles large files (10 MB decrypt limit)
WebRTC E2EE ✅ Done Insertable Streams for video + audio
Digital signatures ✅ Done ECDSA, RSA-PSS
ECDH key exchange ✅ Done P-256 / P-384 Diffie-Hellman
HMAC ✅ Done SHA-256/384/512 and SHA-3
Key derivation ✅ Done PBKDF2 (600k iterations), SHA-3 KDF, HKDF
Key caching ✅ Done 5-min TTL, LRU eviction (max 10)
TypeScript ✅ Done Full .d.ts for all modules
Zero dependencies ✅ Done Pure Web Crypto API
Post-quantum (Kyber/Dilith) ⚠️ Stub Placeholder — see docs/PQC.md

Modules

WebCrypt is split into three modules. Import only what you need:

import { WebCrypt } from "webcrypt"; // Symmetric (password-based)
import { WebCryptAsym } from "webcrypt"; // Asymmetric (public/private key)
import { WebCryptPQC } from "webcrypt"; // Post-quantum (⚠️ stub)
Module Use case Encryption Quantum-safe?
WebCrypt Password-based encryption AES-256-GCM ✅ Yes (Grover-resistant)
WebCryptAsym Public-key encryption RSA-4096 + AES ⚠️ RSA vulnerable to Shor
WebCryptPQC Post-quantum (future) Kyber + Dilithium ⚠️ Stub — not real PQC yet

Symmetric Encryption (WebCrypt)

Password-based AES-256-GCM encryption with PBKDF2 key derivation (600,000 iterations).

Text

import { WebCrypt } from "webcrypt";
const wc = new WebCrypt();

const encrypted = await wc.encryptText("The treasure is buried under the oak tree", "password");
const decrypted = await wc.decryptText(encrypted, "password");

JSON Data

const data = { message: "Hello", users: ["Alice", "Bob"] };
const encrypted = await wc.encryptData(data, "password");
const decrypted = await wc.decryptData(encrypted, "password");
// decrypted.users → ["Alice", "Bob"]

Files

// Encrypt
const { blob, filename } = await wc.encryptFile(file, "password");

// Decrypt
const { blob: decrypted, filename: originalName } = await wc.decryptFile(encryptedBlob, "password");

WebRTC End-to-End Encryption

const wc = new WebCrypt();
const PASSWORD = "shared-call-secret";

const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
const pc = new RTCPeerConnection();

// Encrypt outgoing
stream.getTracks().forEach(async track => {
  const sender = pc.addTrack(track, stream);
  sender.transform = new RTCRtpScriptTransform(await wc.createEncryptTransform(PASSWORD));
});

// Decrypt incoming
pc.ontrack = async event => {
  event.receiver.transform = new RTCRtpScriptTransform(await wc.createDecryptTransform(PASSWORD));
  document.getElementById("remoteVideo").srcObject = event.streams[0];
};

Both peers use the same password. The SFU/server sees only encrypted data.


Asymmetric Encryption (WebCryptAsym)

RSA-4096 hybrid encryption: RSA-OAEP encrypts an ephemeral AES-256-GCM session key, which encrypts the payload.

Encrypt / Decrypt

import { WebCryptAsym } from "webcrypt";
const crypt = new WebCryptAsym();

// Generate key pair
const keys = await crypt.generateKeyPair();

// Share public key
const publicKeyB64 = await crypt.exportPublicKey(keys.publicKey);

// Recipient imports and encrypts
const publicKey = await crypt.importPublicKey(publicKeyB64);
const encrypted = await crypt.encryptText("Secret message", publicKey);

// Decrypt with private key
const decrypted = await crypt.decryptText(encrypted, keys.privateKey);

ECDH Key Exchange

Derive a shared secret between two parties without transmitting any secret material.

// Each party generates an ECDH key pair
const alice = await crypt.generateECDHKeyPair();
const bob = await crypt.generateECDHKeyPair();

// Exchange public keys, then encrypt
const encrypted = await crypt.encryptWithECDH(
  { data: "Secret from Alice" },
  alice.privateKey,
  await crypt.importECDHPublicKey(bob.publicKeyB64)
);

// Recipient decrypts
const decrypted = await crypt.decryptWithECDH(
  encrypted,
  bob.privateKey,
  await crypt.importECDHPublicKey(alice.publicKeyB64)
);
// decrypted.data → "Secret from Alice"

Digital Signatures (ECDSA)

// Generate signing key pair
const { publicKey, privateKey, publicKeyB64 } = await crypt.generateSigningKeyPair("P-256");

// Sign
const signature = await crypt.signText("I approve transaction #123", privateKey);

// Verify
const valid = await crypt.verifyText("I approve transaction #123", signature, publicKey);
// valid === true

// Sign/verify files (detached signatures)
const { signatureB64 } = await crypt.signFile(file, privateKey);
const fileValid = await crypt.verifyFile(file, signatureB64, publicKey);

File Encryption with Progress

const { blob, filename } = await crypt.encryptFileWithProgress(file, publicKey, progress => {
  console.log(`${Math.round(progress * 100)}%`);
});

HMAC

Message authentication codes using SHA-256, SHA-384, SHA-512, or SHA-3.

import { WebCrypt } from "webcrypt";
const wc = new WebCrypt();

// Generate key and compute HMAC
const key = await wc.generateHmacKey("password");
const hmac = await wc.computeHmac("Important message", key);

// Verify
const valid = await wc.verifyHmac("Important message", hmac, key); // true

// SHA-3 variant (quantum-resistant)
const sha3Key = await wc.generateHmacKeySHA3("password");
const sha3Hmac = await wc.computeHmacSHA3("Important message", sha3Key);
const sha3Valid = await wc.verifyHmacSHA3("Important message", sha3Hmac, sha3Key);

Key Derivation

PBKDF2 (default)

const key = await crypt.deriveKeyPBKDF2("password", "salt"); // 600,000 iterations

SHA-3 KDF

const key = await crypt.deriveKeySHA3("password", 50000, "SHA3-256");

HKDF-SHA3

const masterSecret = new TextEncoder().encode("master-password");
const key = await crypt.deriveKeyHKDFSHA3(masterSecret, saltBytes, infoBytes, 256);

Key rotation and hierarchical keys

// Rotate with a new salt
const rotatedKey = await crypt.rotateKeyNew("password", newSaltBytes, "PBKDF2");

// Derive child keys for different purposes
const encKey = await crypt.deriveChildKeyHierarchical(parentKey, childSalt, "encryption");
const sigKey = await crypt.deriveChildKeyHierarchical(parentKey, childSalt, "signing");

Post-Quantum Cryptography

⚠️ STUB IMPLEMENTATION — WebCryptPQC currently uses SHA-3 hashing stubs, not real lattice-based cryptography. For production PQC, integrate liboqs-js directly.

WebCryptPQC provides a placeholder API for Kyber (key encapsulation) and Dilithium (digital signatures) that mirrors the real API surface. Build against it today, swap in real PQC when v0.6+ ships.

import { WebCryptPQC } from "webcrypt";
const pqc = new WebCryptPQC(); // ⚠️ Warns about stub status

const kyberKeys = await pqc.generateKyberKeyPair("Kyber768");
const { ciphertext, sharedSecret } = await pqc.kyberEncapsulate(kyberKeys.publicKey, "Kyber768");
const recovered = await pqc.kyberDecapsulate(ciphertext, kyberKeys.privateKey, "Kyber768");

Full PQC documentation: docs/PQC.md — includes Kyber, Dilithium, hybrid encryption, security levels, and migration path.


API Reference

WebCrypt (Symmetric)

const wc = new WebCrypt();

// Text
wc.encryptText(text: string, password: string): Promise<string>
wc.decryptText(b64: string, password: string): Promise<string>

// JSON data
wc.encryptData(data: any, password: string): Promise<string>
wc.decryptData(b64: string, password: string): Promise<any>

// Files
wc.encryptFile(file: File | Blob, password: string): Promise<{ blob: Blob, filename: string }>
wc.decryptFile(file: File | Blob, password: string): Promise<{ blob: Blob, filename: string }>

// WebRTC E2EE
wc.createEncryptTransform(password: string): Promise<TransformFunction>
wc.createDecryptTransform(password: string): Promise<TransformFunction>

// HMAC
wc.generateHmacKey(password?: string, hash?: string): Promise<CryptoKey>
wc.computeHmac(data: string | ArrayBuffer, key: CryptoKey): Promise<string>
wc.verifyHmac(data: string | ArrayBuffer, hmac: string, key: CryptoKey): Promise<boolean>

// HMAC-SHA3
wc.generateHmacKeySHA3(password?: string, hash?: string): Promise<CryptoKey>
wc.computeHmacSHA3(data: string | ArrayBuffer, key: CryptoKey): Promise<string>
wc.verifyHmacSHA3(data: string | ArrayBuffer, hmac: string, key: CryptoKey): Promise<boolean>

// Utilities
wc.generateRandomPassword(length?: number): string
wc.clearKeyCache(): void
wc.stopAutoCleanup(): void

WebCryptAsym (Asymmetric)

const crypt = new WebCryptAsym();

// Key management
crypt.generateKeyPair(): Promise<CryptoKeyPair>
crypt.exportPublicKey(key: CryptoKey): Promise<string>
crypt.exportPrivateKey(key: CryptoKey): Promise<string>
crypt.importPublicKey(b64: string): Promise<CryptoKey>
crypt.importPrivateKey(b64: string): Promise<CryptoKey>

// Text
crypt.encryptText(text: string, publicKey: CryptoKey): Promise<string>
crypt.decryptText(b64: string, privateKey: CryptoKey): Promise<string>

// JSON data
crypt.encryptData(data: any, publicKey: CryptoKey): Promise<string>
crypt.decryptData(b64: string, privateKey: CryptoKey): Promise<any>

// Files
crypt.encryptFile(file: File | Blob, publicKey: CryptoKey): Promise<{ blob, filename }>
crypt.decryptFile(file: File | Blob, privateKey: CryptoKey): Promise<{ blob, filename }>
crypt.encryptFileWithProgress(file, publicKey, onProgress?): Promise<{ blob, filename }>
crypt.decryptFileWithProgress(file, privateKey, onProgress?): Promise<{ blob, filename }>

// ECDH key exchange
crypt.generateECDHKeyPair(curve?: string): Promise<{ publicKey, privateKey, publicKeyB64 }>
crypt.importECDHPublicKey(b64: string, curve?: string): Promise<CryptoKey>
crypt.encryptWithECDH(data: any, privateKey, recipientPublicKey): Promise<string>
crypt.decryptWithECDH(b64: string, privateKey, senderPublicKey): Promise<any>

// Signing (ECDSA)
crypt.generateSigningKeyPair(curve?: string): Promise<{ publicKey, privateKey, publicKeyB64 }>
crypt.signText(text: string, privateKey: CryptoKey): Promise<string>
crypt.verifyText(text: string, sig: string, publicKey: CryptoKey): Promise<boolean>
crypt.signFile(file: File | Blob, privateKey: CryptoKey): Promise<{ signatureB64, blob }>
crypt.verifyFile(file: File | Blob, sig: string, publicKey: CryptoKey): Promise<boolean>

// Additional signing algorithms
crypt.signTextWithAlgorithm(text, privateKey, algorithm?: 'ECDSA' | 'RSA-PSS'): Promise<string>
crypt.verifyTextWithAlgorithm(text, sig, publicKey, algorithm?): Promise<boolean>

// MAC
crypt.signHMAC(data: string, key: CryptoKey, hash?: string): Promise<string>
crypt.verifyHMAC(data: string, sig: string, key: CryptoKey, hash?: string): Promise<boolean>

// Key derivation
crypt.deriveKeyPBKDF2(password, salt, iterations?): Promise<CryptoKey>
crypt.deriveKeySHA3(password, iterations?, algorithm?): Promise<CryptoKey>
crypt.deriveKeyHKDFSHA3(secret, salt?, info?, keyLength?): Promise<CryptoKey>
crypt.rotateKeyNew(password, newSalt, method?): Promise<CryptoKey>
crypt.deriveChildKeyHierarchical(parentKey, childSalt, purpose?): Promise<CryptoKey>
crypt.secureKeyErase(key: Uint8Array): void

// WebRTC
crypt.createEncryptTransform(publicKey: CryptoKey): Promise<TransformFunction>
crypt.createDecryptTransform(privateKey: CryptoKey): Promise<TransformFunction>

WebCryptPQC (Post-Quantum)

See docs/PQC.md for the full API reference.


Security

What's quantum-safe today

Layer Algorithm Quantum status
Symmetric encryption AES-256-GCM ✅ Safe — 128-bit security even with Grover
Key derivation PBKDF2 600k ✅ Safe — no quantum speedup for password cracking
HMAC SHA-256/3 ✅ Safe — collision resistance holds
Asymmetric encryption RSA-4096 ⚠️ Vulnerable to Shor's algorithm (est. 2030–2040)
Signatures ECDSA ⚠️ Vulnerable to Shor's algorithm
PQC (Kyber/Dilithium) Stubs ❌ Not real PQC yet

Security hardening (v0.5.3+)

  • PBKDF2 iterations: 600,000 (OWASP 2023 compliant)
  • Unique 128-bit salt per message/file
  • Unique 96-bit IV per chunk/frame
  • Key cache with 5-minute TTL and LRU eviction
  • Timing-attack resistant verification via TimingSafeHelper
  • Input validation / DoS protection (10 MB size limit)
  • Error messages sanitized in production (NODE_ENV=production)
  • No keys ever leave your device

Known limitations

  • PQC is a stub — Kyber/Dilithium use SHA-3 hashing, not real lattice-based crypto
  • Argon2id is not supported by Web Crypto API — falls back to PBKDF2 with a warning
  • JavaScript cannot guarantee secure memory erasure — key cleanup is best-effort
  • WebRTC E2EE uses a fixed salt for key derivation from passwords

For vulnerability reporting, see SECURITY.md.
For security fix details, see SECURITY_FIXES.md.


Environment Support

Browser: Chrome 80+ · Edge 80+ · Firefox 90+ · Safari 15+
Runtime: Node.js 18+ · Deno · Cloudflare Workers · Electron
Frameworks: React · Next.js · Vue · Angular · Svelte

// ES Modules
import { WebCrypt } from "webcrypt";

// CommonJS
const { WebCrypt } = require("webcrypt");

License

MIT License — free for personal and commercial use.
© 2025 Lucas Armstrong

No telemetry. No servers. No backdoors.
Just pure, strong encryption that works offline, forever.