New:Socket for Asana Is Now Available.Learn more
Get Started

webcrypt

Package Overview
Dependencies
Maintainers
1
Versions
21
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

webcrypt - npm Package Compare versions

Comparing version
0.6.1
to
0.6.3
+80
-31
dist/WebCrypt.cjs

@@ -45,2 +45,11 @@ var __defProp = Object.defineProperty;

static DEFAULT_HMAC_SALT = new TextEncoder().encode("WebCrypt-HMAC-DefaultSalt-v0.6");
/**
* Generates a cryptographically secure random salt for HMAC key derivation.
* @param {number} [length=16] Length of salt in bytes
* @returns {Uint8Array} Random salt bytes
*/
static generateHmacSalt(length = 16) {
const cryptoInstance = globalThis.crypto || typeof require !== "undefined" && require("crypto").webcrypto;
return cryptoInstance.getRandomValues(new Uint8Array(length));
}
// Caches derived keys for instant reuse with same password/salt (performance optimization)

@@ -73,7 +82,15 @@ static MAX_KEY_CACHE_SIZE = 10;

const now = Date.now();
const keysToDelete = [];
for (const [cacheKey, value] of this.keyCache.entries()) {
if (now - value.createdAt > _WebCrypt.KEY_CACHE_TTL_MS) {
this.keyCache.delete(cacheKey);
keysToDelete.push(cacheKey);
}
}
for (const key of keysToDelete) {
const entry = this.keyCache.get(key);
if (entry) {
entry.key = null;
}
this.keyCache.delete(key);
}
}

