+11
-5
@@ -51,2 +51,3 @@ // src/WebCrypt.d.ts | ||
| * @param password Encryption password | ||
| * @param options Optional configuration object ({ parallelChunks?: number }) | ||
| * @returns Object with encrypted Blob and suggested filename | ||
@@ -56,3 +57,4 @@ */ | ||
| file: File | Blob, | ||
| password: string | ||
| password: string, | ||
| options?: { parallelChunks?: number } | ||
| ): Promise<{ | ||
@@ -67,2 +69,3 @@ blob: Blob; | ||
| * @param password Must match encryption password | ||
| * @param options Optional configuration object ({ parallelChunks?: number }) | ||
| * @returns Object with decrypted Blob and original filename | ||
@@ -73,3 +76,4 @@ * @throws If password is wrong or file is corrupted | ||
| file: File | Blob, | ||
| password: string | ||
| password: string, | ||
| options?: { parallelChunks?: number } | ||
| ): Promise<{ | ||
@@ -140,5 +144,6 @@ blob: Blob; | ||
| * Generate a quantum-resistant HMAC key using SHA-3 hash. | ||
| * @param password Optional password for derivation (600k iterations) | ||
| * @param password Optional password for derivation | ||
| * @param hash Hash algorithm: 'SHA3-256' | 'SHA3-384' | 'SHA3-512' (default: SHA3-256) | ||
| * @param salt Optional salt for deterministic derivation. | ||
| * @param iterations Iterations count for SHA-3 KDF derivation (default: 10,000) | ||
| * @returns Usable HMAC key with SHA-3 | ||
@@ -149,3 +154,4 @@ */ | ||
| hash?: "SHA3-256" | "SHA3-384" | "SHA3-512", | ||
| salt?: Uint8Array | string | ||
| salt?: Uint8Array | string, | ||
| iterations?: number | ||
| ): Promise<CryptoKey>; | ||
@@ -570,3 +576,3 @@ | ||
| /** | ||
| * Generate a Poly1305 authentication tag | ||
| * @deprecated Poly1305 is not supported by standard Web Crypto API. Use signHMAC() instead. | ||
| * @param data - Data to authenticate | ||
@@ -573,0 +579,0 @@ * @param key - Poly1305 key (should be 32 bytes) |
+11
-5
@@ -51,2 +51,3 @@ // src/WebCrypt.d.ts | ||
| * @param password Encryption password | ||
| * @param options Optional configuration object ({ parallelChunks?: number }) | ||
| * @returns Object with encrypted Blob and suggested filename | ||
@@ -56,3 +57,4 @@ */ | ||
| file: File | Blob, | ||
| password: string | ||
| password: string, | ||
| options?: { parallelChunks?: number } | ||
| ): Promise<{ | ||
@@ -67,2 +69,3 @@ blob: Blob; | ||
| * @param password Must match encryption password | ||
| * @param options Optional configuration object ({ parallelChunks?: number }) | ||
| * @returns Object with decrypted Blob and original filename | ||
@@ -73,3 +76,4 @@ * @throws If password is wrong or file is corrupted | ||
| file: File | Blob, | ||
| password: string | ||
| password: string, | ||
| options?: { parallelChunks?: number } | ||
| ): Promise<{ | ||
@@ -140,5 +144,6 @@ blob: Blob; | ||
| * Generate a quantum-resistant HMAC key using SHA-3 hash. | ||
| * @param password Optional password for derivation (600k iterations) | ||
| * @param password Optional password for derivation | ||
| * @param hash Hash algorithm: 'SHA3-256' | 'SHA3-384' | 'SHA3-512' (default: SHA3-256) | ||
| * @param salt Optional salt for deterministic derivation. | ||
| * @param iterations Iterations count for SHA-3 KDF derivation (default: 10,000) | ||
| * @returns Usable HMAC key with SHA-3 | ||
@@ -149,3 +154,4 @@ */ | ||
| hash?: "SHA3-256" | "SHA3-384" | "SHA3-512", | ||
| salt?: Uint8Array | string | ||
| salt?: Uint8Array | string, | ||
| iterations?: number | ||
| ): Promise<CryptoKey>; | ||
@@ -570,3 +576,3 @@ | ||
| /** | ||
| * Generate a Poly1305 authentication tag | ||
| * @deprecated Poly1305 is not supported by standard Web Crypto API. Use signHMAC() instead. | ||
| * @param data - Data to authenticate | ||
@@ -573,0 +579,0 @@ * @param key - Poly1305 key (should be 32 bytes) |
+43
-16
@@ -194,3 +194,3 @@ var __defProp = Object.defineProperty; | ||
| const bytes = new Uint8Array(buffer); | ||
| const CHUNK_SIZE = 1024; | ||
| const CHUNK_SIZE = 32768; | ||
| let binary = ""; | ||
@@ -208,8 +208,3 @@ for (let i = 0; i < bytes.length; i += CHUNK_SIZE) { | ||
| } | ||
| const binary = atob(padded); | ||
| const len = binary.length; | ||
| const bytes = new Uint8Array(len); | ||
| for (let i = 0; i < len; i++) { | ||
| bytes[i] = binary.charCodeAt(i); | ||
| } | ||
| const bytes = Uint8Array.from(atob(padded), (c) => c.charCodeAt(0)); | ||
| return bytes.buffer; | ||
@@ -282,3 +277,12 @@ } | ||
| */ | ||
| async encryptFile(fileOrBlob, password) { | ||
| /** | ||
| * Encrypt a File or Blob using password-derived key. | ||
| * | ||
| * @param {File|Blob} fileOrBlob - File or Blob to encrypt | ||
| * @param {string} password - Encryption password | ||
| * @param {Object} [options={}] - Optional options ({ parallelChunks: 1 }) | ||
| * @returns {Promise<{blob: Blob, filename: string}>} Encrypted blob and suggested filename | ||
| */ | ||
| async encryptFile(fileOrBlob, password, options = {}) { | ||
| const parallelChunks = options.parallelChunks || 1; | ||
| const salt = crypto.getRandomValues(new Uint8Array(_WebCrypt.SALT_LENGTH)); | ||
@@ -290,2 +294,3 @@ const baseIv = crypto.getRandomValues(new Uint8Array(_WebCrypt.IV_LENGTH)); | ||
| let counter = 0; | ||
| let pendingPromises = []; | ||
| while (true) { | ||
@@ -297,5 +302,14 @@ const { done, value } = await reader.read(); | ||
| new DataView(iv.buffer).setUint32(_WebCrypt.IV_LENGTH - 4, counter++, true); | ||
| const encrypted = await crypto.subtle.encrypt({ name: _WebCrypt.ALGORITHM, iv }, key, value); | ||
| chunks.push(encrypted); | ||
| const promise = crypto.subtle.encrypt({ name: _WebCrypt.ALGORITHM, iv }, key, value); | ||
| pendingPromises.push(promise); | ||
| if (pendingPromises.length >= parallelChunks) { | ||
| const resolved = await Promise.all(pendingPromises); | ||
| chunks.push(...resolved); | ||
| pendingPromises = []; | ||
| } | ||
| } | ||
| if (pendingPromises.length > 0) { | ||
| const resolved = await Promise.all(pendingPromises); | ||
| chunks.push(...resolved); | ||
| } | ||
| const header = new Uint8Array(_WebCrypt.SALT_LENGTH + _WebCrypt.IV_LENGTH); | ||
@@ -314,6 +328,8 @@ header.set(salt, 0); | ||
| * @param {string} password - Must match the password used for encryption | ||
| * @param {Object} [options={}] - Optional options ({ parallelChunks: 1 }) | ||
| * @returns {Promise<{blob: Blob, filename: string}>} Decrypted blob and original filename | ||
| * @throws {Error} If password is wrong, file is corrupted, or file exceeds 10 MB | ||
| */ | ||
| async decryptFile(fileOrBlob, password) { | ||
| async decryptFile(fileOrBlob, password, options = {}) { | ||
| const parallelChunks = options.parallelChunks || 1; | ||
| const fileSize = fileOrBlob.size || fileOrBlob.blob && fileOrBlob.blob.size; | ||
@@ -336,2 +352,3 @@ if (fileSize && fileSize > _WebCrypt.MAX_ENCRYPTED_DATA_SIZE) { | ||
| let offset = 0, counter = 0; | ||
| let pendingPromises = []; | ||
| while (offset < ciphertext.byteLength) { | ||
@@ -343,6 +360,15 @@ const size = Math.min(_WebCrypt.CHUNK_SIZE, ciphertext.byteLength - offset); | ||
| new DataView(iv.buffer).setUint32(_WebCrypt.IV_LENGTH - 4, counter++, true); | ||
| const decrypted = await crypto.subtle.decrypt({ name: _WebCrypt.ALGORITHM, iv }, key, chunk); | ||
| chunks.push(decrypted); | ||
| const promise = crypto.subtle.decrypt({ name: _WebCrypt.ALGORITHM, iv }, key, chunk); | ||
| pendingPromises.push(promise); | ||
| offset += size; | ||
| if (pendingPromises.length >= parallelChunks) { | ||
| const resolved = await Promise.all(pendingPromises); | ||
| chunks.push(...resolved); | ||
| pendingPromises = []; | ||
| } | ||
| } | ||
| if (pendingPromises.length > 0) { | ||
| const resolved = await Promise.all(pendingPromises); | ||
| chunks.push(...resolved); | ||
| } | ||
| const filename = (fileOrBlob.name || "decrypted").replace(/\.encrypted$/i, ""); | ||
@@ -466,8 +492,9 @@ return { blob: new Blob(chunks), filename }; | ||
| * Generate a quantum-resistant HMAC key using SHA-3 hash. | ||
| * @param {string} [password] Optional password for derivation (600k iterations) | ||
| * @param {string} [password] Optional password for derivation | ||
| * @param {string} [hash='SHA3-256'] Hash algorithm: 'SHA3-256', 'SHA3-384', 'SHA3-512' | ||
| * @param {Uint8Array|string} [customSalt=null] Optional salt for deterministic derivation (defaults to WebCrypt.DEFAULT_HMAC_SALT if omitted) | ||
| * @param {number} [iterations=10000] Number of hash iterations for key derivation (default: 10,000 for high performance) | ||
| * @returns {Promise<CryptoKey>} Usable HMAC key with SHA-3 | ||
| */ | ||
| async generateHmacKeySHA3(password, hash = "SHA3-256", customSalt = null) { | ||
| async generateHmacKeySHA3(password, hash = "SHA3-256", customSalt = null, iterations = 1e4) { | ||
| const crypto2 = this._getCrypto(); | ||
@@ -481,3 +508,3 @@ let keyMaterial; | ||
| material.set(salt, password.length); | ||
| for (let i = 0; i < 6e5; i++) { | ||
| for (let i = 0; i < iterations; i++) { | ||
| const hashInput = new Uint8Array(material.byteLength + 4); | ||
@@ -484,0 +511,0 @@ hashInput.set(material); |
+10
-4
@@ -51,2 +51,3 @@ // src/WebCrypt.d.ts | ||
| * @param password Encryption password | ||
| * @param options Optional configuration object ({ parallelChunks?: number }) | ||
| * @returns Object with encrypted Blob and suggested filename | ||
@@ -56,3 +57,4 @@ */ | ||
| file: File | Blob, | ||
| password: string | ||
| password: string, | ||
| options?: { parallelChunks?: number } | ||
| ): Promise<{ | ||
@@ -67,2 +69,3 @@ blob: Blob; | ||
| * @param password Must match encryption password | ||
| * @param options Optional configuration object ({ parallelChunks?: number }) | ||
| * @returns Object with decrypted Blob and original filename | ||
@@ -73,3 +76,4 @@ * @throws If password is wrong or file is corrupted | ||
| file: File | Blob, | ||
| password: string | ||
| password: string, | ||
| options?: { parallelChunks?: number } | ||
| ): Promise<{ | ||
@@ -140,5 +144,6 @@ blob: Blob; | ||
| * Generate a quantum-resistant HMAC key using SHA-3 hash. | ||
| * @param password Optional password for derivation (600k iterations) | ||
| * @param password Optional password for derivation | ||
| * @param hash Hash algorithm: 'SHA3-256' | 'SHA3-384' | 'SHA3-512' (default: SHA3-256) | ||
| * @param salt Optional salt for deterministic derivation. | ||
| * @param iterations Iterations count for SHA-3 KDF derivation (default: 10,000) | ||
| * @returns Usable HMAC key with SHA-3 | ||
@@ -149,3 +154,4 @@ */ | ||
| hash?: "SHA3-256" | "SHA3-384" | "SHA3-512", | ||
| salt?: Uint8Array | string | ||
| salt?: Uint8Array | string, | ||
| iterations?: number | ||
| ): Promise<CryptoKey>; | ||
@@ -152,0 +158,0 @@ |
+10
-4
@@ -51,2 +51,3 @@ // src/WebCrypt.d.ts | ||
| * @param password Encryption password | ||
| * @param options Optional configuration object ({ parallelChunks?: number }) | ||
| * @returns Object with encrypted Blob and suggested filename | ||
@@ -56,3 +57,4 @@ */ | ||
| file: File | Blob, | ||
| password: string | ||
| password: string, | ||
| options?: { parallelChunks?: number } | ||
| ): Promise<{ | ||
@@ -67,2 +69,3 @@ blob: Blob; | ||
| * @param password Must match encryption password | ||
| * @param options Optional configuration object ({ parallelChunks?: number }) | ||
| * @returns Object with decrypted Blob and original filename | ||
@@ -73,3 +76,4 @@ * @throws If password is wrong or file is corrupted | ||
| file: File | Blob, | ||
| password: string | ||
| password: string, | ||
| options?: { parallelChunks?: number } | ||
| ): Promise<{ | ||
@@ -140,5 +144,6 @@ blob: Blob; | ||
| * Generate a quantum-resistant HMAC key using SHA-3 hash. | ||
| * @param password Optional password for derivation (600k iterations) | ||
| * @param password Optional password for derivation | ||
| * @param hash Hash algorithm: 'SHA3-256' | 'SHA3-384' | 'SHA3-512' (default: SHA3-256) | ||
| * @param salt Optional salt for deterministic derivation. | ||
| * @param iterations Iterations count for SHA-3 KDF derivation (default: 10,000) | ||
| * @returns Usable HMAC key with SHA-3 | ||
@@ -149,3 +154,4 @@ */ | ||
| hash?: "SHA3-256" | "SHA3-384" | "SHA3-512", | ||
| salt?: Uint8Array | string | ||
| salt?: Uint8Array | string, | ||
| iterations?: number | ||
| ): Promise<CryptoKey>; | ||
@@ -152,0 +158,0 @@ |
+43
-16
@@ -178,3 +178,3 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { | ||
| const bytes = new Uint8Array(buffer); | ||
| const CHUNK_SIZE = 1024; | ||
| const CHUNK_SIZE = 32768; | ||
| let binary = ""; | ||
@@ -192,8 +192,3 @@ for (let i = 0; i < bytes.length; i += CHUNK_SIZE) { | ||
| } | ||
| const binary = atob(padded); | ||
| const len = binary.length; | ||
| const bytes = new Uint8Array(len); | ||
| for (let i = 0; i < len; i++) { | ||
| bytes[i] = binary.charCodeAt(i); | ||
| } | ||
| const bytes = Uint8Array.from(atob(padded), (c) => c.charCodeAt(0)); | ||
| return bytes.buffer; | ||
@@ -266,3 +261,12 @@ } | ||
| */ | ||
| async encryptFile(fileOrBlob, password) { | ||
| /** | ||
| * Encrypt a File or Blob using password-derived key. | ||
| * | ||
| * @param {File|Blob} fileOrBlob - File or Blob to encrypt | ||
| * @param {string} password - Encryption password | ||
| * @param {Object} [options={}] - Optional options ({ parallelChunks: 1 }) | ||
| * @returns {Promise<{blob: Blob, filename: string}>} Encrypted blob and suggested filename | ||
| */ | ||
| async encryptFile(fileOrBlob, password, options = {}) { | ||
| const parallelChunks = options.parallelChunks || 1; | ||
| const salt = crypto.getRandomValues(new Uint8Array(_WebCrypt.SALT_LENGTH)); | ||
@@ -274,2 +278,3 @@ const baseIv = crypto.getRandomValues(new Uint8Array(_WebCrypt.IV_LENGTH)); | ||
| let counter = 0; | ||
| let pendingPromises = []; | ||
| while (true) { | ||
@@ -281,5 +286,14 @@ const { done, value } = await reader.read(); | ||
| new DataView(iv.buffer).setUint32(_WebCrypt.IV_LENGTH - 4, counter++, true); | ||
| const encrypted = await crypto.subtle.encrypt({ name: _WebCrypt.ALGORITHM, iv }, key, value); | ||
| chunks.push(encrypted); | ||
| const promise = crypto.subtle.encrypt({ name: _WebCrypt.ALGORITHM, iv }, key, value); | ||
| pendingPromises.push(promise); | ||
| if (pendingPromises.length >= parallelChunks) { | ||
| const resolved = await Promise.all(pendingPromises); | ||
| chunks.push(...resolved); | ||
| pendingPromises = []; | ||
| } | ||
| } | ||
| if (pendingPromises.length > 0) { | ||
| const resolved = await Promise.all(pendingPromises); | ||
| chunks.push(...resolved); | ||
| } | ||
| const header = new Uint8Array(_WebCrypt.SALT_LENGTH + _WebCrypt.IV_LENGTH); | ||
@@ -298,6 +312,8 @@ header.set(salt, 0); | ||
| * @param {string} password - Must match the password used for encryption | ||
| * @param {Object} [options={}] - Optional options ({ parallelChunks: 1 }) | ||
| * @returns {Promise<{blob: Blob, filename: string}>} Decrypted blob and original filename | ||
| * @throws {Error} If password is wrong, file is corrupted, or file exceeds 10 MB | ||
| */ | ||
| async decryptFile(fileOrBlob, password) { | ||
| async decryptFile(fileOrBlob, password, options = {}) { | ||
| const parallelChunks = options.parallelChunks || 1; | ||
| const fileSize = fileOrBlob.size || fileOrBlob.blob && fileOrBlob.blob.size; | ||
@@ -320,2 +336,3 @@ if (fileSize && fileSize > _WebCrypt.MAX_ENCRYPTED_DATA_SIZE) { | ||
| let offset = 0, counter = 0; | ||
| let pendingPromises = []; | ||
| while (offset < ciphertext.byteLength) { | ||
@@ -327,6 +344,15 @@ const size = Math.min(_WebCrypt.CHUNK_SIZE, ciphertext.byteLength - offset); | ||
| new DataView(iv.buffer).setUint32(_WebCrypt.IV_LENGTH - 4, counter++, true); | ||
| const decrypted = await crypto.subtle.decrypt({ name: _WebCrypt.ALGORITHM, iv }, key, chunk); | ||
| chunks.push(decrypted); | ||
| const promise = crypto.subtle.decrypt({ name: _WebCrypt.ALGORITHM, iv }, key, chunk); | ||
| pendingPromises.push(promise); | ||
| offset += size; | ||
| if (pendingPromises.length >= parallelChunks) { | ||
| const resolved = await Promise.all(pendingPromises); | ||
| chunks.push(...resolved); | ||
| pendingPromises = []; | ||
| } | ||
| } | ||
| if (pendingPromises.length > 0) { | ||
| const resolved = await Promise.all(pendingPromises); | ||
| chunks.push(...resolved); | ||
| } | ||
| const filename = (fileOrBlob.name || "decrypted").replace(/\.encrypted$/i, ""); | ||
@@ -450,8 +476,9 @@ return { blob: new Blob(chunks), filename }; | ||
| * Generate a quantum-resistant HMAC key using SHA-3 hash. | ||
| * @param {string} [password] Optional password for derivation (600k iterations) | ||
| * @param {string} [password] Optional password for derivation | ||
| * @param {string} [hash='SHA3-256'] Hash algorithm: 'SHA3-256', 'SHA3-384', 'SHA3-512' | ||
| * @param {Uint8Array|string} [customSalt=null] Optional salt for deterministic derivation (defaults to WebCrypt.DEFAULT_HMAC_SALT if omitted) | ||
| * @param {number} [iterations=10000] Number of hash iterations for key derivation (default: 10,000 for high performance) | ||
| * @returns {Promise<CryptoKey>} Usable HMAC key with SHA-3 | ||
| */ | ||
| async generateHmacKeySHA3(password, hash = "SHA3-256", customSalt = null) { | ||
| async generateHmacKeySHA3(password, hash = "SHA3-256", customSalt = null, iterations = 1e4) { | ||
| const crypto2 = this._getCrypto(); | ||
@@ -465,3 +492,3 @@ let keyMaterial; | ||
| material.set(salt, password.length); | ||
| for (let i = 0; i < 6e5; i++) { | ||
| for (let i = 0; i < iterations; i++) { | ||
| const hashInput = new Uint8Array(material.byteLength + 4); | ||
@@ -468,0 +495,0 @@ hashInput.set(material); |
@@ -375,3 +375,3 @@ // WebCryptAsym.d.ts | ||
| /** | ||
| * Generate a Poly1305 authentication tag | ||
| * @deprecated Poly1305 is not supported by standard Web Crypto API. Use signHMAC() instead. | ||
| * @param data - Data to authenticate | ||
@@ -378,0 +378,0 @@ * @param key - Poly1305 key (should be 32 bytes) |
@@ -375,3 +375,3 @@ // WebCryptAsym.d.ts | ||
| /** | ||
| * Generate a Poly1305 authentication tag | ||
| * @deprecated Poly1305 is not supported by standard Web Crypto API. Use signHMAC() instead. | ||
| * @param data - Data to authenticate | ||
@@ -378,0 +378,0 @@ * @param key - Poly1305 key (should be 32 bytes) |
+33
-18
@@ -251,2 +251,3 @@ var __defProp = Object.defineProperty; | ||
| const msgBytes = typeof message === "string" ? new TextEncoder().encode(message) : message; | ||
| const pubKey = dilithiumPrivateKey.subarray(params.privateKeySize - params.publicKeySize); | ||
| const hashInput = new Uint8Array(dilithiumPrivateKey.byteLength + msgBytes.byteLength); | ||
@@ -256,12 +257,15 @@ hashInput.set(dilithiumPrivateKey); | ||
| const digest = await this._sha3Hash(hashInput, 512); | ||
| const verifyInput = new Uint8Array(pubKey.byteLength + msgBytes.byteLength); | ||
| verifyInput.set(pubKey); | ||
| verifyInput.set(msgBytes, pubKey.byteLength); | ||
| const verifyTag = await this._sha3Hash(verifyInput, 512); | ||
| const signature = new Uint8Array(params.signatureSize); | ||
| signature.set(digest.slice(0, Math.min(params.signatureSize, digest.byteLength))); | ||
| signature.set(digest.slice(0, Math.min(64, digest.byteLength))); | ||
| signature.set(verifyTag.slice(0, Math.min(64, verifyTag.byteLength)), 64); | ||
| return signature; | ||
| } | ||
| /** | ||
| * โ ๏ธ PLACEHOLDER: Dilithium signature verification stub | ||
| * Dilithium signature verification stub | ||
| * Validates format and checks stub signature tag against message and public key. | ||
| * | ||
| * This is NOT real post-quantum signature verification. | ||
| * It only validates basic format, not cryptographic correctness. | ||
| * | ||
| * @param {Uint8Array|string} message - Original message | ||
@@ -271,3 +275,3 @@ * @param {Uint8Array} signature - Signature from dilithiumSign | ||
| * @param {string} level - Dilithium level | ||
| * @returns {Promise<boolean>} True if format is valid (NOT cryptographic verification!) | ||
| * @returns {Promise<boolean>} True if signature matches public key and message under stub mode | ||
| */ | ||
@@ -289,5 +293,16 @@ async dilithiumVerify(message, signature, dilithiumPublicKey, level = _WebCryptPQC.DILITHIUM_3) { | ||
| const msgBytes = typeof message === "string" ? new TextEncoder().encode(message) : message; | ||
| console.warn( | ||
| "\u26A0\uFE0F dilithiumVerify() is a PLACEHOLDER stub. This does NOT perform real post-quantum signature verification. Integrate liboqs-js for production use." | ||
| ); | ||
| const verifyInput = new Uint8Array(dilithiumPublicKey.byteLength + msgBytes.byteLength); | ||
| verifyInput.set(dilithiumPublicKey); | ||
| verifyInput.set(msgBytes, dilithiumPublicKey.byteLength); | ||
| const expectedTag = await this._sha3Hash(verifyInput, 512); | ||
| const sigTag = signature.subarray(64, 128); | ||
| const expectedTagSlice = expectedTag.subarray(0, 64); | ||
| if (sigTag.byteLength < 64 || expectedTagSlice.byteLength < 64) { | ||
| return false; | ||
| } | ||
| for (let i = 0; i < 64; i++) { | ||
| if (sigTag[i] !== expectedTagSlice[i]) { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
@@ -354,7 +369,8 @@ } | ||
| } | ||
| const rsaSecretBytes = !rsaSharedSecret ? kyberSharedSecret : rsaSharedSecret instanceof Uint8Array ? rsaSharedSecret : new Uint8Array(rsaSharedSecret); | ||
| const combinedInput = new Uint8Array( | ||
| kyberSharedSecret.byteLength + rsaSharedSecret.byteLength | ||
| kyberSharedSecret.byteLength + rsaSecretBytes.byteLength | ||
| ); | ||
| combinedInput.set(kyberSharedSecret); | ||
| combinedInput.set(new Uint8Array(rsaSharedSecret), kyberSharedSecret.byteLength); | ||
| combinedInput.set(rsaSecretBytes, kyberSharedSecret.byteLength); | ||
| const finalSharedSecret = await this._sha3Hash(combinedInput, 256); | ||
@@ -457,3 +473,6 @@ return finalSharedSecret; | ||
| const privHash = await this._sha3Hash(hashInput, 512); | ||
| privateKey.set(privHash.slice(0, Math.min(params.privateKeySize, privHash.byteLength))); | ||
| privateKey.set( | ||
| privHash.slice(0, Math.min(params.privateKeySize - params.publicKeySize, privHash.byteLength)) | ||
| ); | ||
| privateKey.set(publicKey, params.privateKeySize - params.publicKeySize); | ||
| return { publicKey, privateKey }; | ||
@@ -464,3 +483,3 @@ } | ||
| const bytes = new Uint8Array(buffer); | ||
| const CHUNK_SIZE = 1024; | ||
| const CHUNK_SIZE = 32768; | ||
| let binary = ""; | ||
@@ -478,7 +497,3 @@ for (let i = 0; i < bytes.length; i += CHUNK_SIZE) { | ||
| } | ||
| const binary = atob(padded); | ||
| const bytes = new Uint8Array(binary.length); | ||
| for (let i = 0; i < binary.length; i++) { | ||
| bytes[i] = binary.charCodeAt(i); | ||
| } | ||
| const bytes = Uint8Array.from(atob(padded), (c) => c.charCodeAt(0)); | ||
| return bytes.buffer; | ||
@@ -485,0 +500,0 @@ } |
+33
-18
@@ -234,2 +234,3 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { | ||
| const msgBytes = typeof message === "string" ? new TextEncoder().encode(message) : message; | ||
| const pubKey = dilithiumPrivateKey.subarray(params.privateKeySize - params.publicKeySize); | ||
| const hashInput = new Uint8Array(dilithiumPrivateKey.byteLength + msgBytes.byteLength); | ||
@@ -239,12 +240,15 @@ hashInput.set(dilithiumPrivateKey); | ||
| const digest = await this._sha3Hash(hashInput, 512); | ||
| const verifyInput = new Uint8Array(pubKey.byteLength + msgBytes.byteLength); | ||
| verifyInput.set(pubKey); | ||
| verifyInput.set(msgBytes, pubKey.byteLength); | ||
| const verifyTag = await this._sha3Hash(verifyInput, 512); | ||
| const signature = new Uint8Array(params.signatureSize); | ||
| signature.set(digest.slice(0, Math.min(params.signatureSize, digest.byteLength))); | ||
| signature.set(digest.slice(0, Math.min(64, digest.byteLength))); | ||
| signature.set(verifyTag.slice(0, Math.min(64, verifyTag.byteLength)), 64); | ||
| return signature; | ||
| } | ||
| /** | ||
| * โ ๏ธ PLACEHOLDER: Dilithium signature verification stub | ||
| * Dilithium signature verification stub | ||
| * Validates format and checks stub signature tag against message and public key. | ||
| * | ||
| * This is NOT real post-quantum signature verification. | ||
| * It only validates basic format, not cryptographic correctness. | ||
| * | ||
| * @param {Uint8Array|string} message - Original message | ||
@@ -254,3 +258,3 @@ * @param {Uint8Array} signature - Signature from dilithiumSign | ||
| * @param {string} level - Dilithium level | ||
| * @returns {Promise<boolean>} True if format is valid (NOT cryptographic verification!) | ||
| * @returns {Promise<boolean>} True if signature matches public key and message under stub mode | ||
| */ | ||
@@ -272,5 +276,16 @@ async dilithiumVerify(message, signature, dilithiumPublicKey, level = _WebCryptPQC.DILITHIUM_3) { | ||
| const msgBytes = typeof message === "string" ? new TextEncoder().encode(message) : message; | ||
| console.warn( | ||
| "\u26A0\uFE0F dilithiumVerify() is a PLACEHOLDER stub. This does NOT perform real post-quantum signature verification. Integrate liboqs-js for production use." | ||
| ); | ||
| const verifyInput = new Uint8Array(dilithiumPublicKey.byteLength + msgBytes.byteLength); | ||
| verifyInput.set(dilithiumPublicKey); | ||
| verifyInput.set(msgBytes, dilithiumPublicKey.byteLength); | ||
| const expectedTag = await this._sha3Hash(verifyInput, 512); | ||
| const sigTag = signature.subarray(64, 128); | ||
| const expectedTagSlice = expectedTag.subarray(0, 64); | ||
| if (sigTag.byteLength < 64 || expectedTagSlice.byteLength < 64) { | ||
| return false; | ||
| } | ||
| for (let i = 0; i < 64; i++) { | ||
| if (sigTag[i] !== expectedTagSlice[i]) { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
@@ -337,7 +352,8 @@ } | ||
| } | ||
| const rsaSecretBytes = !rsaSharedSecret ? kyberSharedSecret : rsaSharedSecret instanceof Uint8Array ? rsaSharedSecret : new Uint8Array(rsaSharedSecret); | ||
| const combinedInput = new Uint8Array( | ||
| kyberSharedSecret.byteLength + rsaSharedSecret.byteLength | ||
| kyberSharedSecret.byteLength + rsaSecretBytes.byteLength | ||
| ); | ||
| combinedInput.set(kyberSharedSecret); | ||
| combinedInput.set(new Uint8Array(rsaSharedSecret), kyberSharedSecret.byteLength); | ||
| combinedInput.set(rsaSecretBytes, kyberSharedSecret.byteLength); | ||
| const finalSharedSecret = await this._sha3Hash(combinedInput, 256); | ||
@@ -440,3 +456,6 @@ return finalSharedSecret; | ||
| const privHash = await this._sha3Hash(hashInput, 512); | ||
| privateKey.set(privHash.slice(0, Math.min(params.privateKeySize, privHash.byteLength))); | ||
| privateKey.set( | ||
| privHash.slice(0, Math.min(params.privateKeySize - params.publicKeySize, privHash.byteLength)) | ||
| ); | ||
| privateKey.set(publicKey, params.privateKeySize - params.publicKeySize); | ||
| return { publicKey, privateKey }; | ||
@@ -447,3 +466,3 @@ } | ||
| const bytes = new Uint8Array(buffer); | ||
| const CHUNK_SIZE = 1024; | ||
| const CHUNK_SIZE = 32768; | ||
| let binary = ""; | ||
@@ -461,7 +480,3 @@ for (let i = 0; i < bytes.length; i += CHUNK_SIZE) { | ||
| } | ||
| const binary = atob(padded); | ||
| const bytes = new Uint8Array(binary.length); | ||
| for (let i = 0; i < binary.length; i++) { | ||
| bytes[i] = binary.charCodeAt(i); | ||
| } | ||
| const bytes = Uint8Array.from(atob(padded), (c) => c.charCodeAt(0)); | ||
| return bytes.buffer; | ||
@@ -468,0 +483,0 @@ } |
+3
-4
| { | ||
| "name": "webcrypt", | ||
| "version": "0.6.5", | ||
| "version": "0.7.0", | ||
| "description": "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.", | ||
@@ -112,2 +112,3 @@ "license": "MIT", | ||
| "test:coverage": "node --experimental-vm-modules node_modules/.bin/jest --coverage", | ||
| "test:matrix": "bash scripts/test-matrix.sh", | ||
| "format": "prettier --write \"**/*.{js,ts,json,md}\"", | ||
@@ -136,6 +137,4 @@ "format:check": "prettier --check \"**/*.{js,ts,json,md}\"", | ||
| "overrides": { | ||
| "brace-expansion": "5.0.8", | ||
| "minimatch": "10.2.6", | ||
| "glob": "^10.5.0" | ||
| "brace-expansion": "^5.0.9" | ||
| } | ||
| } |
+39
-484
| # WebCrypt | ||
| **Zero-dependency end-to-end encryption for the modern web.** | ||
| **Zero-dependency end-to-end cryptography suite for the modern web.** | ||
| [](https://www.npmjs.com/package/webcrypt) | ||
| [](./LICENSE) | ||
| [](./__tests__) | ||
| [](./__tests__) | ||
| [](./__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. | ||
| AES-256-GCM symmetric encryption, RSA-4096 hybrid asymmetric encryption, ECDH key agreement, digital signatures, JWE compact serialization (RFC 7516), and WebRTC insertable streams โ built natively for browser and Node.js Web Crypto API environments. | ||
| --- | ||
| ## Quick Start | ||
| ## Interactive Live Demo & Documentation | ||
| ```bash | ||
| npm install webcrypt | ||
| ``` | ||
| - ๐ **[Try WebCrypt Live Playground](https://lucasarmstrong.github.io/WebCrypt/)**: Test AES-256, RSA-4096, ECDH, digital signatures, and file encryption directly in your browser. | ||
| - ๐ **[Documentation Index](./docs/)** | ||
| **Encrypt and decrypt text:** | ||
| ```js | ||
| 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:** | ||
| ```js | ||
| const { blob, filename } = await wc.encryptFile(file, "my-password"); | ||
| ``` | ||
| **Public-key encryption (RSA-4096):** | ||
| ```js | ||
| 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 | ||
| ## Installation | ||
| - [Features](#features) | ||
| - [Modules](#modules) | ||
| - [Symmetric Encryption (WebCrypt)](#symmetric-encryption-webcrypt) | ||
| - [Asymmetric Encryption (WebCryptAsym)](#asymmetric-encryption-webcryptasym) | ||
| - [HMAC](#hmac) | ||
| - [Key Derivation](#key-derivation) | ||
| - [Post-Quantum Cryptography](#post-quantum-cryptography) | ||
| - [API Reference](#api-reference) | ||
| - [Security](#security) | ||
| - [Environment Support](#environment-support) | ||
| - [License](#license) | ||
| --- | ||
| ## Features | ||
| | 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) | | ||
| --- | ||
| ## Modules | ||
| WebCrypt is split into three modules. Import only what you need: | ||
| ```js | ||
| import { WebCrypt } from "webcrypt"; // Symmetric (password-based) | ||
| import { WebCryptAsym } from "webcrypt"; // Asymmetric (public/private key) | ||
| import { WebCryptPQC } from "webcrypt"; // Post-quantum (โ ๏ธ stub) | ||
| ```bash | ||
| npm install webcrypt | ||
| ``` | ||
| | 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) | ||
| ## Quick Start Code Examples | ||
| Password-based AES-256-GCM encryption with PBKDF2 key derivation (600,000 iterations). | ||
| ### 1. Symmetric Text Encryption (AES-256-GCM) | ||
| ### Text | ||
| ```js | ||
@@ -111,429 +37,58 @@ import { WebCrypt } from "webcrypt"; | ||
| const encrypted = await wc.encryptText("The treasure is buried under the oak tree", "password"); | ||
| const decrypted = await wc.decryptText(encrypted, "password"); | ||
| const encrypted = await wc.encryptText("Secret message", "my-password"); | ||
| const decrypted = await wc.decryptText(encrypted, "my-password"); | ||
| ``` | ||
| ### JSON Data | ||
| ### 2. Large File Streaming Encryption (8MB Chunking) | ||
| ```js | ||
| 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"] | ||
| const { blob, filename } = await wc.encryptFile(file, "my-password", { parallelChunks: 4 }); | ||
| const decrypted = await wc.decryptFile(blob, "my-password"); | ||
| ``` | ||
| ### Files | ||
| ### 3. Public-Key Hybrid Encryption (RSA-4096) | ||
| ```js | ||
| // 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 | ||
| ```js | ||
| 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 | ||
| ```js | ||
| import { WebCryptAsym } from "webcrypt"; | ||
| const crypt = new WebCryptAsym(); | ||
| const wca = 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); | ||
| const keys = await wca.generateKeyPair(4096); | ||
| const encrypted = await wca.encryptText("Secret payload", keys.publicKey); | ||
| const decrypted = await wca.decryptText(encrypted, keys.privateKey); | ||
| ``` | ||
| ### ECDH Key Exchange | ||
| ### 4. ECDH Key Agreement & One-Step Encryption | ||
| Derive a shared secret between two parties without transmitting any secret material. | ||
| ```js | ||
| // Each party generates an ECDH key pair | ||
| const alice = await crypt.generateECDHKeyPair(); | ||
| const bob = await crypt.generateECDHKeyPair(); | ||
| const aliceKeys = await wca.generateECDHKeyPair("P-256"); | ||
| const bobKeys = await wca.generateECDHKeyPair("P-256"); | ||
| // 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" | ||
| const encrypted = await wca.encryptWithECDH("Confidential data", bobKeys.publicKey); | ||
| const decrypted = await wca.decryptWithECDH(encrypted, bobKeys.privateKey, aliceKeys.publicKey); | ||
| ``` | ||
| ### Digital Signatures (ECDSA) | ||
| ### 5. Digital Signatures (ECDSA P-256 / RSA-PSS) | ||
| ```js | ||
| // 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 signingKeys = await wca.generateSigningKeyPair("ECDSA", "P-256"); | ||
| const signature = await wca.signText("Tamper-proof payload", signingKeys.privateKey); | ||
| const isValid = await wca.verifyText("Tamper-proof payload", signature, signingKeys.publicKey); | ||
| ``` | ||
| ### File Encryption with Progress | ||
| ```js | ||
| 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. | ||
| ```js | ||
| 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 | ||
| ## Complete API & Technical Documentation | ||
| Message authentication codes using SHA-256, SHA-384, SHA-512, or SHA-3. | ||
| For complete method signatures, options, and advanced usage, see our detailed documentation sub-documents: | ||
| ```js | ||
| import { WebCrypt } from "webcrypt"; | ||
| const wc = new WebCrypt(); | ||
| - ๐ **[Symmetric Encryption API (`WebCrypt`)](./docs/API_SYMMETRIC.md)** โ AES-256-GCM, File Streaming, WebRTC E2EE, LRU Key Cache, HMAC. | ||
| - ๐ **[Asymmetric Encryption API (`WebCryptAsym`)](./docs/API_ASYMMETRIC.md)** โ RSA-4096 Hybrid, ECDH, Signatures, JWE RFC 7516, HKDF. | ||
| - ๐ **[Cryptographic Architecture & Security](./docs/ARCHITECTURE.md)** โ Threat Model, Timing-Safe Helpers, Grover Quantum Resistance. | ||
| - โ๏ธ **[Post-Quantum Cryptography Guide](./docs/PQC.md)** โ Kyber/Dilithium stubs & liboqs-js migration path. | ||
| - ๐ก๏ธ **[Security Policy](./SECURITY.md)** โ Vulnerability reporting and version support table. | ||
| // 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) | ||
| ```js | ||
| const key = await crypt.deriveKeyPBKDF2("password", "salt"); // 600,000 iterations | ||
| ``` | ||
| ### SHA-3 KDF | ||
| ```js | ||
| const key = await crypt.deriveKeySHA3("password", 50000, "SHA3-256"); | ||
| ``` | ||
| ### HKDF-SHA3 | ||
| ```js | ||
| const masterSecret = new TextEncoder().encode("master-password"); | ||
| const key = await crypt.deriveKeyHKDFSHA3(masterSecret, saltBytes, infoBytes, 256); | ||
| ``` | ||
| ### Key rotation and hierarchical keys | ||
| ```js | ||
| // 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](https://github.com/open-quantum-safe/liboqs) 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. | ||
| ```js | ||
| 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](./docs/PQC.md) โ includes Kyber, Dilithium, hybrid encryption, security levels, and migration path. | ||
| --- | ||
| ## API Reference | ||
| ### WebCrypt (Symmetric) | ||
| ```ts | ||
| 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.generateHmacSalt(length?: number): Uint8Array | ||
| wc.generateHmacKey(password?: string, hash?: string, salt?: Uint8Array | 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, salt?: Uint8Array | 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) | ||
| ```ts | ||
| const crypt = new WebCryptAsym(); | ||
| // Key management | ||
| crypt.generateKeyPair(modulusLength?: number): 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.exportECDHPublicKey(key: CryptoKey): Promise<string> | ||
| crypt.importECDHPublicKey(b64: string, curve?: string): Promise<CryptoKey> | ||
| crypt.deriveECDHSharedSecret(privateKey: CryptoKey, peerPublicKey: CryptoKey): Promise<CryptoKey> | ||
| crypt.encryptWithECDH(data: any, privateKey, recipientPublicKey): Promise<string> | ||
| crypt.decryptWithECDH(b64: string, privateKey, senderPublicKey): Promise<any> | ||
| // Digital Signatures (ECDSA / RSA-PSS) | ||
| crypt.generateSigningKeyPair(curve?: string): Promise<{ publicKey, privateKey, publicKeyB64 }> | ||
| crypt.generateEdDSASigningKeyPair(): Promise<{ publicKey, privateKey, publicKeyB64 }> | ||
| crypt.generateRSAPSSigningKeyPair(modulusLength?: number): Promise<{ publicKey, privateKey, publicKeyB64 }> | ||
| crypt.importPublicSigningKey(b64: string, curve?: string): Promise<CryptoKey> | ||
| 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> | ||
| crypt.signTextWithAlgorithm(text, privateKey, algorithm?: 'ECDSA' | 'RSA-PSS'): Promise<string> | ||
| crypt.verifyTextWithAlgorithm(text, sig, publicKey, algorithm?: 'ECDSA' | 'RSA-PSS'): Promise<boolean> | ||
| // JWE (JSON Web Encryption) | ||
| crypt.encryptJWE(payload: any, publicKey: CryptoKey, headers?: object): Promise<string> | ||
| crypt.decryptJWE(jweToken: string, privateKey: CryptoKey): Promise<any> | ||
| // MAC & Poly1305 | ||
| crypt.signHMAC(data: string, key: CryptoKey, hash?: string): Promise<string> | ||
| crypt.verifyHMAC(data: string, sig: string, key: CryptoKey, hash?: string): Promise<boolean> | ||
| crypt.authenticatePoly1305(data: string | Uint8Array, key: Uint8Array): Promise<string> | ||
| // Key derivation & rotation | ||
| crypt.deriveKeyPBKDF2(password, salt, iterations?, hash?, keyLength?): Promise<CryptoKey> | ||
| crypt.deriveKeyArgon2(password, salt, options?): Promise<CryptoKey> | ||
| crypt.deriveKeySHA3(password, iterations?, algorithm?): Promise<CryptoKey> | ||
| crypt.deriveKeyHKDFSHA2(secret, salt?, info?, keyLength?): Promise<CryptoKey> | ||
| crypt.deriveKeyHKDFSHA3(secret, salt?, info?, keyLength?): Promise<CryptoKey> | ||
| crypt.generateKeyFromPassword(password, salt, algorithm?): Promise<CryptoKey> | ||
| crypt.generateRotatingKey(password, salt, algorithm?, rotationCount?): Promise<CryptoKey> | ||
| crypt.generateHierarchicalKey(masterPassword, path: string[]): Promise<{ masterKey, childKeys }> | ||
| crypt.generateKeyFromMultipleInputs(inputs: string[], salt, algorithm?): Promise<CryptoKey> | ||
| crypt.rotateKeyNew(password, newSalt, method?): Promise<CryptoKey> | ||
| crypt.deriveChildKeyHierarchical(parentKey, childSalt, purpose?): Promise<CryptoKey> | ||
| crypt.secureRandom(length: number): Promise<Uint8Array> | ||
| crypt.secureKeyErase(key: Uint8Array): void | ||
| // WebRTC Insertable Streams | ||
| crypt.createEncryptTransform(publicKey: CryptoKey): Promise<TransformFunction> | ||
| crypt.createDecryptTransform(privateKey: CryptoKey): Promise<TransformFunction> | ||
| crypt.createHybridEncryptTransform(publicKey, kyberPublicKey, level?): Promise<TransformFunction> | ||
| crypt.createEncryptTransformWithProgress(publicKey, onProgress?): Promise<TransformFunction> | ||
| ``` | ||
| ### WebCryptPQC (Post-Quantum) | ||
| See [docs/PQC.md](./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.6.5) | ||
| - **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"`. | ||
| ### 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](./SECURITY.md). | ||
| For security fix details, see [SECURITY_FIXES.md](./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 | ||
| ```js | ||
| // ES Modules | ||
| import { WebCrypt } from "webcrypt"; | ||
| // CommonJS | ||
| const { WebCrypt } = require("webcrypt"); | ||
| ``` | ||
| --- | ||
| ## License | ||
| MIT License โ free for personal and commercial use. | ||
| ยฉ 2025-2026 [PuterVision LLC](https://putervision.com) | ||
| --- | ||
| ## Legal & Usage Disclaimers | ||
| > [!WARNING] | ||
| > **Limitation of Liability & Disclaimer of Warranty** | ||
| > WebCrypt is maintained by [PuterVision LLC](https://putervision.com) 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](https://putervision.com) 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](https://putervision.com) 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. | ||
| [MIT](./LICENSE) ยฉ PuterVision LLC |
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
13201
1.26%597782
-1.33%94
-82.56%