
Research
/Security News
OpenAPI React Query Codegen Compromised in Mini Shai-Hulud npm Supply Chain Attack
Ten malicious OpenAPI React Query Codegen versions were published to npm in the Mini Shai-Hulud attack, all with valid provenance.
Zero-dependency JavaScript AES-256-GCM encryption suite for text, files & WebRTC E2EE. Supports symmetric password derivation, RSA-4096 hybrid encryption, and post-quantum cryptography via pure Web Crypto API.
Zero-dependency end-to-end encryption for the modern web.
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.
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);
| Feature | Status | Details |
|---|---|---|
| Text encryption | ✅ Done | AES-256-GCM, returns base64 string |
| File encryption | ✅ Done | Streaming — handles large files (1 GB size 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 | Safe unref'd timers & reference nulling |
| 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 | Stub mode testing guard — see docs/PQC.md |
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 |
Password-based AES-256-GCM encryption with PBKDF2 key derivation (600,000 iterations).
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");
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"]
// Encrypt
const { blob, filename } = await wc.encryptFile(file, "password");
// Decrypt
const { blob: decrypted, filename: originalName } = await wc.decryptFile(encryptedBlob, "password");
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.
RSA-4096 hybrid encryption: RSA-OAEP encrypts an ephemeral AES-256-GCM session key, which encrypts the payload.
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);
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"
// 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);
const { blob, filename } = await crypt.encryptFileWithProgress(file, publicKey, progress => {
console.log(`${Math.round(progress * 100)}%`);
});
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"
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);
const key = await crypt.deriveKeyPBKDF2("password", "salt"); // 600,000 iterations
const key = await crypt.deriveKeySHA3("password", 50000, "SHA3-256");
const masterSecret = new TextEncoder().encode("master-password");
const key = await crypt.deriveKeyHKDFSHA3(masterSecret, saltBytes, infoBytes, 256);
// 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");
⚠️ 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.
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
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>
See docs/PQC.md for the full API reference.
| 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 |
extractable: false to crypto.subtle.importKey preventing secret key extraction while allowing full signing and verification.TimingSafeHelper.timingSafeVerify() catches signature verification failures and returns false with padding instead of re-throwing exceptions.WebCryptPQC._STUB_MODE throws explicit errors on production calls unless WebCryptPQC.enableStubTesting(true) is explicitly enabled for unit testing.clearKeyCache() and secret CryptoKey references are nulled._base64ToArrayBuffer() automatically normalizes unpadded Base64 strings.MAX_ENCRYPTED_DATA_SIZE increased to 1 GB for large file streaming.generateRandomPassword() returns full-entropy hexadecimal strings derived via crypto.getRandomValues.console.warn logging when SHA-3 falls back to SHA-256/384/512 in standard Web Crypto runtimes.NODE_ENV !== "production".For vulnerability reporting, see SECURITY.md.
For security fix details, see SECURITY_FIXES.md.
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");
MIT License — free for personal and commercial use.
© 2025-2026 PuterVision LLC
[!WARNING] Limitation of Liability & Disclaimer of Warranty
WebCrypt is maintained by PuterVision LLC and provided "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL PUTERVISION LLC, ITS AFFILIATES, OR CONTRIBUTORS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. USERS AND DEVELOPERS ARE SOLELY RESPONSIBLE FOR VERIFYING CRYPTOGRAPHIC PARAMETERS, CONDUCTING INDEPENDENT SECURITY AUDITS, AND DETERMINING SUITABILITY FOR PRODUCTION DEPLOYMENTS.
[!NOTE] Data Privacy & Local Execution Guarantee
WebCrypt operations execute 100% locally in your browser or Node.js runtime using the native Web Crypto API. No private keys, passwords, plaintext data, or encrypted payloads are ever transmitted to external cloud servers.
[!IMPORTANT] Third-Party AI Model & API Fees Disclaimer
This software is provided as free, open-source software under the MIT License. If integrated into developer AI agent frameworks (such as OpenAI, Anthropic Claude, Google Gemini, xAI Grok, or Ollama), any third-party API usage and billing fees remain the sole responsibility of the user. PuterVision LLC is not responsible for third-party API costs.
[!NOTE] Trademark & Open Specification Attributions
Model Context Protocol (MCP) is an open specification created by Anthropic, PBC. Web Crypto API, W3C standards, and third-party IDE trademarks are property of their respective owners. PuterVision LLC is an independent open-source software developer.
[!NOTE] Performance & Cost Savings Estimates
Encryption performance benchmarks, throughput speeds, and zero-dependency efficiency metrics reported in documentation are derived from standard browser and Node.js WebCrypto benchmarks and may vary based on hardware acceleration.
FAQs
Zero-dependency JavaScript AES-256-GCM encryption suite for text, files, WebRTC E2EE & AI Agent Tooling (MCP). Supports symmetric password derivation, RSA-4096 hybrid encryption, and post-quantum cryptography via pure Web Crypto API.
The npm package webcrypt receives a total of 1,183 weekly downloads. As such, webcrypt popularity was classified as popular.
We found that webcrypt demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Research
/Security News
Ten malicious OpenAPI React Query Codegen versions were published to npm in the Mini Shai-Hulud attack, all with valid provenance.

Security News
Socket joins more than 100 technology, cybersecurity, and financial organizations calling for a global surge in cyber defense.

Product
Enterprise security teams can now detect malware, credential theft, suspicious network activity, and risky updates across Microsoft Edge extensions.