@@ -84,2 +101,7 @@ /**

clearKeyCache() {
for (const [key, value] of this.keyCache) {
if (value) {
value.key = null;
}
}
this.keyCache.clear();

@@ -97,7 +119,15 @@ }

_getCrypto() {
if (typeof globalThis !== "undefined" && globalThis.crypto) return globalThis.crypto;
if (typeof globalThis !== "undefined" && globalThis.crypto && globalThis.crypto.subtle) {
return globalThis.crypto;
}
if (typeof require !== "undefined") {
const { webcrypto } = require("crypto");
return webcrypto;
try {
const { webcrypto } = require("crypto");
if (webcrypto && webcrypto.subtle) return webcrypto;
} catch (e) {
}
}
if (typeof globalThis !== "undefined" && globalThis.crypto) {
return globalThis.crypto;
}
throw new Error("Web Crypto API not available in this environment");

@@ -161,3 +191,3 @@ }

const bytes = new Uint8Array(buffer);
const CHUNK_SIZE = 32768;
const CHUNK_SIZE = 1024;
let binary = "";

@@ -170,3 +200,8 @@ for (let i = 0; i < bytes.length; i += CHUNK_SIZE) {

_base64ToArrayBuffer(base64) {
const binary = atob(base64);
let padded = base64;
const mod = base64.length % 4;
if (mod > 0) {
padded += "=".repeat(4 - mod);
}
const binary = atob(padded);
const len = binary.length;

@@ -200,4 +235,4 @@ const bytes = new Uint8Array(len);

}
// Maximum allowed encrypted data size (10MB) to prevent DoS attacks
static MAX_ENCRYPTED_DATA_SIZE = 10 * 1024 * 1024;
// Max encrypted data size for single-buffer operations (1GB threshold)
static MAX_ENCRYPTED_DATA_SIZE = 1024 * 1024 * 1024;
/**

@@ -365,2 +400,3 @@ * Decrypt a Base64 string produced by encryptText().

async generateHmacKey(password, hash = "SHA-256", customSalt = null) {
const crypto2 = this._getCrypto();
let keyMaterial;

@@ -375,3 +411,3 @@ if (password) {

};
const baseKey = await crypto.subtle.importKey(
const baseKey = await crypto2.subtle.importKey(
"raw",

@@ -383,12 +419,12 @@ new TextEncoder().encode(password),

);
keyMaterial = await crypto.subtle.deriveBits(pbkdf2Params, baseKey, 256);
keyMaterial = await crypto2.subtle.deriveBits(pbkdf2Params, baseKey, 256);
} else {
keyMaterial = crypto.getRandomValues(new Uint8Array(32));
keyMaterial = crypto2.getRandomValues(new Uint8Array(32));
}
return crypto.subtle.importKey(
return crypto2.subtle.importKey(
"raw",
keyMaterial,
{ name: "HMAC", hash },
true,
// Exportable for storage if needed
false,
// Non-exportable for security
["sign", "verify"]

@@ -404,4 +440,5 @@ );

async computeHmac(data, key) {
const crypto2 = this._getCrypto();
const dataBuffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
const signature = await crypto.subtle.sign("HMAC", key, dataBuffer);
const signature = await crypto2.subtle.sign("HMAC", key, dataBuffer);
return this._arrayBufferToBase64(signature);

@@ -417,5 +454,6 @@ }

async verifyHmac(data, hmac, key) {
const crypto2 = this._getCrypto();
const dataBuffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
const signatureBuffer = new Uint8Array(this._base64ToArrayBuffer(hmac));
return crypto.subtle.verify("HMAC", key, signatureBuffer, dataBuffer);
return crypto2.subtle.verify("HMAC", key, signatureBuffer, dataBuffer);
}

@@ -431,2 +469,3 @@ // ════════════════════════════ Post-Quantum HMAC (SHA-3) ════════════════════════════

async generateHmacKeySHA3(password, hash = "SHA3-256", customSalt = null) {
const crypto2 = this._getCrypto();
let keyMaterial;

@@ -444,5 +483,5 @@ if (password) {

try {
material = new Uint8Array(await crypto.subtle.digest(hash, hashInput));
material = new Uint8Array(await crypto2.subtle.digest(hash, hashInput));
} catch (e) {
material = new Uint8Array(await crypto.subtle.digest("SHA-256", hashInput));
material = new Uint8Array(await crypto2.subtle.digest("SHA-256", hashInput));
}

@@ -452,3 +491,3 @@ }

} else {
keyMaterial = crypto.getRandomValues(new Uint8Array(32));
keyMaterial = crypto2.getRandomValues(new Uint8Array(32));
}

@@ -459,3 +498,3 @@ let hmacHash = hash;

}
return crypto.subtle.importKey("raw", keyMaterial, { name: "HMAC", hash: hmacHash }, true, [
return crypto2.subtle.importKey("raw", keyMaterial, { name: "HMAC", hash: hmacHash }, false, [
"sign",

@@ -472,4 +511,5 @@ "verify"

async computeHmacSHA3(data, key) {
const crypto2 = this._getCrypto();
const dataBuffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
const signature = await crypto.subtle.sign("HMAC", key, dataBuffer);
const signature = await crypto2.subtle.sign("HMAC", key, dataBuffer);
return btoa(String.fromCharCode(...new Uint8Array(signature)));

@@ -485,5 +525,6 @@ }

async verifyHmacSHA3(data, hmac, key) {
const crypto2 = this._getCrypto();
const dataBuffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
const signatureBuffer = Uint8Array.from(atob(hmac), (c) => c.charCodeAt(0));
return crypto.subtle.verify("HMAC", key, signatureBuffer, dataBuffer);
return crypto2.subtle.verify("HMAC", key, signatureBuffer, dataBuffer);
}

@@ -499,4 +540,9 @@ // ────────────────────── Human-Friendly Data Operations ──────────────────────

async encryptData(data, password) {
const text = JSON.stringify(data);
return await this.encryptText(text, password);
try {
const text = JSON.stringify(data);
return await this.encryptText(text, password);
} catch (e) {
if (e.message && e.message.startsWith("WebCrypt")) throw e;
throw new Error(`Failed to serialize data: ${e.message}`);
}
}

@@ -510,4 +556,11 @@ /**

async decryptData(b64, password) {
const text = await this.decryptText(b64, password);
return JSON.parse(text);
try {
const text = await this.decryptText(b64, password);
return JSON.parse(text);
} catch (e) {
if (e instanceof SyntaxError) {
throw new Error(`Failed to parse decrypted data as JSON: ${e.message}`);
}
throw e;
}
}

@@ -523,7 +576,3 @@ /**

const randomBytes = cryptoInstance.getRandomValues(new Uint8Array(length));
let binary = "";
for (let i = 0; i < randomBytes.byteLength; i++) {
binary += String.fromCharCode(randomBytes[i]);
}
return btoa(binary);
return Array.from(randomBytes, (b) => b.toString(16).padStart(2, "0")).join("");
}

@@ -530,0 +579,0 @@ };

@@ -29,2 +29,11 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {

static DEFAULT_HMAC_SALT = new TextEncoder().encode("WebCrypt-HMAC-DefaultSalt-v0.6");
/**
* Generates a cryptographically secure random salt for HMAC key derivation.
* @param {number} [length=16] Length of salt in bytes
* @returns {Uint8Array} Random salt bytes
*/
static generateHmacSalt(length = 16) {
const cryptoInstance = globalThis.crypto || typeof __require !== "undefined" && __require("crypto").webcrypto;
return cryptoInstance.getRandomValues(new Uint8Array(length));
}
// Caches derived keys for instant reuse with same password/salt (performance optimization)

