npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

webcrypt

v0.5.5

Published

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.

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 | | JWE (JSON Web Encryption) | ✅ Done | RFC 7516 Compact Serialization (RSA-OAEP/A256GCM) | | 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)}%`);
});

JSON Web Encryption (JWE)

Create and decrypt standard JWE Compact Serialization tokens (RFC 7516) using RSA-OAEP-256 and A256GCM.

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

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

// Encrypt payload into a JWE string
const payload = { userId: 123, role: "admin" };
const jweToken = await crypt.encryptJWE(payload, keys.publicKey, { kid: "my-key-id" });

// Decrypt JWE token
const decrypted = await crypt.decryptJWE(jweToken, keys.privateKey);
// decrypted.role → "admin"

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>

// JWE
crypt.encryptJWE(payload: any, publicKey: CryptoKey, headers?: object): Promise<string>
crypt.decryptJWE(jweToken: string, privateKey: CryptoKey): Promise<any>

// 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.