@@ -57,7 +66,15 @@ static MAX_KEY_CACHE_SIZE = 10;

const now = Date.now();
const keysToDelete = [];
for (const [cacheKey, value] of this.keyCache.entries()) {
if (now - value.createdAt > _WebCrypt.KEY_CACHE_TTL_MS) {
this.keyCache.delete(cacheKey);
keysToDelete.push(cacheKey);
}
}
for (const key of keysToDelete) {
const entry = this.keyCache.get(key);
if (entry) {
entry.key = null;
}
this.keyCache.delete(key);
}
}

@@ -68,2 +85,7 @@ /**

clearKeyCache() {
for (const [key, value] of this.keyCache) {
if (value) {
value.key = null;
}
}
this.keyCache.clear();

@@ -81,7 +103,15 @@ }

_getCrypto() {
if (typeof globalThis !== "undefined" && globalThis.crypto) return globalThis.crypto;
if (typeof globalThis !== "undefined" && globalThis.crypto && globalThis.crypto.subtle) {
return globalThis.crypto;
}
if (typeof __require !== "undefined") {
const { webcrypto } = __require("crypto");
return webcrypto;
try {
const { webcrypto } = __require("crypto");
if (webcrypto && webcrypto.subtle) return webcrypto;
} catch (e) {
}
}
if (typeof globalThis !== "undefined" && globalThis.crypto) {
return globalThis.crypto;
}
throw new Error("Web Crypto API not available in this environment");

@@ -145,3 +175,3 @@ }

const bytes = new Uint8Array(buffer);
const CHUNK_SIZE = 32768;
const CHUNK_SIZE = 1024;
let binary = "";

@@ -154,3 +184,8 @@ for (let i = 0; i < bytes.length; i += CHUNK_SIZE) {

_base64ToArrayBuffer(base64) {
const binary = atob(base64);
let padded = base64;
const mod = base64.length % 4;
if (mod > 0) {
padded += "=".repeat(4 - mod);
}
const binary = atob(padded);
const len = binary.length;

@@ -184,4 +219,4 @@ const bytes = new Uint8Array(len);

}
// Maximum allowed encrypted data size (10MB) to prevent DoS attacks
static MAX_ENCRYPTED_DATA_SIZE = 10 * 1024 * 1024;
// Max encrypted data size for single-buffer operations (1GB threshold)
static MAX_ENCRYPTED_DATA_SIZE = 1024 * 1024 * 1024;
/**

@@ -349,2 +384,3 @@ * Decrypt a Base64 string produced by encryptText().

async generateHmacKey(password, hash = "SHA-256", customSalt = null) {
const crypto2 = this._getCrypto();
let keyMaterial;

@@ -359,3 +395,3 @@ if (password) {

};
const baseKey = await crypto.subtle.importKey(
const baseKey = await crypto2.subtle.importKey(
"raw",

@@ -367,12 +403,12 @@ new TextEncoder().encode(password),

);
keyMaterial = await crypto.subtle.deriveBits(pbkdf2Params, baseKey, 256);
keyMaterial = await crypto2.subtle.deriveBits(pbkdf2Params, baseKey, 256);
} else {
keyMaterial = crypto.getRandomValues(new Uint8Array(32));
keyMaterial = crypto2.getRandomValues(new Uint8Array(32));
}
return crypto.subtle.importKey(
return crypto2.subtle.importKey(
"raw",
keyMaterial,
{ name: "HMAC", hash },
true,
// Exportable for storage if needed
false,
// Non-exportable for security
["sign", "verify"]

@@ -388,4 +424,5 @@ );

async computeHmac(data, key) {
const crypto2 = this._getCrypto();
const dataBuffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
const signature = await crypto.subtle.sign("HMAC", key, dataBuffer);
const signature = await crypto2.subtle.sign("HMAC", key, dataBuffer);
return this._arrayBufferToBase64(signature);

@@ -401,5 +438,6 @@ }

async verifyHmac(data, hmac, key) {
const crypto2 = this._getCrypto();
const dataBuffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
const signatureBuffer = new Uint8Array(this._base64ToArrayBuffer(hmac));
return crypto.subtle.verify("HMAC", key, signatureBuffer, dataBuffer);
return crypto2.subtle.verify("HMAC", key, signatureBuffer, dataBuffer);
}

@@ -415,2 +453,3 @@ // ════════════════════════════ Post-Quantum HMAC (SHA-3) ════════════════════════════

async generateHmacKeySHA3(password, hash = "SHA3-256", customSalt = null) {
const crypto2 = this._getCrypto();
let keyMaterial;

@@ -428,5 +467,5 @@ if (password) {

try {
material = new Uint8Array(await crypto.subtle.digest(hash, hashInput));
material = new Uint8Array(await crypto2.subtle.digest(hash, hashInput));
} catch (e) {
material = new Uint8Array(await crypto.subtle.digest("SHA-256", hashInput));
material = new Uint8Array(await crypto2.subtle.digest("SHA-256", hashInput));
}

@@ -436,3 +475,3 @@ }

} else {
keyMaterial = crypto.getRandomValues(new Uint8Array(32));
keyMaterial = crypto2.getRandomValues(new Uint8Array(32));
}

@@ -443,3 +482,3 @@ let hmacHash = hash;

}
return crypto.subtle.importKey("raw", keyMaterial, { name: "HMAC", hash: hmacHash }, true, [
return crypto2.subtle.importKey("raw", keyMaterial, { name: "HMAC", hash: hmacHash }, false, [
"sign",

@@ -456,4 +495,5 @@ "verify"

async computeHmacSHA3(data, key) {
const crypto2 = this._getCrypto();
const dataBuffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
const signature = await crypto.subtle.sign("HMAC", key, dataBuffer);
const signature = await crypto2.subtle.sign("HMAC", key, dataBuffer);
return btoa(String.fromCharCode(...new Uint8Array(signature)));

@@ -469,5 +509,6 @@ }

async verifyHmacSHA3(data, hmac, key) {
const crypto2 = this._getCrypto();
const dataBuffer = typeof data === "string" ? new TextEncoder().encode(data) : data;
const signatureBuffer = Uint8Array.from(atob(hmac), (c) => c.charCodeAt(0));
return crypto.subtle.verify("HMAC", key, signatureBuffer, dataBuffer);
return crypto2.subtle.verify("HMAC", key, signatureBuffer, dataBuffer);
}

@@ -483,4 +524,9 @@ // ────────────────────── Human-Friendly Data Operations ──────────────────────

async encryptData(data, password) {
const text = JSON.stringify(data);
return await this.encryptText(text, password);
try {
const text = JSON.stringify(data);
return await this.encryptText(text, password);
} catch (e) {
if (e.message && e.message.startsWith("WebCrypt")) throw e;
throw new Error(`Failed to serialize data: ${e.message}`);
}
}

@@ -494,4 +540,11 @@ /**

async decryptData(b64, password) {
const text = await this.decryptText(b64, password);
return JSON.parse(text);
try {
const text = await this.decryptText(b64, password);
return JSON.parse(text);
} catch (e) {
if (e instanceof SyntaxError) {
throw new Error(`Failed to parse decrypted data as JSON: ${e.message}`);
}
throw e;
}
}

@@ -507,7 +560,3 @@ /**

const randomBytes = cryptoInstance.getRandomValues(new Uint8Array(length));
let binary = "";
for (let i = 0; i < randomBytes.byteLength; i++) {
binary += String.fromCharCode(randomBytes[i]);
}
return btoa(binary);
return Array.from(randomBytes, (b) => b.toString(16).padStart(2, "0")).join("");
}

@@ -514,0 +563,0 @@ };

@@ -28,2 +28,27 @@ var __defProp = Object.defineProperty;

static WARNING = "\u26A0\uFE0F CRITICAL: WebCryptPQC is PLACEHOLDER/STUB implementation. Kyber and Dilithium are NOT real PQC - they use SHA-3 hashing stubs. Not suitable for production security. Integrate liboqs-js or wait for official implementation.";
static _STUB_MODE = true;
/**
* Programmatically check if PQC module is running in stub mode.
* @returns {boolean} True if PQC module is a placeholder stub.
*/
static isStub() {
return _WebCryptPQC._STUB_MODE;
}
/**
* Enable or disable stub testing mode.
* @param {boolean} [allow=true] If true, allows stub operations for testing purposes.
*/
static enableStubTesting(allow = true) {
_WebCryptPQC._STUB_MODE = !allow;
}
/**
* Internal helper to verify stub mode state before PQC operations.
*/
_checkStubMode() {
if (_WebCryptPQC._STUB_MODE) {
throw new Error(
"WebCryptPQC is a placeholder stub \u2014 not for production use. Call WebCryptPQC.enableStubTesting(true) for testing."
);
}
}
// ─────────────────────── Kyber Constants ───────────────────────

@@ -108,7 +133,15 @@ static KYBER_512 = "Kyber512";

_getCrypto() {
if (typeof globalThis !== "undefined" && globalThis.crypto) return globalThis.crypto;
if (typeof globalThis !== "undefined" && globalThis.crypto && globalThis.crypto.subtle) {
return globalThis.crypto;
}
if (typeof require !== "undefined") {
const { webcrypto } = require("crypto");
return webcrypto;
try {
const { webcrypto } = require("crypto");
if (webcrypto && webcrypto.subtle) return webcrypto;
} catch (e) {
}
}
if (typeof globalThis !== "undefined" && globalThis.crypto) {
return globalThis.crypto;
}
throw new Error("Web Crypto API not available");

@@ -123,2 +156,3 @@ }

async generateKyberKeyPair(level = _WebCryptPQC.KYBER_768) {
this._checkStubMode();
if (!_WebCryptPQC.SUPPORTED_KYBER_LEVELS.includes(level)) {

@@ -138,2 +172,3 @@ throw new Error(`Unsupported Kyber level: ${level}. Use Kyber512, Kyber768, or Kyber1024`);

async kyberEncapsulate(kyberPublicKey, level = _WebCryptPQC.KYBER_768) {
this._checkStubMode();
if (!_WebCryptPQC.SUPPORTED_KYBER_LEVELS.includes(level)) {

@@ -165,2 +200,3 @@ throw new Error(`Unsupported Kyber level: ${level}`);

async kyberDecapsulate(ciphertext, kyberPrivateKey, level = _WebCryptPQC.KYBER_768) {
this._checkStubMode();
if (!_WebCryptPQC.SUPPORTED_KYBER_LEVELS.includes(level)) {

@@ -193,2 +229,3 @@ throw new Error(`Unsupported Kyber level: ${level}`);

async generateDilithiumKeyPair(level = _WebCryptPQC.DILITHIUM_3) {
this._checkStubMode();
if (!_WebCryptPQC.SUPPORTED_DILITHIUM_LEVELS.includes(level)) {

@@ -209,2 +246,3 @@ throw new Error(`Unsupported Dilithium level: ${level}`);

async dilithiumSign(message, dilithiumPrivateKey, level = _WebCryptPQC.DILITHIUM_3) {
this._checkStubMode();
if (!_WebCryptPQC.SUPPORTED_DILITHIUM_LEVELS.includes(level)) {

@@ -241,2 +279,3 @@ throw new Error(`Unsupported Dilithium level: ${level}`);

async dilithiumVerify(message, signature, dilithiumPublicKey, level = _WebCryptPQC.DILITHIUM_3) {
this._checkStubMode();
if (!_WebCryptPQC.SUPPORTED_DILITHIUM_LEVELS.includes(level)) {

@@ -346,2 +385,7 @@ throw new Error(`Unsupported Dilithium level: ${level}`);

const fallbackAlgorithm = bitLength <= 256 ? "SHA-256" : bitLength <= 384 ? "SHA-384" : "SHA-512";
if (typeof console !== "undefined" && console.warn) {
console.warn(
`SHA3-${bitLength} not natively supported by Web Crypto, falling back to ${fallbackAlgorithm}`
);
}
const digest = await this._crypto.subtle.digest(fallbackAlgorithm, data);

@@ -423,3 +467,3 @@ return new Uint8Array(digest).slice(0, bitLength / 8);

const bytes = new Uint8Array(buffer);
const CHUNK_SIZE = 32768;
const CHUNK_SIZE = 1024;
let binary = "";

@@ -432,3 +476,8 @@ for (let i = 0; i < bytes.length; i += CHUNK_SIZE) {

_base64ToArrayBuffer(base64) {
const binary = atob(base64);
let padded = base64;
const mod = base64.length % 4;
if (mod > 0) {
padded += "=".repeat(4 - mod);
}
const binary = atob(padded);
const bytes = new Uint8Array(binary.length);

@@ -435,0 +484,0 @@ for (let i = 0; i < binary.length; i++) {

@@ -11,2 +11,27 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {

static WARNING = "\u26A0\uFE0F CRITICAL: WebCryptPQC is PLACEHOLDER/STUB implementation. Kyber and Dilithium are NOT real PQC - they use SHA-3 hashing stubs. Not suitable for production security. Integrate liboqs-js or wait for official implementation.";
static _STUB_MODE = true;
/**
* Programmatically check if PQC module is running in stub mode.
* @returns {boolean} True if PQC module is a placeholder stub.
*/
static isStub() {
return _WebCryptPQC._STUB_MODE;
}
/**
* Enable or disable stub testing mode.
* @param {boolean} [allow=true] If true, allows stub operations for testing purposes.
*/
static enableStubTesting(allow = true) {
_WebCryptPQC._STUB_MODE = !allow;
}
/**
* Internal helper to verify stub mode state before PQC operations.
*/
_checkStubMode() {
if (_WebCryptPQC._STUB_MODE) {
throw new Error(
"WebCryptPQC is a placeholder stub \u2014 not for production use. Call WebCryptPQC.enableStubTesting(true) for testing."
);
}
}
// ─────────────────────── Kyber Constants ───────────────────────

@@ -91,7 +116,15 @@ static KYBER_512 = "Kyber512";

_getCrypto() {
if (typeof globalThis !== "undefined" && globalThis.crypto) return globalThis.crypto;
if (typeof globalThis !== "undefined" && globalThis.crypto && globalThis.crypto.subtle) {
return globalThis.crypto;
}
if (typeof __require !== "undefined") {
const { webcrypto } = __require("crypto");
return webcrypto;
try {
const { webcrypto } = __require("crypto");
if (webcrypto && webcrypto.subtle) return webcrypto;
} catch (e) {
}
}
if (typeof globalThis !== "undefined" && globalThis.crypto) {
return globalThis.crypto;
}
throw new Error("Web Crypto API not available");

@@ -106,2 +139,3 @@ }

async generateKyberKeyPair(level = _WebCryptPQC.KYBER_768) {
this._checkStubMode();
if (!_WebCryptPQC.SUPPORTED_KYBER_LEVELS.includes(level)) {

@@ -121,2 +155,3 @@ throw new Error(`Unsupported Kyber level: ${level}. Use Kyber512, Kyber768, or Kyber1024`);

async kyberEncapsulate(kyberPublicKey, level = _WebCryptPQC.KYBER_768) {
this._checkStubMode();
if (!_WebCryptPQC.SUPPORTED_KYBER_LEVELS.includes(level)) {

@@ -148,2 +183,3 @@ throw new Error(`Unsupported Kyber level: ${level}`);

async kyberDecapsulate(ciphertext, kyberPrivateKey, level = _WebCryptPQC.KYBER_768) {
this._checkStubMode();
if (!_WebCryptPQC.SUPPORTED_KYBER_LEVELS.includes(level)) {

@@ -176,2 +212,3 @@ throw new Error(`Unsupported Kyber level: ${level}`);

async generateDilithiumKeyPair(level = _WebCryptPQC.DILITHIUM_3) {
this._checkStubMode();
if (!_WebCryptPQC.SUPPORTED_DILITHIUM_LEVELS.includes(level)) {

@@ -192,2 +229,3 @@ throw new Error(`Unsupported Dilithium level: ${level}`);

async dilithiumSign(message, dilithiumPrivateKey, level = _WebCryptPQC.DILITHIUM_3) {
this._checkStubMode();
if (!_WebCryptPQC.SUPPORTED_DILITHIUM_LEVELS.includes(level)) {

@@ -224,2 +262,3 @@ throw new Error(`Unsupported Dilithium level: ${level}`);

async dilithiumVerify(message, signature, dilithiumPublicKey, level = _WebCryptPQC.DILITHIUM_3) {
this._checkStubMode();
if (!_WebCryptPQC.SUPPORTED_DILITHIUM_LEVELS.includes(level)) {

@@ -329,2 +368,7 @@ throw new Error(`Unsupported Dilithium level: ${level}`);

const fallbackAlgorithm = bitLength <= 256 ? "SHA-256" : bitLength <= 384 ? "SHA-384" : "SHA-512";
if (typeof console !== "undefined" && console.warn) {
console.warn(
`SHA3-${bitLength} not natively supported by Web Crypto, falling back to ${fallbackAlgorithm}`
);
}
const digest = await this._crypto.subtle.digest(fallbackAlgorithm, data);

@@ -406,3 +450,3 @@ return new Uint8Array(digest).slice(0, bitLength / 8);

const bytes = new Uint8Array(buffer);
const CHUNK_SIZE = 32768;
const CHUNK_SIZE = 1024;
let binary = "";

@@ -415,3 +459,8 @@ for (let i = 0; i < bytes.length; i += CHUNK_SIZE) {

_base64ToArrayBuffer(base64) {
const binary = atob(base64);
let padded = base64;
const mod = base64.length % 4;
if (mod > 0) {
padded += "=".repeat(4 - mod);
}
const binary = atob(padded);
const bytes = new Uint8Array(binary.length);

@@ -418,0 +467,0 @@ for (let i = 0; i < binary.length; i++) {

{
"name": "webcrypt",
"version": "0.6.1",
"version": "0.6.3",
"description": "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.",

@@ -63,7 +63,7 @@ "type": "module",

"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"test:coverage": "node --experimental-vm-modules $(which jest) --coverage",
"format": "prettier --write \"**/*.{js,ts,json,md}\"",
"format:check": "prettier --check \"**/*.{js,ts,json,md}\"",
"lint": "npm run format:check",
"prepare": "npm run build",
"prepare": "if [ \"$NODE_ENV\" != \"development\" ]; then npm run build; fi",
"prepublishOnly": "npm run format:check",

@@ -70,0 +70,0 @@ "publish": "npm publish --access public"

+26
-25

@@ -66,16 +66,16 @@ # WebCrypt

| 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](./docs/PQC.md) |
| 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](./docs/PQC.md) |

@@ -456,14 +456,15 @@ ---

### Security hardening (v0.6.1)
### Security hardening (v0.6.3)
- PBKDF2 iterations: **600,000** (OWASP 2025+ compliant)
- Unique **128-bit salt** per message/file
- Unique **96-bit IV** per chunk/frame
- Key cache with **5-minute TTL**, LRU eviction, and automatic `unref()` timer cleanup
- Deterministic password-derived HMAC key derivation (`generateHmacKey` / `generateHmacKeySHA3`)
- Non-blocking timing-attack resistant verification via `TimingSafeHelper`
- High-speed 32KB chunked Base64 conversion
- Input validation / DoS protection (10 MB size limit)
- Error messages sanitized in production (`NODE_ENV=production`)
- No keys ever leave your device
- **PBKDF2 Iterations**: **600,000** (OWASP 2025+ compliant).
- **Non-Exportable HMAC Keys**: Generated HMAC keys pass `extractable: false` to `crypto.subtle.importKey` preventing secret key extraction while allowing full signing and verification.
- **Constant-Time Verification Contract**: `TimingSafeHelper.timingSafeVerify()` catches signature verification failures and returns `false` with padding instead of re-throwing exceptions.
- **PQC Stub Guard**: `WebCryptPQC._STUB_MODE` throws explicit errors on production calls unless `WebCryptPQC.enableStubTesting(true)` is explicitly enabled for unit testing.
- **Safe Memory Cleanup**: Map mutations are isolated prior to deletion in `clearKeyCache()` and secret `CryptoKey` references are nulled.
- **Stack-Safe Base64**: Uses 1024-byte chunking in Base64 encoding/decoding, eliminating call stack overflow risks across JS runtimes.
- **Base64 Padding Resilience**: `_base64ToArrayBuffer()` automatically normalizes unpadded Base64 strings.
- **Increased File Payload Limit**: `MAX_ENCRYPTED_DATA_SIZE` increased to **1 GB** for large file streaming.
- **Full-Entropy Random Passwords**: `generateRandomPassword()` returns full-entropy hexadecimal strings derived via `crypto.getRandomValues`.
- **SHA-3 Fallback Warning**: Explicit `console.warn` logging when SHA-3 falls back to SHA-256/384/512 in standard Web Crypto runtimes.
- **Sanitized Production Logs**: Detailed internal error logging gated behind `NODE_ENV !== "production"`.

@@ -470,0 +471,0 @@ ### Known limitations

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display