@time-file/browser-file-crypto 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +69 -10
- package/README.md +69 -14
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +264 -7
- package/dist/index.mjs +389 -194
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/index.umd.js.map +1 -1
- package/package.json +2 -2
package/dist/index.umd.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.umd.js","sources":["../src/constants.ts","../src/errors.ts","../src/utils.ts","../src/encrypt.ts","../src/decrypt.ts","../src/detect.ts","../src/keyfile.ts","../src/download.ts"],"sourcesContent":["/**\n * Cryptographic constants used throughout the library.\n * These values follow industry best practices for AES-256-GCM.\n *\n * @module constants\n * @since 1.0.0\n */\n\n/**\n * Salt length in bytes for PBKDF2 key derivation.\n * 16 bytes (128 bits) provides sufficient randomness to prevent rainbow table attacks.\n */\nexport const SALT_LENGTH = 16;\n\n/**\n * Initialization vector length in bytes for AES-GCM.\n * 12 bytes (96 bits) is the recommended IV size for AES-GCM per NIST SP 800-38D.\n */\nexport const IV_LENGTH = 12;\n\n/**\n * AES key length in bits.\n * 256 bits provides the highest security level for AES.\n */\nexport const KEY_LENGTH = 256;\n\n/**\n * PBKDF2 iteration count for key derivation.\n * 100,000 iterations balance security and performance.\n * Higher values increase resistance to brute-force attacks.\n */\nexport const PBKDF2_ITERATIONS = 100_000;\n\n/**\n * Key file raw key length in bytes (256-bit).\n * Matches the AES-256 key size requirement.\n */\nexport const KEYFILE_KEY_LENGTH = 32;\n\n/**\n * Encryption marker byte for password-based encryption.\n * Used to identify the encryption method when decrypting.\n */\nexport const ENCRYPTION_MARKER_PASSWORD = 0x01;\n\n/**\n * Encryption marker byte for keyfile-based encryption.\n * Used to identify the encryption method when decrypting.\n */\nexport const ENCRYPTION_MARKER_KEYFILE = 0x02;\n\n/**\n * AES-GCM authentication tag length in bytes.\n * 16 bytes (128 bits) is the default and recommended tag size.\n */\nexport const AUTH_TAG_LENGTH = 16;\n\n/**\n * Minimum encrypted file size for password-based encryption.\n * Header (1 + 16 + 12 = 29 bytes) + Auth tag (16 bytes) = 45 bytes.\n */\nexport const MIN_ENCRYPTED_SIZE_PASSWORD = 1 + SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH;\n\n/**\n * Minimum encrypted file size for keyfile-based encryption.\n * Header (1 + 12 = 13 bytes) + Auth tag (16 bytes) = 29 bytes.\n */\nexport const MIN_ENCRYPTED_SIZE_KEYFILE = 1 + IV_LENGTH + AUTH_TAG_LENGTH;\n\n/**\n * Algorithm identifier string for AES-GCM.\n */\nexport const ALGORITHM = 'AES-GCM' as const;\n\n/**\n * Hash algorithm used in PBKDF2 key derivation.\n */\nexport const HASH_ALGORITHM = 'SHA-256' as const;\n","/**\n * Error handling for browser-file-crypto.\n *\n * @module errors\n * @since 1.0.0\n */\n\n/**\n * Error codes for CryptoError.\n *\n * @description\n * - `INVALID_INPUT`: Input is not a valid File, Blob, or ArrayBuffer\n * - `PASSWORD_REQUIRED`: Password is required but not provided\n * - `KEYFILE_REQUIRED`: Keyfile is required but not provided\n * - `INVALID_PASSWORD`: Decryption failed due to incorrect password\n * - `INVALID_KEYFILE`: Decryption failed due to incorrect keyfile\n * - `INVALID_ENCRYPTED_DATA`: Data is corrupted or not encrypted with this library\n * - `ENCRYPTION_FAILED`: Encryption operation failed\n * - `DECRYPTION_FAILED`: Decryption operation failed\n * - `DOWNLOAD_FAILED`: File download failed\n * - `UNSUPPORTED_FORMAT`: Encrypted file format is not supported\n */\nexport type CryptoErrorCode =\n | 'INVALID_INPUT'\n | 'PASSWORD_REQUIRED'\n | 'KEYFILE_REQUIRED'\n | 'INVALID_PASSWORD'\n | 'INVALID_KEYFILE'\n | 'INVALID_ENCRYPTED_DATA'\n | 'ENCRYPTION_FAILED'\n | 'DECRYPTION_FAILED'\n | 'DOWNLOAD_FAILED'\n | 'UNSUPPORTED_FORMAT';\n\n/**\n * Error messages for each error code.\n */\nconst ERROR_MESSAGES: Record<CryptoErrorCode, string> = {\n INVALID_INPUT: 'Input must be a File, Blob, or ArrayBuffer.',\n PASSWORD_REQUIRED: 'Password or keyfile is required for encryption.',\n KEYFILE_REQUIRED: 'Keyfile is required to decrypt this file.',\n INVALID_PASSWORD: 'Decryption failed: incorrect password.',\n INVALID_KEYFILE: 'Decryption failed: incorrect keyfile.',\n INVALID_ENCRYPTED_DATA: 'Invalid encrypted data: file may be corrupted.',\n ENCRYPTION_FAILED: 'Encryption failed.',\n DECRYPTION_FAILED: 'Decryption failed.',\n DOWNLOAD_FAILED: 'File download failed.',\n UNSUPPORTED_FORMAT: 'Unsupported encryption format.',\n};\n\n/**\n * Custom error class for crypto operations.\n *\n * @description\n * Provides structured error handling with error codes for programmatic handling.\n * Each error includes a code that can be used for i18n or specific error handling.\n *\n * @example\n * ```typescript\n * try {\n * await decryptFile(data, { password: 'wrong' });\n * } catch (error) {\n * if (error instanceof CryptoError) {\n * console.log(error.code); // 'INVALID_PASSWORD'\n * console.log(error.message); // 'Decryption failed: incorrect password.'\n *\n * switch (error.code) {\n * case 'INVALID_PASSWORD':\n * showPasswordError();\n * break;\n * case 'INVALID_ENCRYPTED_DATA':\n * showCorruptedFileError();\n * break;\n * }\n * }\n * }\n * ```\n *\n * @see {@link CryptoErrorCode} for all available error codes\n * @since 1.0.0\n */\nexport class CryptoError extends Error {\n /** Error code for programmatic handling */\n readonly code: CryptoErrorCode;\n\n /**\n * Creates a new CryptoError.\n *\n * @param code - Error code identifying the type of error\n * @param message - Optional custom message (defaults to predefined message)\n */\n constructor(code: CryptoErrorCode, message?: string) {\n super(message ?? ERROR_MESSAGES[code]);\n this.name = 'CryptoError';\n this.code = code;\n\n // Maintains proper stack trace in V8 environments (Chrome, Node.js)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, CryptoError);\n }\n }\n}\n\n/**\n * Type guard to check if an error is a CryptoError.\n *\n * @param error - The error to check\n * @returns True if the error is a CryptoError\n *\n * @example\n * ```typescript\n * try {\n * await decryptFile(data, options);\n * } catch (error) {\n * if (isCryptoError(error)) {\n * console.log(error.code);\n * }\n * }\n * ```\n */\nexport function isCryptoError(error: unknown): error is CryptoError {\n return error instanceof CryptoError;\n}\n","/**\n * Utility functions for browser-file-crypto.\n *\n * @module utils\n * @internal\n * @since 1.0.0\n */\n\nimport {\n SALT_LENGTH,\n IV_LENGTH,\n KEY_LENGTH,\n PBKDF2_ITERATIONS,\n ALGORITHM,\n HASH_ALGORITHM,\n} from './constants';\nimport { CryptoError } from './errors';\n\n/**\n * Normalizes input to ArrayBuffer for consistent processing.\n *\n * @description\n * Accepts File, Blob, or ArrayBuffer and converts to ArrayBuffer.\n * This ensures all encryption/decryption functions work with a consistent type.\n *\n * @param input - File, Blob, or ArrayBuffer to normalize\n * @returns Promise resolving to ArrayBuffer\n *\n * @throws {CryptoError} When input is not a valid type (INVALID_INPUT)\n *\n * @example\n * ```typescript\n * const file = document.querySelector('input').files[0];\n * const buffer = await normalizeInput(file);\n * ```\n *\n * @internal\n */\nexport async function normalizeInput(\n input: File | Blob | ArrayBuffer\n): Promise<ArrayBuffer> {\n if (input instanceof ArrayBuffer) {\n return input;\n }\n\n if (input instanceof Blob) {\n return input.arrayBuffer();\n }\n\n throw new CryptoError('INVALID_INPUT');\n}\n\n/**\n * Safely slices a TypedArray's buffer to get exact range.\n *\n * @description\n * Required because TypedArray.buffer may reference a larger ArrayBuffer\n * when the TypedArray is a view into a portion of the buffer.\n * This function ensures we get only the exact bytes we need.\n *\n * @param arr - Uint8Array to slice\n * @returns ArrayBuffer containing only the bytes from the Uint8Array\n *\n * @example\n * ```typescript\n * const iv = new Uint8Array(12);\n * const ivBuffer = sliceBuffer(iv);\n * // ivBuffer is guaranteed to be exactly 12 bytes\n * ```\n *\n * @internal\n */\nexport function sliceBuffer(arr: Uint8Array): ArrayBuffer {\n return (arr.buffer as ArrayBuffer).slice(arr.byteOffset, arr.byteOffset + arr.byteLength);\n}\n\n/**\n * Converts ArrayBuffer to base64 string.\n *\n * @description\n * Uses browser's native btoa() for encoding.\n * Handles binary data by converting each byte to a character.\n *\n * @param buffer - ArrayBuffer to encode\n * @returns Base64-encoded string\n *\n * @example\n * ```typescript\n * const key = crypto.getRandomValues(new Uint8Array(32));\n * const base64Key = arrayBufferToBase64(key.buffer);\n * ```\n *\n * @internal\n */\nexport function arrayBufferToBase64(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = '';\n for (let i = 0; i < bytes.byteLength; i++) {\n binary += String.fromCharCode(bytes[i]!);\n }\n return btoa(binary);\n}\n\n/**\n * Converts base64 string to ArrayBuffer.\n *\n * @description\n * Uses browser's native atob() for decoding.\n * Returns an ArrayBuffer that can be used with Web Crypto API.\n *\n * @param base64 - Base64-encoded string to decode\n * @returns ArrayBuffer containing the decoded bytes\n *\n * @throws {DOMException} When base64 string is invalid\n *\n * @example\n * ```typescript\n * const keyBuffer = base64ToArrayBuffer(keyFile.key);\n * const cryptoKey = await crypto.subtle.importKey('raw', keyBuffer, ...);\n * ```\n *\n * @internal\n */\nexport function base64ToArrayBuffer(base64: string): ArrayBuffer {\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes.buffer;\n}\n\n/**\n * Derives an AES-256 encryption key from a password using PBKDF2.\n *\n * @description\n * Uses PBKDF2 with SHA-256 and 100,000 iterations to derive a 256-bit key.\n * The derived key is non-extractable for security.\n *\n * @param password - The user-provided password string\n * @param salt - 16-byte random salt for key derivation\n * @returns Promise resolving to a non-extractable CryptoKey\n *\n * @throws {Error} When key derivation fails\n *\n * @example\n * ```typescript\n * const salt = crypto.getRandomValues(new Uint8Array(16));\n * const key = await deriveKeyFromPassword('my-password', salt);\n * ```\n *\n * @internal\n */\nexport async function deriveKeyFromPassword(\n password: string,\n salt: Uint8Array\n): Promise<CryptoKey> {\n const encoder = new TextEncoder();\n\n // Import password as raw key material\n const keyMaterial = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(password),\n 'PBKDF2',\n false,\n ['deriveKey']\n );\n\n // Derive AES-256 key using PBKDF2\n return crypto.subtle.deriveKey(\n {\n name: 'PBKDF2',\n salt: sliceBuffer(salt),\n iterations: PBKDF2_ITERATIONS,\n hash: HASH_ALGORITHM,\n },\n keyMaterial,\n { name: ALGORITHM, length: KEY_LENGTH },\n false, // non-extractable for security\n ['encrypt', 'decrypt']\n );\n}\n\n/**\n * Imports raw key bytes for keyfile-based encryption.\n *\n * @description\n * Imports a 256-bit key directly from base64-encoded key data.\n * No key derivation is needed - the key is used as-is.\n * The imported key is non-extractable for security.\n *\n * @param keyData - Base64-encoded 256-bit key string\n * @returns Promise resolving to a non-extractable CryptoKey\n *\n * @throws {CryptoError} When key import fails (INVALID_KEYFILE)\n *\n * @example\n * ```typescript\n * const keyFile = generateKeyFile();\n * const cryptoKey = await importKeyFromKeyfile(keyFile.key);\n * ```\n *\n * @internal\n */\nexport async function importKeyFromKeyfile(keyData: string): Promise<CryptoKey> {\n try {\n const keyBuffer = base64ToArrayBuffer(keyData);\n\n return await crypto.subtle.importKey(\n 'raw',\n keyBuffer,\n { name: ALGORITHM, length: KEY_LENGTH },\n false, // non-extractable for security\n ['encrypt', 'decrypt']\n );\n } catch {\n throw new CryptoError('INVALID_KEYFILE');\n }\n}\n\n/**\n * Generates cryptographically secure random bytes.\n *\n * @description\n * Uses crypto.getRandomValues() for cryptographically secure randomness.\n *\n * @param length - Number of random bytes to generate\n * @returns Uint8Array containing random bytes\n *\n * @example\n * ```typescript\n * const salt = generateRandomBytes(SALT_LENGTH);\n * const iv = generateRandomBytes(IV_LENGTH);\n * ```\n *\n * @internal\n */\nexport function generateRandomBytes(length: number): Uint8Array {\n return crypto.getRandomValues(new Uint8Array(length));\n}\n\n/**\n * Generates a random salt for PBKDF2 key derivation.\n *\n * @returns 16-byte random salt\n *\n * @internal\n */\nexport function generateSalt(): Uint8Array {\n return generateRandomBytes(SALT_LENGTH);\n}\n\n/**\n * Generates a random IV for AES-GCM encryption.\n *\n * @returns 12-byte random IV\n *\n * @internal\n */\nexport function generateIV(): Uint8Array {\n return generateRandomBytes(IV_LENGTH);\n}\n\n/**\n * Default character set for random password generation.\n * Includes uppercase, lowercase, numbers, and special characters.\n */\nconst PASSWORD_CHARSET =\n 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*';\n\n/**\n * Default password length.\n */\nconst DEFAULT_PASSWORD_LENGTH = 16;\n\n/**\n * Generates a cryptographically secure random password.\n *\n * @description\n * Creates a random password using crypto.getRandomValues() for\n * cryptographic randomness. The password includes:\n * - Uppercase letters (A-Z)\n * - Lowercase letters (a-z)\n * - Numbers (0-9)\n * - Special characters (!@#$%^&*)\n *\n * @param length - Password length (default: 16, min: 8, max: 128)\n * @returns Random password string\n *\n * @example\n * ```typescript\n * const password = generateRandomPassword();\n * console.log(password); // e.g., 'Kx9#mP2$vL5@nQ8!'\n *\n * const longPassword = generateRandomPassword(32);\n * console.log(longPassword.length); // 32\n * ```\n *\n * @since 1.0.0\n */\nexport function generateRandomPassword(length: number = DEFAULT_PASSWORD_LENGTH): string {\n // Clamp length to reasonable bounds\n const safeLength = Math.max(8, Math.min(128, length));\n\n const randomBytes = generateRandomBytes(safeLength);\n let password = '';\n\n for (let i = 0; i < safeLength; i++) {\n const index = randomBytes[i]! % PASSWORD_CHARSET.length;\n password += PASSWORD_CHARSET[index];\n }\n\n return password;\n}\n","/**\n * File encryption functions for browser-file-crypto.\n *\n * @module encrypt\n * @since 1.0.0\n */\n\nimport {\n SALT_LENGTH,\n IV_LENGTH,\n ENCRYPTION_MARKER_PASSWORD,\n ENCRYPTION_MARKER_KEYFILE,\n ALGORITHM,\n} from './constants';\nimport { CryptoError } from './errors';\nimport type { EncryptOptions } from './types';\nimport {\n normalizeInput,\n sliceBuffer,\n deriveKeyFromPassword,\n importKeyFromKeyfile,\n generateSalt,\n generateIV,\n} from './utils';\n\n/**\n * Encrypts a file using AES-256-GCM with password-based key derivation.\n *\n * @description\n * Uses PBKDF2 with 100,000 iterations to derive a 256-bit key from the password.\n * Each encryption generates a unique random salt (16 bytes) and IV (12 bytes).\n * The output format is: [marker(1)] + [salt(16)] + [iv(12)] + [ciphertext + auth tag].\n *\n * For keyfile-based encryption, the key is used directly without derivation.\n * The output format is: [marker(1)] + [iv(12)] + [ciphertext + auth tag].\n *\n * @param file - The file to encrypt (File, Blob, or ArrayBuffer)\n * @param options - Encryption options including password or keyData\n * @returns Promise resolving to encrypted Blob with 'application/octet-stream' MIME type\n *\n * @throws {CryptoError} When neither password nor keyData is provided (PASSWORD_REQUIRED)\n * @throws {CryptoError} When input is invalid (INVALID_INPUT)\n * @throws {CryptoError} When encryption fails (ENCRYPTION_FAILED)\n *\n * @example\n * ```typescript\n * // Password-based encryption\n * const encrypted = await encryptFile(file, {\n * password: 'my-secret',\n * onProgress: ({ phase, progress }) => console.log(`${phase}: ${progress}%`)\n * });\n *\n * // Keyfile-based encryption\n * const keyFile = generateKeyFile();\n * const encrypted = await encryptFile(file, {\n * keyData: keyFile.key,\n * onProgress: ({ progress }) => updateProgressBar(progress)\n * });\n * ```\n *\n * @see {@link decryptFile} for decryption\n * @see {@link generateKeyFile} for creating keyfiles\n * @since 1.0.0\n */\nexport async function encryptFile(\n file: File | Blob | ArrayBuffer,\n options: EncryptOptions\n): Promise<Blob> {\n const { password, keyData, onProgress } = options;\n\n // Validate: either password or keyData must be provided\n if (!password && !keyData) {\n throw new CryptoError('PASSWORD_REQUIRED');\n }\n\n try {\n // Step 1: Normalize input to ArrayBuffer\n onProgress?.({ phase: 'deriving_key', progress: 0 });\n const data = await normalizeInput(file);\n onProgress?.({ phase: 'deriving_key', progress: 10 });\n\n // Branch based on encryption method\n if (keyData) {\n return await encryptWithKeyfile(data, keyData, onProgress);\n } else {\n return await encryptWithPassword(data, password!, onProgress);\n }\n } catch (error) {\n if (error instanceof CryptoError) {\n throw error;\n }\n throw new CryptoError('ENCRYPTION_FAILED');\n }\n}\n\n/**\n * Encrypts data using password-based key derivation.\n *\n * @internal\n */\nasync function encryptWithPassword(\n data: ArrayBuffer,\n password: string,\n onProgress?: EncryptOptions['onProgress']\n): Promise<Blob> {\n // Step 2: Generate random salt and IV\n const salt = generateSalt();\n const iv = generateIV();\n onProgress?.({ phase: 'deriving_key', progress: 20 });\n\n // Step 3: Derive key from password\n const key = await deriveKeyFromPassword(password, salt);\n onProgress?.({ phase: 'encrypting', progress: 30 });\n\n // Step 4: Encrypt with AES-GCM\n const ciphertext = await crypto.subtle.encrypt(\n { name: ALGORITHM, iv: sliceBuffer(iv) },\n key,\n data\n );\n onProgress?.({ phase: 'encrypting', progress: 90 });\n\n // Step 5: Assemble output: marker + salt + iv + ciphertext\n const result = new Uint8Array(\n 1 + SALT_LENGTH + IV_LENGTH + ciphertext.byteLength\n );\n result[0] = ENCRYPTION_MARKER_PASSWORD;\n result.set(salt, 1);\n result.set(iv, 1 + SALT_LENGTH);\n result.set(new Uint8Array(ciphertext), 1 + SALT_LENGTH + IV_LENGTH);\n\n onProgress?.({ phase: 'complete', progress: 100 });\n\n return new Blob([result], { type: 'application/octet-stream' });\n}\n\n/**\n * Encrypts data using keyfile-based encryption.\n *\n * @internal\n */\nasync function encryptWithKeyfile(\n data: ArrayBuffer,\n keyData: string,\n onProgress?: EncryptOptions['onProgress']\n): Promise<Blob> {\n // Step 2: Generate random IV (no salt needed for keyfile)\n const iv = generateIV();\n onProgress?.({ phase: 'deriving_key', progress: 20 });\n\n // Step 3: Import key from keyfile\n const key = await importKeyFromKeyfile(keyData);\n onProgress?.({ phase: 'encrypting', progress: 30 });\n\n // Step 4: Encrypt with AES-GCM\n const ciphertext = await crypto.subtle.encrypt(\n { name: ALGORITHM, iv: sliceBuffer(iv) },\n key,\n data\n );\n onProgress?.({ phase: 'encrypting', progress: 90 });\n\n // Step 5: Assemble output: marker + iv + ciphertext\n const result = new Uint8Array(1 + IV_LENGTH + ciphertext.byteLength);\n result[0] = ENCRYPTION_MARKER_KEYFILE;\n result.set(iv, 1);\n result.set(new Uint8Array(ciphertext), 1 + IV_LENGTH);\n\n onProgress?.({ phase: 'complete', progress: 100 });\n\n return new Blob([result], { type: 'application/octet-stream' });\n}\n","/**\n * File decryption functions for browser-file-crypto.\n *\n * @module decrypt\n * @since 1.0.0\n */\n\nimport {\n SALT_LENGTH,\n IV_LENGTH,\n ENCRYPTION_MARKER_PASSWORD,\n ENCRYPTION_MARKER_KEYFILE,\n MIN_ENCRYPTED_SIZE_PASSWORD,\n MIN_ENCRYPTED_SIZE_KEYFILE,\n ALGORITHM,\n} from './constants';\nimport { CryptoError } from './errors';\nimport type { DecryptOptions } from './types';\nimport {\n normalizeInput,\n sliceBuffer,\n deriveKeyFromPassword,\n importKeyFromKeyfile,\n} from './utils';\n\n/**\n * Decrypts a file that was encrypted with encryptFile.\n *\n * @description\n * Automatically detects whether the file was encrypted with password or keyfile\n * based on the marker byte, then decrypts accordingly.\n *\n * For password-encrypted files:\n * - Extracts salt and IV from header\n * - Derives key using PBKDF2\n * - Decrypts with AES-GCM\n *\n * For keyfile-encrypted files:\n * - Extracts IV from header\n * - Imports key directly\n * - Decrypts with AES-GCM\n *\n * @param encrypted - The encrypted data (Blob or ArrayBuffer)\n * @param options - Decryption options including password or keyData\n * @returns Promise resolving to decrypted Blob\n *\n * @throws {CryptoError} When password is required but not provided (PASSWORD_REQUIRED)\n * @throws {CryptoError} When keyfile is required but not provided (KEYFILE_REQUIRED)\n * @throws {CryptoError} When password is incorrect (INVALID_PASSWORD)\n * @throws {CryptoError} When keyfile is incorrect (INVALID_KEYFILE)\n * @throws {CryptoError} When data is corrupted (INVALID_ENCRYPTED_DATA)\n *\n * @example\n * ```typescript\n * // Password-based decryption\n * try {\n * const decrypted = await decryptFile(encryptedBlob, {\n * password: 'my-secret',\n * onProgress: ({ phase, progress }) => console.log(`${phase}: ${progress}%`)\n * });\n * } catch (error) {\n * if (error instanceof CryptoError && error.code === 'INVALID_PASSWORD') {\n * console.log('Wrong password!');\n * }\n * }\n *\n * // Keyfile-based decryption\n * const decrypted = await decryptFile(encryptedBlob, {\n * keyData: keyFile.key\n * });\n * ```\n *\n * @see {@link encryptFile} for encryption\n * @see {@link getEncryptionType} for detecting encryption method\n * @since 1.0.0\n */\nexport async function decryptFile(\n encrypted: Blob | ArrayBuffer,\n options: DecryptOptions\n): Promise<Blob> {\n const { password, keyData, onProgress } = options;\n\n try {\n // Step 1: Normalize input to ArrayBuffer\n onProgress?.({ phase: 'decrypting', progress: 0 });\n const data = new Uint8Array(await normalizeInput(encrypted));\n onProgress?.({ phase: 'decrypting', progress: 5 });\n\n // Step 2: Check minimum size and read marker\n if (data.length < 1) {\n throw new CryptoError('INVALID_ENCRYPTED_DATA');\n }\n\n const marker = data[0];\n\n // Step 3: Branch based on encryption method\n if (marker === ENCRYPTION_MARKER_PASSWORD) {\n if (!password) {\n throw new CryptoError('PASSWORD_REQUIRED');\n }\n return await decryptWithPassword(data, password, onProgress);\n } else if (marker === ENCRYPTION_MARKER_KEYFILE) {\n if (!keyData) {\n throw new CryptoError('KEYFILE_REQUIRED');\n }\n return await decryptWithKeyfile(data, keyData, onProgress);\n } else {\n throw new CryptoError('UNSUPPORTED_FORMAT');\n }\n } catch (error) {\n if (error instanceof CryptoError) {\n throw error;\n }\n throw new CryptoError('DECRYPTION_FAILED');\n }\n}\n\n/**\n * Decrypts data that was encrypted with password.\n *\n * @internal\n */\nasync function decryptWithPassword(\n data: Uint8Array,\n password: string,\n onProgress?: DecryptOptions['onProgress']\n): Promise<Blob> {\n // Validate minimum size\n if (data.length < MIN_ENCRYPTED_SIZE_PASSWORD) {\n throw new CryptoError('INVALID_ENCRYPTED_DATA');\n }\n\n onProgress?.({ phase: 'deriving_key', progress: 10 });\n\n // Extract components: marker(1) + salt(16) + iv(12) + ciphertext\n const salt = data.slice(1, 1 + SALT_LENGTH);\n const iv = data.slice(1 + SALT_LENGTH, 1 + SALT_LENGTH + IV_LENGTH);\n const ciphertext = data.slice(1 + SALT_LENGTH + IV_LENGTH);\n\n // Derive key from password\n const key = await deriveKeyFromPassword(password, salt);\n onProgress?.({ phase: 'decrypting', progress: 30 });\n\n // Decrypt with AES-GCM\n try {\n const plaintext = await crypto.subtle.decrypt(\n { name: ALGORITHM, iv: sliceBuffer(iv) },\n key,\n ciphertext\n );\n onProgress?.({ phase: 'complete', progress: 100 });\n\n return new Blob([plaintext]);\n } catch {\n throw new CryptoError('INVALID_PASSWORD');\n }\n}\n\n/**\n * Decrypts data that was encrypted with keyfile.\n *\n * @internal\n */\nasync function decryptWithKeyfile(\n data: Uint8Array,\n keyData: string,\n onProgress?: DecryptOptions['onProgress']\n): Promise<Blob> {\n // Validate minimum size\n if (data.length < MIN_ENCRYPTED_SIZE_KEYFILE) {\n throw new CryptoError('INVALID_ENCRYPTED_DATA');\n }\n\n onProgress?.({ phase: 'decrypting', progress: 10 });\n\n // Extract components: marker(1) + iv(12) + ciphertext\n const iv = data.slice(1, 1 + IV_LENGTH);\n const ciphertext = data.slice(1 + IV_LENGTH);\n\n // Import key from keyfile\n const key = await importKeyFromKeyfile(keyData);\n onProgress?.({ phase: 'decrypting', progress: 30 });\n\n // Decrypt with AES-GCM\n try {\n const plaintext = await crypto.subtle.decrypt(\n { name: ALGORITHM, iv: sliceBuffer(iv) },\n key,\n ciphertext\n );\n onProgress?.({ phase: 'complete', progress: 100 });\n\n return new Blob([plaintext]);\n } catch {\n throw new CryptoError('INVALID_KEYFILE');\n }\n}\n","/**\n * Encryption type detection functions for browser-file-crypto.\n *\n * @module detect\n * @since 1.0.0\n */\n\nimport {\n ENCRYPTION_MARKER_PASSWORD,\n ENCRYPTION_MARKER_KEYFILE,\n MIN_ENCRYPTED_SIZE_PASSWORD,\n MIN_ENCRYPTED_SIZE_KEYFILE,\n} from './constants';\nimport type { EncryptionType } from './types';\nimport { normalizeInput } from './utils';\n\n/**\n * Detects the encryption type of encrypted data.\n *\n * @description\n * Reads the first byte (marker) of the encrypted data to determine\n * whether it was encrypted with a password or keyfile.\n *\n * - Marker 0x01: Password-based encryption\n * - Marker 0x02: Keyfile-based encryption\n * - Other: Unknown format\n *\n * @param data - The encrypted data (Blob or ArrayBuffer)\n * @returns Promise resolving to encryption type\n *\n * @example\n * ```typescript\n * const type = await getEncryptionType(encryptedBlob);\n *\n * switch (type) {\n * case 'password':\n * // Show password input\n * break;\n * case 'keyfile':\n * // Show keyfile picker\n * break;\n * case 'unknown':\n * // Show error: not encrypted with this library\n * break;\n * }\n * ```\n *\n * @see {@link isEncryptedFile} for simple encryption check\n * @since 1.0.0\n */\nexport async function getEncryptionType(\n data: Blob | ArrayBuffer\n): Promise<EncryptionType> {\n const buffer = await normalizeInput(data);\n const bytes = new Uint8Array(buffer);\n\n if (bytes.length < 1) {\n return 'unknown';\n }\n\n const marker = bytes[0];\n\n if (marker === ENCRYPTION_MARKER_PASSWORD) {\n return 'password';\n }\n\n if (marker === ENCRYPTION_MARKER_KEYFILE) {\n return 'keyfile';\n }\n\n return 'unknown';\n}\n\n/**\n * Checks if data appears to be encrypted with this library.\n *\n * @description\n * Performs a quick check to determine if the data was likely encrypted\n * with browser-file-crypto. This checks:\n * 1. The marker byte is valid (0x01 or 0x02)\n * 2. The data meets minimum size requirements\n *\n * Note: This is not a cryptographic verification. It only checks\n * the format markers and size constraints.\n *\n * @param data - The data to check (Blob or ArrayBuffer)\n * @returns Promise resolving to true if data appears to be encrypted\n *\n * @example\n * ```typescript\n * const file = event.target.files[0];\n * const isEncrypted = await isEncryptedFile(file);\n *\n * if (isEncrypted) {\n * showDecryptionUI();\n * } else {\n * showEncryptionUI();\n * }\n * ```\n *\n * @since 1.0.0\n */\nexport async function isEncryptedFile(data: Blob | ArrayBuffer): Promise<boolean> {\n const buffer = await normalizeInput(data);\n const bytes = new Uint8Array(buffer);\n\n if (bytes.length < 1) {\n return false;\n }\n\n const marker = bytes[0];\n\n // Check password-encrypted format\n if (\n marker === ENCRYPTION_MARKER_PASSWORD &&\n bytes.length >= MIN_ENCRYPTED_SIZE_PASSWORD\n ) {\n return true;\n }\n\n // Check keyfile-encrypted format\n if (\n marker === ENCRYPTION_MARKER_KEYFILE &&\n bytes.length >= MIN_ENCRYPTED_SIZE_KEYFILE\n ) {\n return true;\n }\n\n return false;\n}\n","/**\n * Keyfile generation and management functions for browser-file-crypto.\n *\n * @module keyfile\n * @since 1.0.0\n */\n\nimport { KEYFILE_KEY_LENGTH } from './constants';\nimport type { KeyFile } from './types';\nimport { arrayBufferToBase64, base64ToArrayBuffer, generateRandomBytes } from './utils';\n\n/**\n * Generates a new keyfile with a 256-bit random key.\n *\n * @description\n * Creates a cryptographically secure 256-bit (32 bytes) random key\n * using crypto.getRandomValues(). The key is returned as a KeyFile\n * object with metadata including version, algorithm, and creation timestamp.\n *\n * The generated key can be used directly with encryptFile() and decryptFile()\n * without any key derivation (unlike password-based encryption).\n *\n * @returns KeyFile object containing the generated key\n *\n * @example\n * ```typescript\n * const keyFile = generateKeyFile();\n * console.log(keyFile);\n * // {\n * // version: 1,\n * // algorithm: 'AES-256-GCM',\n * // key: 'base64-encoded-32-bytes...',\n * // createdAt: '2025-01-01T12:00:00.000Z'\n * // }\n *\n * // Use for encryption\n * const encrypted = await encryptFile(file, { keyData: keyFile.key });\n *\n * // Save keyfile for later\n * downloadKeyFile(keyFile.key, 'my-encryption-key');\n * ```\n *\n * @see {@link downloadKeyFile} for saving the keyfile\n * @see {@link parseKeyFile} for loading a saved keyfile\n * @since 1.0.0\n */\nexport function generateKeyFile(): KeyFile {\n const keyBytes = generateRandomBytes(KEYFILE_KEY_LENGTH);\n const key = arrayBufferToBase64(keyBytes.buffer as ArrayBuffer);\n\n return {\n version: 1,\n algorithm: 'AES-256-GCM',\n key,\n createdAt: new Date().toISOString(),\n };\n}\n\n/**\n * Parses a keyfile from JSON string content.\n *\n * @description\n * Validates and parses a JSON string into a KeyFile object.\n * Performs validation to ensure:\n * - Valid JSON format\n * - Required fields present (version, algorithm, key)\n * - Correct version (1)\n * - Correct algorithm ('AES-256-GCM')\n * - Key is a non-empty string\n *\n * @param content - JSON string content of the keyfile\n * @returns KeyFile object if valid, null if invalid\n *\n * @example\n * ```typescript\n * // From file input\n * const fileInput = document.querySelector('input[type=\"file\"]');\n * fileInput.addEventListener('change', async (e) => {\n * const file = e.target.files[0];\n * const content = await file.text();\n * const keyFile = parseKeyFile(content);\n *\n * if (keyFile) {\n * const decrypted = await decryptFile(encrypted, { keyData: keyFile.key });\n * } else {\n * console.error('Invalid keyfile format');\n * }\n * });\n * ```\n *\n * @see {@link generateKeyFile} for creating keyfiles\n * @since 1.0.0\n */\nexport function parseKeyFile(content: string): KeyFile | null {\n try {\n const parsed = JSON.parse(content) as unknown;\n\n // Type guard and validation\n if (!isValidKeyFile(parsed)) {\n return null;\n }\n\n return parsed;\n } catch {\n return null;\n }\n}\n\n/**\n * Validates if an object is a valid KeyFile.\n *\n * @internal\n */\nfunction isValidKeyFile(obj: unknown): obj is KeyFile {\n if (typeof obj !== 'object' || obj === null) {\n return false;\n }\n\n const candidate = obj as Record<string, unknown>;\n\n return (\n candidate.version === 1 &&\n candidate.algorithm === 'AES-256-GCM' &&\n typeof candidate.key === 'string' &&\n candidate.key.length > 0 &&\n typeof candidate.createdAt === 'string'\n );\n}\n\n/**\n * Downloads a keyfile as a JSON file with customizable extension.\n *\n * @description\n * Creates a downloadable JSON file containing the keyfile data.\n * The file extension can be customized (default: .key).\n *\n * Note: This function uses browser APIs (URL.createObjectURL, document.createElement)\n * and will not work in Node.js or Web Worker environments.\n *\n * @param keyData - Base64-encoded key string (from KeyFile.key)\n * @param fileName - Name for the downloaded file (without extension)\n * @param extension - File extension without dot (default: 'key')\n *\n * @example\n * ```typescript\n * const keyFile = generateKeyFile();\n *\n * // Downloads as 'my-secret-key.key' (default)\n * downloadKeyFile(keyFile.key, 'my-secret-key');\n *\n * // Downloads as 'my-secret-key.tfkey' (custom extension)\n * downloadKeyFile(keyFile.key, 'my-secret-key', 'tfkey');\n *\n * // The downloaded file contains:\n * // {\n * // \"version\": 1,\n * // \"algorithm\": \"AES-256-GCM\",\n * // \"key\": \"base64...\",\n * // \"createdAt\": \"2025-01-01T00:00:00.000Z\"\n * // }\n * ```\n *\n * @since 1.0.0\n */\nexport function downloadKeyFile(keyData: string, fileName: string, extension: string = 'key'): void {\n const keyFile: KeyFile = {\n version: 1,\n algorithm: 'AES-256-GCM',\n key: keyData,\n createdAt: new Date().toISOString(),\n };\n\n const json = JSON.stringify(keyFile, null, 2);\n const blob = new Blob([json], { type: 'application/json' });\n const url = URL.createObjectURL(blob);\n\n const link = document.createElement('a');\n link.href = url;\n link.download = `${fileName}.${extension}`;\n link.style.display = 'none';\n\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n\n URL.revokeObjectURL(url);\n}\n\n/**\n * Computes SHA-256 hash of a keyfile's key data.\n *\n * @description\n * Generates a SHA-256 hash of the key data for server-side verification.\n * This allows the server to verify that the client has the correct keyfile\n * without ever seeing the actual key.\n *\n * Use case: Store the hash on the server, then verify client-provided\n * hash matches before allowing access to encrypted content.\n *\n * @param keyData - Base64-encoded key string (from KeyFile.key)\n * @returns Promise resolving to hex-encoded SHA-256 hash\n *\n * @example\n * ```typescript\n * const keyFile = generateKeyFile();\n * const hash = await computeKeyFileHash(keyFile.key);\n * console.log(hash); // '3a7bd3e2c1f8...' (64 hex characters)\n *\n * // Send hash to server for storage\n * await fetch('/api/store-key-hash', {\n * method: 'POST',\n * body: JSON.stringify({ hash })\n * });\n *\n * // Later, verify keyfile by comparing hashes\n * const uploadedHash = await computeKeyFileHash(uploadedKeyFile.key);\n * const isValid = uploadedHash === storedHash;\n * ```\n *\n * @since 1.0.0\n */\nexport async function computeKeyFileHash(keyData: string): Promise<string> {\n const keyBuffer = base64ToArrayBuffer(keyData);\n const hashBuffer = await crypto.subtle.digest('SHA-256', keyBuffer);\n const hashArray = new Uint8Array(hashBuffer);\n\n // Convert to hex string\n return Array.from(hashArray)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n","/**\n * Download and decrypt functions for browser-file-crypto.\n *\n * @module download\n * @since 1.0.0\n */\n\nimport { CryptoError } from './errors';\nimport type { DownloadDecryptOptions } from './types';\nimport { decryptFile } from './decrypt';\n\n/**\n * Downloads an encrypted file from URL, decrypts it, and saves to disk.\n *\n * @description\n * Combines file download, decryption, and save into a single operation.\n * Progress callback reports both download and decryption phases.\n *\n * Phases:\n * 1. `downloading` (0-50%): Fetching file from URL\n * 2. `deriving_key` (50-60%): Key derivation (password mode only)\n * 3. `decrypting` (60-95%): AES-GCM decryption\n * 4. `complete` (100%): File saved\n *\n * Note: This function uses browser APIs (fetch, URL.createObjectURL, document.createElement)\n * and will not work in Node.js environments.\n *\n * @param url - URL of the encrypted file to download\n * @param options - Options including password/keyData and fileName\n * @returns Promise that resolves when file is saved\n *\n * @throws {CryptoError} When download fails (DOWNLOAD_FAILED)\n * @throws {CryptoError} When decryption fails (see decryptFile errors)\n *\n * @example\n * ```typescript\n * await downloadAndDecrypt('https://example.com/secret.enc', {\n * password: 'my-secret',\n * fileName: 'document.pdf',\n * onProgress: ({ phase, progress }) => {\n * console.log(`${phase}: ${progress}%`);\n * // downloading: 25%\n * // downloading: 50%\n * // deriving_key: 55%\n * // decrypting: 80%\n * // complete: 100%\n * }\n * });\n * ```\n *\n * @see {@link decryptFile} for decryption only\n * @since 1.0.0\n */\nexport async function downloadAndDecrypt(\n url: string,\n options: DownloadDecryptOptions\n): Promise<void> {\n const { fileName, onProgress, ...decryptOptions } = options;\n\n try {\n // Phase 1: Download file\n onProgress?.({ phase: 'downloading', progress: 0 });\n\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new CryptoError(\n 'DOWNLOAD_FAILED',\n `Download failed: ${response.status} ${response.statusText}`\n );\n }\n\n // Track download progress if content-length is available\n const contentLength = response.headers.get('content-length');\n let encryptedData: ArrayBuffer;\n\n if (contentLength && response.body) {\n encryptedData = await downloadWithProgress(\n response.body,\n parseInt(contentLength, 10),\n (downloadProgress) => {\n // Map download progress to 0-50%\n onProgress?.({ phase: 'downloading', progress: Math.round(downloadProgress * 50) });\n }\n );\n } else {\n // Fallback: no progress tracking\n onProgress?.({ phase: 'downloading', progress: 25 });\n encryptedData = await response.arrayBuffer();\n }\n\n onProgress?.({ phase: 'downloading', progress: 50 });\n\n // Phase 2 & 3: Decrypt with progress mapping\n const decrypted = await decryptFile(encryptedData, {\n ...decryptOptions,\n onProgress: (progress) => {\n // Map decryption progress (0-100) to (50-95)\n const mappedProgress = 50 + Math.round(progress.progress * 0.45);\n onProgress?.({\n phase: progress.phase === 'complete' ? 'complete' : progress.phase,\n progress: progress.phase === 'complete' ? 100 : mappedProgress,\n });\n },\n });\n\n // Phase 4: Save file\n saveFile(decrypted, fileName);\n onProgress?.({ phase: 'complete', progress: 100 });\n } catch (error) {\n if (error instanceof CryptoError) {\n throw error;\n }\n throw new CryptoError('DOWNLOAD_FAILED');\n }\n}\n\n/**\n * Downloads with progress tracking using ReadableStream.\n *\n * @internal\n */\nasync function downloadWithProgress(\n body: ReadableStream<Uint8Array>,\n contentLength: number,\n onProgress: (progress: number) => void\n): Promise<ArrayBuffer> {\n const reader = body.getReader();\n const chunks: Uint8Array[] = [];\n let receivedLength = 0;\n\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) break;\n\n chunks.push(value);\n receivedLength += value.length;\n\n const progress = receivedLength / contentLength;\n onProgress(Math.min(progress, 1));\n }\n\n // Combine chunks into single ArrayBuffer\n const result = new Uint8Array(receivedLength);\n let position = 0;\n for (const chunk of chunks) {\n result.set(chunk, position);\n position += chunk.length;\n }\n\n return result.buffer;\n}\n\n/**\n * Saves a Blob as a file download.\n *\n * @internal\n */\nfunction saveFile(blob: Blob, fileName: string): void {\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n\n link.href = url;\n link.download = fileName;\n link.style.display = 'none';\n\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n\n URL.revokeObjectURL(url);\n}\n"],"names":["ALGORITHM","HASH_ALGORITHM","ERROR_MESSAGES","CryptoError","code","message","isCryptoError","error","normalizeInput","input","sliceBuffer","arr","arrayBufferToBase64","buffer","bytes","binary","i","base64ToArrayBuffer","base64","deriveKeyFromPassword","password","salt","encoder","keyMaterial","importKeyFromKeyfile","keyData","keyBuffer","generateRandomBytes","length","generateSalt","generateIV","PASSWORD_CHARSET","DEFAULT_PASSWORD_LENGTH","generateRandomPassword","safeLength","randomBytes","index","encryptFile","file","options","onProgress","data","encryptWithKeyfile","encryptWithPassword","iv","key","ciphertext","result","decryptFile","encrypted","marker","decryptWithPassword","decryptWithKeyfile","plaintext","getEncryptionType","isEncryptedFile","generateKeyFile","keyBytes","parseKeyFile","content","parsed","isValidKeyFile","obj","candidate","downloadKeyFile","fileName","extension","keyFile","json","blob","url","link","computeKeyFileHash","hashBuffer","hashArray","b","downloadAndDecrypt","decryptOptions","response","contentLength","encryptedData","downloadWithProgress","downloadProgress","decrypted","progress","mappedProgress","saveFile","body","reader","chunks","receivedLength","done","value","position","chunk"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.umd.js","sources":["../src/constants.ts","../src/errors.ts","../src/utils.ts","../src/encrypt.ts","../src/decrypt.ts","../src/stream.ts","../src/detect.ts","../src/keyfile.ts","../src/download.ts"],"sourcesContent":["/**\n * Cryptographic constants used throughout the library.\n * These values follow industry best practices for AES-256-GCM.\n *\n * @module constants\n * @since 1.0.0\n */\n\n/**\n * Salt length in bytes for PBKDF2 key derivation.\n * 16 bytes (128 bits) provides sufficient randomness to prevent rainbow table attacks.\n */\nexport const SALT_LENGTH = 16;\n\n/**\n * Initialization vector length in bytes for AES-GCM.\n * 12 bytes (96 bits) is the recommended IV size for AES-GCM per NIST SP 800-38D.\n */\nexport const IV_LENGTH = 12;\n\n/**\n * AES key length in bits.\n * 256 bits provides the highest security level for AES.\n */\nexport const KEY_LENGTH = 256;\n\n/**\n * PBKDF2 iteration count for key derivation.\n * 100,000 iterations balance security and performance.\n * Higher values increase resistance to brute-force attacks.\n */\nexport const PBKDF2_ITERATIONS = 100_000;\n\n/**\n * Key file raw key length in bytes (256-bit).\n * Matches the AES-256 key size requirement.\n */\nexport const KEYFILE_KEY_LENGTH = 32;\n\n/**\n * Encryption marker byte for password-based encryption.\n * Used to identify the encryption method when decrypting.\n */\nexport const ENCRYPTION_MARKER_PASSWORD = 0x01;\n\n/**\n * Encryption marker byte for keyfile-based encryption.\n * Used to identify the encryption method when decrypting.\n */\nexport const ENCRYPTION_MARKER_KEYFILE = 0x02;\n\n/**\n * Encryption marker byte for password-based streaming encryption.\n * Used to identify streaming encryption with password.\n * @since 1.1.0\n */\nexport const ENCRYPTION_MARKER_PASSWORD_STREAM = 0x11;\n\n/**\n * Encryption marker byte for keyfile-based streaming encryption.\n * Used to identify streaming encryption with keyfile.\n * @since 1.1.0\n */\nexport const ENCRYPTION_MARKER_KEYFILE_STREAM = 0x12;\n\n/**\n * Default chunk size for streaming encryption (64KB).\n * @since 1.1.0\n */\nexport const DEFAULT_CHUNK_SIZE = 64 * 1024;\n\n/**\n * Streaming format version.\n * @since 1.1.0\n */\nexport const STREAM_FORMAT_VERSION = 0x01;\n\n/**\n * AES-GCM authentication tag length in bytes.\n * 16 bytes (128 bits) is the default and recommended tag size.\n */\nexport const AUTH_TAG_LENGTH = 16;\n\n/**\n * Minimum encrypted file size for password-based encryption.\n * Header (1 + 16 + 12 = 29 bytes) + Auth tag (16 bytes) = 45 bytes.\n */\nexport const MIN_ENCRYPTED_SIZE_PASSWORD = 1 + SALT_LENGTH + IV_LENGTH + AUTH_TAG_LENGTH;\n\n/**\n * Minimum encrypted file size for keyfile-based encryption.\n * Header (1 + 12 = 13 bytes) + Auth tag (16 bytes) = 29 bytes.\n */\nexport const MIN_ENCRYPTED_SIZE_KEYFILE = 1 + IV_LENGTH + AUTH_TAG_LENGTH;\n\n/**\n * Algorithm identifier string for AES-GCM.\n */\nexport const ALGORITHM = 'AES-GCM' as const;\n\n/**\n * Hash algorithm used in PBKDF2 key derivation.\n */\nexport const HASH_ALGORITHM = 'SHA-256' as const;\n","/**\n * Error handling for browser-file-crypto.\n *\n * @module errors\n * @since 1.0.0\n */\n\n/**\n * Error codes for CryptoError.\n *\n * @description\n * - `INVALID_INPUT`: Input is not a valid File, Blob, or ArrayBuffer\n * - `PASSWORD_REQUIRED`: Password is required but not provided\n * - `KEYFILE_REQUIRED`: Keyfile is required but not provided\n * - `INVALID_PASSWORD`: Decryption failed due to incorrect password\n * - `INVALID_KEYFILE`: Decryption failed due to incorrect keyfile\n * - `INVALID_ENCRYPTED_DATA`: Data is corrupted or not encrypted with this library\n * - `ENCRYPTION_FAILED`: Encryption operation failed\n * - `DECRYPTION_FAILED`: Decryption operation failed\n * - `DOWNLOAD_FAILED`: File download failed\n * - `UNSUPPORTED_FORMAT`: Encrypted file format is not supported\n */\nexport type CryptoErrorCode =\n | 'INVALID_INPUT'\n | 'PASSWORD_REQUIRED'\n | 'KEYFILE_REQUIRED'\n | 'INVALID_PASSWORD'\n | 'INVALID_KEYFILE'\n | 'INVALID_ENCRYPTED_DATA'\n | 'ENCRYPTION_FAILED'\n | 'DECRYPTION_FAILED'\n | 'DOWNLOAD_FAILED'\n | 'UNSUPPORTED_FORMAT';\n\n/**\n * Error messages for each error code.\n */\nconst ERROR_MESSAGES: Record<CryptoErrorCode, string> = {\n INVALID_INPUT: 'Input must be a File, Blob, or ArrayBuffer.',\n PASSWORD_REQUIRED: 'Password or keyfile is required for encryption.',\n KEYFILE_REQUIRED: 'Keyfile is required to decrypt this file.',\n INVALID_PASSWORD: 'Decryption failed: incorrect password.',\n INVALID_KEYFILE: 'Decryption failed: incorrect keyfile.',\n INVALID_ENCRYPTED_DATA: 'Invalid encrypted data: file may be corrupted.',\n ENCRYPTION_FAILED: 'Encryption failed.',\n DECRYPTION_FAILED: 'Decryption failed.',\n DOWNLOAD_FAILED: 'File download failed.',\n UNSUPPORTED_FORMAT: 'Unsupported encryption format.',\n};\n\n/**\n * Custom error class for crypto operations.\n *\n * @description\n * Provides structured error handling with error codes for programmatic handling.\n * Each error includes a code that can be used for i18n or specific error handling.\n *\n * @example\n * ```typescript\n * try {\n * await decryptFile(data, { password: 'wrong' });\n * } catch (error) {\n * if (error instanceof CryptoError) {\n * console.log(error.code); // 'INVALID_PASSWORD'\n * console.log(error.message); // 'Decryption failed: incorrect password.'\n *\n * switch (error.code) {\n * case 'INVALID_PASSWORD':\n * showPasswordError();\n * break;\n * case 'INVALID_ENCRYPTED_DATA':\n * showCorruptedFileError();\n * break;\n * }\n * }\n * }\n * ```\n *\n * @see {@link CryptoErrorCode} for all available error codes\n * @since 1.0.0\n */\nexport class CryptoError extends Error {\n /** Error code for programmatic handling */\n readonly code: CryptoErrorCode;\n\n /**\n * Creates a new CryptoError.\n *\n * @param code - Error code identifying the type of error\n * @param message - Optional custom message (defaults to predefined message)\n */\n constructor(code: CryptoErrorCode, message?: string) {\n super(message ?? ERROR_MESSAGES[code]);\n this.name = 'CryptoError';\n this.code = code;\n\n // Maintains proper stack trace in V8 environments (Chrome, Node.js)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, CryptoError);\n }\n }\n}\n\n/**\n * Type guard to check if an error is a CryptoError.\n *\n * @param error - The error to check\n * @returns True if the error is a CryptoError\n *\n * @example\n * ```typescript\n * try {\n * await decryptFile(data, options);\n * } catch (error) {\n * if (isCryptoError(error)) {\n * console.log(error.code);\n * }\n * }\n * ```\n */\nexport function isCryptoError(error: unknown): error is CryptoError {\n return error instanceof CryptoError;\n}\n","/**\n * Utility functions for browser-file-crypto.\n *\n * @module utils\n * @internal\n * @since 1.0.0\n */\n\nimport {\n SALT_LENGTH,\n IV_LENGTH,\n KEY_LENGTH,\n PBKDF2_ITERATIONS,\n ALGORITHM,\n HASH_ALGORITHM,\n} from './constants';\nimport { CryptoError } from './errors';\n\n/**\n * Normalizes input to ArrayBuffer for consistent processing.\n *\n * @description\n * Accepts File, Blob, or ArrayBuffer and converts to ArrayBuffer.\n * This ensures all encryption/decryption functions work with a consistent type.\n *\n * @param input - File, Blob, or ArrayBuffer to normalize\n * @returns Promise resolving to ArrayBuffer\n *\n * @throws {CryptoError} When input is not a valid type (INVALID_INPUT)\n *\n * @example\n * ```typescript\n * const file = document.querySelector('input').files[0];\n * const buffer = await normalizeInput(file);\n * ```\n *\n * @internal\n */\nexport async function normalizeInput(\n input: File | Blob | ArrayBuffer\n): Promise<ArrayBuffer> {\n if (input instanceof ArrayBuffer) {\n return input;\n }\n\n if (input instanceof Blob) {\n return input.arrayBuffer();\n }\n\n throw new CryptoError('INVALID_INPUT');\n}\n\n/**\n * Safely slices a TypedArray's buffer to get exact range.\n *\n * @description\n * Required because TypedArray.buffer may reference a larger ArrayBuffer\n * when the TypedArray is a view into a portion of the buffer.\n * This function ensures we get only the exact bytes we need.\n *\n * @param arr - Uint8Array to slice\n * @returns ArrayBuffer containing only the bytes from the Uint8Array\n *\n * @example\n * ```typescript\n * const iv = new Uint8Array(12);\n * const ivBuffer = sliceBuffer(iv);\n * // ivBuffer is guaranteed to be exactly 12 bytes\n * ```\n *\n * @internal\n */\nexport function sliceBuffer(arr: Uint8Array): ArrayBuffer {\n return (arr.buffer as ArrayBuffer).slice(arr.byteOffset, arr.byteOffset + arr.byteLength);\n}\n\n/**\n * Converts ArrayBuffer to base64 string.\n *\n * @description\n * Uses browser's native btoa() for encoding.\n * Handles binary data by converting each byte to a character.\n *\n * @param buffer - ArrayBuffer to encode\n * @returns Base64-encoded string\n *\n * @example\n * ```typescript\n * const key = crypto.getRandomValues(new Uint8Array(32));\n * const base64Key = arrayBufferToBase64(key.buffer);\n * ```\n *\n * @internal\n */\nexport function arrayBufferToBase64(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = '';\n for (let i = 0; i < bytes.byteLength; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary);\n}\n\n/**\n * Converts base64 string to ArrayBuffer.\n *\n * @description\n * Uses browser's native atob() for decoding.\n * Returns an ArrayBuffer that can be used with Web Crypto API.\n *\n * @param base64 - Base64-encoded string to decode\n * @returns ArrayBuffer containing the decoded bytes\n *\n * @throws {DOMException} When base64 string is invalid\n *\n * @example\n * ```typescript\n * const keyBuffer = base64ToArrayBuffer(keyFile.key);\n * const cryptoKey = await crypto.subtle.importKey('raw', keyBuffer, ...);\n * ```\n *\n * @internal\n */\nexport function base64ToArrayBuffer(base64: string): ArrayBuffer {\n const binary = atob(base64);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes.buffer;\n}\n\n/**\n * Derives an AES-256 encryption key from a password using PBKDF2.\n *\n * @description\n * Uses PBKDF2 with SHA-256 and 100,000 iterations to derive a 256-bit key.\n * The derived key is non-extractable for security.\n *\n * @param password - The user-provided password string\n * @param salt - 16-byte random salt for key derivation\n * @returns Promise resolving to a non-extractable CryptoKey\n *\n * @throws {Error} When key derivation fails\n *\n * @example\n * ```typescript\n * const salt = crypto.getRandomValues(new Uint8Array(16));\n * const key = await deriveKeyFromPassword('my-password', salt);\n * ```\n *\n * @internal\n */\nexport async function deriveKeyFromPassword(\n password: string,\n salt: Uint8Array\n): Promise<CryptoKey> {\n const encoder = new TextEncoder();\n\n // Import password as raw key material\n const keyMaterial = await crypto.subtle.importKey(\n 'raw',\n encoder.encode(password),\n 'PBKDF2',\n false,\n ['deriveKey']\n );\n\n // Derive AES-256 key using PBKDF2\n return crypto.subtle.deriveKey(\n {\n name: 'PBKDF2',\n salt: sliceBuffer(salt),\n iterations: PBKDF2_ITERATIONS,\n hash: HASH_ALGORITHM,\n },\n keyMaterial,\n { name: ALGORITHM, length: KEY_LENGTH },\n false, // non-extractable for security\n ['encrypt', 'decrypt']\n );\n}\n\n/**\n * Imports raw key bytes for keyfile-based encryption.\n *\n * @description\n * Imports a 256-bit key directly from base64-encoded key data.\n * No key derivation is needed - the key is used as-is.\n * The imported key is non-extractable for security.\n *\n * @param keyData - Base64-encoded 256-bit key string\n * @returns Promise resolving to a non-extractable CryptoKey\n *\n * @throws {CryptoError} When key import fails (INVALID_KEYFILE)\n *\n * @example\n * ```typescript\n * const keyFile = generateKeyFile();\n * const cryptoKey = await importKeyFromKeyfile(keyFile.key);\n * ```\n *\n * @internal\n */\nexport async function importKeyFromKeyfile(keyData: string): Promise<CryptoKey> {\n try {\n const keyBuffer = base64ToArrayBuffer(keyData);\n\n return await crypto.subtle.importKey(\n 'raw',\n keyBuffer,\n { name: ALGORITHM, length: KEY_LENGTH },\n false, // non-extractable for security\n ['encrypt', 'decrypt']\n );\n } catch {\n throw new CryptoError('INVALID_KEYFILE');\n }\n}\n\n/**\n * Generates cryptographically secure random bytes.\n *\n * @description\n * Uses crypto.getRandomValues() for cryptographically secure randomness.\n *\n * @param length - Number of random bytes to generate\n * @returns Uint8Array containing random bytes\n *\n * @example\n * ```typescript\n * const salt = generateRandomBytes(SALT_LENGTH);\n * const iv = generateRandomBytes(IV_LENGTH);\n * ```\n *\n * @internal\n */\nexport function generateRandomBytes(length: number): Uint8Array {\n return crypto.getRandomValues(new Uint8Array(length));\n}\n\n/**\n * Generates a random salt for PBKDF2 key derivation.\n *\n * @returns 16-byte random salt\n *\n * @internal\n */\nexport function generateSalt(): Uint8Array {\n return generateRandomBytes(SALT_LENGTH);\n}\n\n/**\n * Generates a random IV for AES-GCM encryption.\n *\n * @returns 12-byte random IV\n *\n * @internal\n */\nexport function generateIV(): Uint8Array {\n return generateRandomBytes(IV_LENGTH);\n}\n\n/**\n * Default character set for random password generation.\n * Includes uppercase, lowercase, numbers, and special characters.\n */\nconst PASSWORD_CHARSET =\n 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*';\n\n/**\n * Default password length.\n */\nconst DEFAULT_PASSWORD_LENGTH = 16;\n\n/**\n * Generates a cryptographically secure random password.\n *\n * @description\n * Creates a random password using crypto.getRandomValues() for\n * cryptographic randomness. The password includes:\n * - Uppercase letters (A-Z)\n * - Lowercase letters (a-z)\n * - Numbers (0-9)\n * - Special characters (!@#$%^&*)\n *\n * @param length - Password length (default: 16, min: 8, max: 128)\n * @returns Random password string\n *\n * @example\n * ```typescript\n * const password = generateRandomPassword();\n * console.log(password); // e.g., 'Kx9#mP2$vL5@nQ8!'\n *\n * const longPassword = generateRandomPassword(32);\n * console.log(longPassword.length); // 32\n * ```\n *\n * @since 1.0.0\n */\nexport function generateRandomPassword(length: number = DEFAULT_PASSWORD_LENGTH): string {\n // Clamp length to reasonable bounds\n const safeLength = Math.max(8, Math.min(128, length));\n\n const randomBytes = generateRandomBytes(safeLength);\n let password = '';\n\n for (let i = 0; i < safeLength; i++) {\n const index = randomBytes[i] % PASSWORD_CHARSET.length;\n password += PASSWORD_CHARSET[index];\n }\n\n return password;\n}\n","/**\n * File encryption functions for browser-file-crypto.\n *\n * @module encrypt\n * @since 1.0.0\n */\n\nimport {\n SALT_LENGTH,\n IV_LENGTH,\n ENCRYPTION_MARKER_PASSWORD,\n ENCRYPTION_MARKER_KEYFILE,\n ALGORITHM,\n} from './constants';\nimport { CryptoError } from './errors';\nimport type { EncryptOptions } from './types';\nimport {\n normalizeInput,\n sliceBuffer,\n deriveKeyFromPassword,\n importKeyFromKeyfile,\n generateSalt,\n generateIV,\n} from './utils';\n\n/**\n * Encrypts a file using AES-256-GCM with password-based key derivation.\n *\n * @description\n * Uses PBKDF2 with 100,000 iterations to derive a 256-bit key from the password.\n * Each encryption generates a unique random salt (16 bytes) and IV (12 bytes).\n * The output format is: [marker(1)] + [salt(16)] + [iv(12)] + [ciphertext + auth tag].\n *\n * For keyfile-based encryption, the key is used directly without derivation.\n * The output format is: [marker(1)] + [iv(12)] + [ciphertext + auth tag].\n *\n * @param file - The file to encrypt (File, Blob, or ArrayBuffer)\n * @param options - Encryption options including password or keyData\n * @returns Promise resolving to encrypted Blob with 'application/octet-stream' MIME type\n *\n * @throws {CryptoError} When neither password nor keyData is provided (PASSWORD_REQUIRED)\n * @throws {CryptoError} When input is invalid (INVALID_INPUT)\n * @throws {CryptoError} When encryption fails (ENCRYPTION_FAILED)\n *\n * @example\n * ```typescript\n * // Password-based encryption\n * const encrypted = await encryptFile(file, {\n * password: 'my-secret',\n * onProgress: ({ phase, progress }) => console.log(`${phase}: ${progress}%`)\n * });\n *\n * // Keyfile-based encryption\n * const keyFile = generateKeyFile();\n * const encrypted = await encryptFile(file, {\n * keyData: keyFile.key,\n * onProgress: ({ progress }) => updateProgressBar(progress)\n * });\n * ```\n *\n * @see {@link decryptFile} for decryption\n * @see {@link generateKeyFile} for creating keyfiles\n * @since 1.0.0\n */\nexport async function encryptFile(\n file: File | Blob | ArrayBuffer,\n options: EncryptOptions\n): Promise<Blob> {\n const { password, keyData, onProgress } = options;\n\n // Validate: either password or keyData must be provided\n if (!password && !keyData) {\n throw new CryptoError('PASSWORD_REQUIRED');\n }\n\n try {\n // Step 1: Normalize input to ArrayBuffer\n onProgress?.({ phase: 'deriving_key', progress: 0 });\n const data = await normalizeInput(file);\n onProgress?.({ phase: 'deriving_key', progress: 10 });\n\n // Branch based on encryption method\n if (keyData) {\n return await encryptWithKeyfile(data, keyData, onProgress);\n } else {\n return await encryptWithPassword(data, password!, onProgress);\n }\n } catch (error) {\n if (error instanceof CryptoError) {\n throw error;\n }\n throw new CryptoError('ENCRYPTION_FAILED');\n }\n}\n\n/**\n * Encrypts data using password-based key derivation.\n *\n * @internal\n */\nasync function encryptWithPassword(\n data: ArrayBuffer,\n password: string,\n onProgress?: EncryptOptions['onProgress']\n): Promise<Blob> {\n // Step 2: Generate random salt and IV\n const salt = generateSalt();\n const iv = generateIV();\n onProgress?.({ phase: 'deriving_key', progress: 20 });\n\n // Step 3: Derive key from password\n const key = await deriveKeyFromPassword(password, salt);\n onProgress?.({ phase: 'encrypting', progress: 30 });\n\n // Step 4: Encrypt with AES-GCM\n const ciphertext = await crypto.subtle.encrypt(\n { name: ALGORITHM, iv: sliceBuffer(iv) },\n key,\n data\n );\n onProgress?.({ phase: 'encrypting', progress: 90 });\n\n // Step 5: Assemble output: marker + salt + iv + ciphertext\n const result = new Uint8Array(\n 1 + SALT_LENGTH + IV_LENGTH + ciphertext.byteLength\n );\n result[0] = ENCRYPTION_MARKER_PASSWORD;\n result.set(salt, 1);\n result.set(iv, 1 + SALT_LENGTH);\n result.set(new Uint8Array(ciphertext), 1 + SALT_LENGTH + IV_LENGTH);\n\n onProgress?.({ phase: 'complete', progress: 100 });\n\n return new Blob([result], { type: 'application/octet-stream' });\n}\n\n/**\n * Encrypts data using keyfile-based encryption.\n *\n * @internal\n */\nasync function encryptWithKeyfile(\n data: ArrayBuffer,\n keyData: string,\n onProgress?: EncryptOptions['onProgress']\n): Promise<Blob> {\n // Step 2: Generate random IV (no salt needed for keyfile)\n const iv = generateIV();\n onProgress?.({ phase: 'deriving_key', progress: 20 });\n\n // Step 3: Import key from keyfile\n const key = await importKeyFromKeyfile(keyData);\n onProgress?.({ phase: 'encrypting', progress: 30 });\n\n // Step 4: Encrypt with AES-GCM\n const ciphertext = await crypto.subtle.encrypt(\n { name: ALGORITHM, iv: sliceBuffer(iv) },\n key,\n data\n );\n onProgress?.({ phase: 'encrypting', progress: 90 });\n\n // Step 5: Assemble output: marker + iv + ciphertext\n const result = new Uint8Array(1 + IV_LENGTH + ciphertext.byteLength);\n result[0] = ENCRYPTION_MARKER_KEYFILE;\n result.set(iv, 1);\n result.set(new Uint8Array(ciphertext), 1 + IV_LENGTH);\n\n onProgress?.({ phase: 'complete', progress: 100 });\n\n return new Blob([result], { type: 'application/octet-stream' });\n}\n","/**\n * File decryption functions for browser-file-crypto.\n *\n * @module decrypt\n * @since 1.0.0\n */\n\nimport {\n SALT_LENGTH,\n IV_LENGTH,\n ENCRYPTION_MARKER_PASSWORD,\n ENCRYPTION_MARKER_KEYFILE,\n MIN_ENCRYPTED_SIZE_PASSWORD,\n MIN_ENCRYPTED_SIZE_KEYFILE,\n ALGORITHM,\n} from './constants';\nimport { CryptoError } from './errors';\nimport type { DecryptOptions } from './types';\nimport {\n normalizeInput,\n sliceBuffer,\n deriveKeyFromPassword,\n importKeyFromKeyfile,\n} from './utils';\n\n/**\n * Decrypts a file that was encrypted with encryptFile.\n *\n * @description\n * Automatically detects whether the file was encrypted with password or keyfile\n * based on the marker byte, then decrypts accordingly.\n *\n * For password-encrypted files:\n * - Extracts salt and IV from header\n * - Derives key using PBKDF2\n * - Decrypts with AES-GCM\n *\n * For keyfile-encrypted files:\n * - Extracts IV from header\n * - Imports key directly\n * - Decrypts with AES-GCM\n *\n * @param encrypted - The encrypted data (Blob or ArrayBuffer)\n * @param options - Decryption options including password or keyData\n * @returns Promise resolving to decrypted Blob\n *\n * @throws {CryptoError} When password is required but not provided (PASSWORD_REQUIRED)\n * @throws {CryptoError} When keyfile is required but not provided (KEYFILE_REQUIRED)\n * @throws {CryptoError} When password is incorrect (INVALID_PASSWORD)\n * @throws {CryptoError} When keyfile is incorrect (INVALID_KEYFILE)\n * @throws {CryptoError} When data is corrupted (INVALID_ENCRYPTED_DATA)\n *\n * @example\n * ```typescript\n * // Password-based decryption\n * try {\n * const decrypted = await decryptFile(encryptedBlob, {\n * password: 'my-secret',\n * onProgress: ({ phase, progress }) => console.log(`${phase}: ${progress}%`)\n * });\n * } catch (error) {\n * if (error instanceof CryptoError && error.code === 'INVALID_PASSWORD') {\n * console.log('Wrong password!');\n * }\n * }\n *\n * // Keyfile-based decryption\n * const decrypted = await decryptFile(encryptedBlob, {\n * keyData: keyFile.key\n * });\n * ```\n *\n * @see {@link encryptFile} for encryption\n * @see {@link getEncryptionType} for detecting encryption method\n * @since 1.0.0\n */\nexport async function decryptFile(\n encrypted: Blob | ArrayBuffer,\n options: DecryptOptions\n): Promise<Blob> {\n const { password, keyData, onProgress } = options;\n\n try {\n // Step 1: Normalize input to ArrayBuffer\n onProgress?.({ phase: 'decrypting', progress: 0 });\n const data = new Uint8Array(await normalizeInput(encrypted));\n onProgress?.({ phase: 'decrypting', progress: 5 });\n\n // Step 2: Check minimum size and read marker\n if (data.length < 1) {\n throw new CryptoError('INVALID_ENCRYPTED_DATA');\n }\n\n const marker = data[0];\n\n // Step 3: Branch based on encryption method\n if (marker === ENCRYPTION_MARKER_PASSWORD) {\n if (!password) {\n throw new CryptoError('PASSWORD_REQUIRED');\n }\n return await decryptWithPassword(data, password, onProgress);\n } else if (marker === ENCRYPTION_MARKER_KEYFILE) {\n if (!keyData) {\n throw new CryptoError('KEYFILE_REQUIRED');\n }\n return await decryptWithKeyfile(data, keyData, onProgress);\n } else {\n throw new CryptoError('UNSUPPORTED_FORMAT');\n }\n } catch (error) {\n if (error instanceof CryptoError) {\n throw error;\n }\n throw new CryptoError('DECRYPTION_FAILED');\n }\n}\n\n/**\n * Decrypts data that was encrypted with password.\n *\n * @internal\n */\nasync function decryptWithPassword(\n data: Uint8Array,\n password: string,\n onProgress?: DecryptOptions['onProgress']\n): Promise<Blob> {\n // Validate minimum size\n if (data.length < MIN_ENCRYPTED_SIZE_PASSWORD) {\n throw new CryptoError('INVALID_ENCRYPTED_DATA');\n }\n\n onProgress?.({ phase: 'deriving_key', progress: 10 });\n\n // Extract components: marker(1) + salt(16) + iv(12) + ciphertext\n const salt = data.slice(1, 1 + SALT_LENGTH);\n const iv = data.slice(1 + SALT_LENGTH, 1 + SALT_LENGTH + IV_LENGTH);\n const ciphertext = data.slice(1 + SALT_LENGTH + IV_LENGTH);\n\n // Derive key from password\n const key = await deriveKeyFromPassword(password, salt);\n onProgress?.({ phase: 'decrypting', progress: 30 });\n\n // Decrypt with AES-GCM\n try {\n const plaintext = await crypto.subtle.decrypt(\n { name: ALGORITHM, iv: sliceBuffer(iv) },\n key,\n ciphertext\n );\n onProgress?.({ phase: 'complete', progress: 100 });\n\n return new Blob([plaintext]);\n } catch {\n throw new CryptoError('INVALID_PASSWORD');\n }\n}\n\n/**\n * Decrypts data that was encrypted with keyfile.\n *\n * @internal\n */\nasync function decryptWithKeyfile(\n data: Uint8Array,\n keyData: string,\n onProgress?: DecryptOptions['onProgress']\n): Promise<Blob> {\n // Validate minimum size\n if (data.length < MIN_ENCRYPTED_SIZE_KEYFILE) {\n throw new CryptoError('INVALID_ENCRYPTED_DATA');\n }\n\n onProgress?.({ phase: 'decrypting', progress: 10 });\n\n // Extract components: marker(1) + iv(12) + ciphertext\n const iv = data.slice(1, 1 + IV_LENGTH);\n const ciphertext = data.slice(1 + IV_LENGTH);\n\n // Import key from keyfile\n const key = await importKeyFromKeyfile(keyData);\n onProgress?.({ phase: 'decrypting', progress: 30 });\n\n // Decrypt with AES-GCM\n try {\n const plaintext = await crypto.subtle.decrypt(\n { name: ALGORITHM, iv: sliceBuffer(iv) },\n key,\n ciphertext\n );\n onProgress?.({ phase: 'complete', progress: 100 });\n\n return new Blob([plaintext]);\n } catch {\n throw new CryptoError('INVALID_KEYFILE');\n }\n}\n","/**\n * Streaming encryption/decryption functions for browser-file-crypto.\n *\n * @description\n * Provides chunk-based encryption/decryption for large files using\n * TransformStream API. Each chunk is independently encrypted with\n * AES-256-GCM, allowing memory-efficient processing of files larger\n * than available RAM.\n *\n * @module stream\n * @since 1.1.0\n */\n\nimport {\n ENCRYPTION_MARKER_PASSWORD_STREAM,\n ENCRYPTION_MARKER_KEYFILE_STREAM,\n STREAM_FORMAT_VERSION,\n DEFAULT_CHUNK_SIZE,\n ALGORITHM,\n} from './constants';\nimport { CryptoError } from './errors';\nimport type {\n StreamEncryptOptions,\n StreamDecryptOptions,\n StreamProgress,\n} from './types';\nimport {\n sliceBuffer,\n deriveKeyFromPassword,\n importKeyFromKeyfile,\n generateSalt,\n generateIV,\n} from './utils';\n\n// =============================================================================\n// Internal Helper Functions\n// =============================================================================\n\n/**\n * Derives a chunk-specific IV from the base IV and chunk index.\n * Uses XOR to combine base IV with chunk index to ensure uniqueness.\n *\n * @param baseIV - The base 12-byte IV\n * @param chunkIndex - The 0-based chunk index\n * @returns A unique 12-byte IV for this chunk\n *\n * @internal\n */\nfunction deriveChunkIV(baseIV: Uint8Array, chunkIndex: number): Uint8Array {\n const iv = new Uint8Array(12);\n iv.set(baseIV);\n\n // XOR the last 4 bytes with chunk index (little-endian)\n const view = new DataView(iv.buffer);\n const currentValue = view.getUint32(8, true);\n view.setUint32(8, currentValue ^ chunkIndex, true);\n\n return iv;\n}\n\n/**\n * Concatenates two Uint8Arrays.\n *\n * @internal\n */\nfunction concatArrays(a: Uint8Array, b: Uint8Array): Uint8Array {\n const result = new Uint8Array(a.length + b.length);\n result.set(a, 0);\n result.set(b, a.length);\n return result;\n}\n\n/**\n * Encrypts a single chunk with AES-GCM.\n *\n * @internal\n */\nasync function encryptChunk(\n chunk: Uint8Array,\n key: CryptoKey,\n baseIV: Uint8Array,\n chunkIndex: number\n): Promise<Uint8Array> {\n const iv = deriveChunkIV(baseIV, chunkIndex);\n\n const ciphertext = await crypto.subtle.encrypt(\n { name: ALGORITHM, iv: sliceBuffer(iv) },\n key,\n sliceBuffer(chunk)\n );\n\n // Format: [4 bytes chunk length (LE)] + [ciphertext + auth tag]\n const result = new Uint8Array(4 + ciphertext.byteLength);\n new DataView(result.buffer as ArrayBuffer).setUint32(0, ciphertext.byteLength, true);\n result.set(new Uint8Array(ciphertext), 4);\n\n return result;\n}\n\n/**\n * Decrypts a single chunk with AES-GCM.\n *\n * @internal\n */\nasync function decryptChunk(\n encryptedChunk: Uint8Array,\n key: CryptoKey,\n baseIV: Uint8Array,\n chunkIndex: number\n): Promise<Uint8Array> {\n const iv = deriveChunkIV(baseIV, chunkIndex);\n\n try {\n const plaintext = await crypto.subtle.decrypt(\n { name: ALGORITHM, iv: sliceBuffer(iv) },\n key,\n sliceBuffer(encryptedChunk)\n );\n\n return new Uint8Array(plaintext);\n } catch {\n throw new CryptoError('DECRYPTION_FAILED');\n }\n}\n\n/**\n * Creates the streaming encryption header.\n *\n * @internal\n */\nfunction createStreamHeader(\n isPassword: boolean,\n chunkSize: number,\n salt: Uint8Array | null,\n baseIV: Uint8Array\n): Uint8Array {\n if (isPassword && salt) {\n // Password-based: marker(1) + version(1) + chunkSize(4) + salt(16) + baseIV(12) = 34 bytes\n const header = new Uint8Array(34);\n header[0] = ENCRYPTION_MARKER_PASSWORD_STREAM;\n header[1] = STREAM_FORMAT_VERSION;\n new DataView(header.buffer).setUint32(2, chunkSize, true);\n header.set(salt, 6);\n header.set(baseIV, 22);\n return header;\n } else {\n // Keyfile-based: marker(1) + version(1) + chunkSize(4) + baseIV(12) = 18 bytes\n const header = new Uint8Array(18);\n header[0] = ENCRYPTION_MARKER_KEYFILE_STREAM;\n header[1] = STREAM_FORMAT_VERSION;\n new DataView(header.buffer).setUint32(2, chunkSize, true);\n header.set(baseIV, 6);\n return header;\n }\n}\n\n/**\n * Parses the streaming encryption header.\n *\n * @internal\n */\nfunction parseStreamHeader(header: Uint8Array): {\n isPassword: boolean;\n version: number;\n chunkSize: number;\n salt: Uint8Array | null;\n baseIV: Uint8Array;\n headerSize: number;\n} {\n const marker = header[0];\n const isPassword = marker === ENCRYPTION_MARKER_PASSWORD_STREAM;\n\n if (\n marker !== ENCRYPTION_MARKER_PASSWORD_STREAM &&\n marker !== ENCRYPTION_MARKER_KEYFILE_STREAM\n ) {\n throw new CryptoError('UNSUPPORTED_FORMAT');\n }\n\n const version = header[1];\n if (version !== STREAM_FORMAT_VERSION) {\n throw new CryptoError('UNSUPPORTED_FORMAT');\n }\n\n const chunkSize = new DataView(header.buffer as ArrayBuffer, header.byteOffset).getUint32(\n 2,\n true\n );\n\n if (isPassword) {\n const salt = header.slice(6, 22);\n const baseIV = header.slice(22, 34);\n return { isPassword, version, chunkSize, salt, baseIV, headerSize: 34 };\n } else {\n const baseIV = header.slice(6, 18);\n return { isPassword, version, chunkSize, salt: null, baseIV, headerSize: 18 };\n }\n}\n\n// =============================================================================\n// Public API\n// =============================================================================\n\n/**\n * Creates a streaming encryption TransformStream.\n *\n * @description\n * Creates a TransformStream that encrypts data in chunks using AES-256-GCM.\n * Each chunk is independently encrypted with a unique IV derived from the\n * base IV and chunk index. This allows memory-efficient encryption of\n * arbitrarily large files.\n *\n * @param options - Streaming encryption options\n * @returns Promise resolving to an object containing the TransformStream and header\n *\n * @throws {CryptoError} When neither password nor keyData is provided (PASSWORD_REQUIRED)\n * @throws {CryptoError} When encryption fails (ENCRYPTION_FAILED)\n *\n * @example\n * ```typescript\n * const { stream, header } = await createEncryptStream({\n * password: 'my-secret',\n * chunkSize: 1024 * 1024, // 1MB chunks\n * onProgress: ({ processedBytes }) => console.log(`${processedBytes} bytes processed`)\n * });\n *\n * // Write header first, then pipe data through the stream\n * const writer = writableStream.getWriter();\n * await writer.write(header);\n * writer.releaseLock();\n * await readableStream.pipeThrough(stream).pipeTo(writableStream);\n * ```\n *\n * @since 1.1.0\n */\nexport async function createEncryptStream(options: StreamEncryptOptions): Promise<{\n stream: TransformStream<Uint8Array, Uint8Array>;\n header: Uint8Array;\n}> {\n const { password, keyData, chunkSize = DEFAULT_CHUNK_SIZE, onProgress } = options;\n\n if (!password && !keyData) {\n throw new CryptoError('PASSWORD_REQUIRED');\n }\n\n const isPassword = !!password;\n const salt = isPassword ? generateSalt() : null;\n const baseIV = generateIV();\n\n // Derive or import key\n onProgress?.({\n phase: 'deriving_key',\n processedBytes: 0,\n processedChunks: 0,\n });\n\n const key = isPassword\n ? await deriveKeyFromPassword(password!, salt!)\n : await importKeyFromKeyfile(keyData!);\n\n // Create header\n const header = createStreamHeader(isPassword, chunkSize, salt, baseIV);\n\n // Create transform stream\n let buffer: Uint8Array = new Uint8Array(0);\n let chunkIndex = 0;\n let processedBytes = 0;\n\n const stream = new TransformStream<Uint8Array, Uint8Array>({\n async transform(chunk, controller) {\n buffer = concatArrays(buffer, chunk) as Uint8Array;\n\n while (buffer.length >= chunkSize) {\n const toProcess = buffer.slice(0, chunkSize);\n buffer = buffer.slice(chunkSize);\n\n const encrypted = await encryptChunk(toProcess, key, baseIV, chunkIndex);\n controller.enqueue(encrypted);\n\n chunkIndex++;\n processedBytes += toProcess.length;\n\n onProgress?.({\n phase: 'processing',\n processedBytes,\n processedChunks: chunkIndex,\n });\n }\n },\n\n async flush(controller) {\n // Process remaining buffer (last chunk)\n if (buffer.length > 0) {\n const encrypted = await encryptChunk(buffer, key, baseIV, chunkIndex);\n controller.enqueue(encrypted);\n processedBytes += buffer.length;\n chunkIndex++;\n }\n\n onProgress?.({\n phase: 'complete',\n processedBytes,\n processedChunks: chunkIndex,\n progress: 100,\n });\n },\n });\n\n return { stream, header };\n}\n\n/**\n * Creates a streaming decryption TransformStream.\n *\n * @description\n * Creates a TransformStream that decrypts streaming-encrypted data.\n * The stream automatically parses the header and decrypts each chunk\n * using the appropriate key derivation method.\n *\n * @param options - Streaming decryption options\n * @returns A TransformStream that decrypts data\n *\n * @throws {CryptoError} When password is required but not provided (PASSWORD_REQUIRED)\n * @throws {CryptoError} When keyfile is required but not provided (KEYFILE_REQUIRED)\n * @throws {CryptoError} When decryption fails (DECRYPTION_FAILED)\n * @throws {CryptoError} When format is unsupported (UNSUPPORTED_FORMAT)\n *\n * @example\n * ```typescript\n * const stream = createDecryptStream({\n * password: 'my-secret',\n * onProgress: ({ processedBytes }) => console.log(`${processedBytes} bytes processed`)\n * });\n *\n * await encryptedStream.pipeThrough(stream).pipeTo(outputStream);\n * ```\n *\n * @since 1.1.0\n */\nexport function createDecryptStream(\n options: StreamDecryptOptions\n): TransformStream<Uint8Array, Uint8Array> {\n const { password, keyData, onProgress } = options;\n\n let buffer: Uint8Array = new Uint8Array(0);\n let headerParsed = false;\n let key: CryptoKey | null = null;\n let baseIV: Uint8Array | null = null;\n let chunkIndex = 0;\n let processedBytes = 0;\n let isPasswordMode = false;\n let headerSize = 0;\n\n return new TransformStream<Uint8Array, Uint8Array>({\n async transform(chunk, controller) {\n buffer = concatArrays(buffer, chunk) as Uint8Array;\n\n // Parse header if not yet done\n if (!headerParsed) {\n // Need at least the marker to determine header size\n if (buffer.length < 2) return;\n\n const marker = buffer[0];\n const minHeaderSize =\n marker === ENCRYPTION_MARKER_PASSWORD_STREAM ? 34 : 18;\n\n if (buffer.length < minHeaderSize) return;\n\n const headerData = buffer.slice(0, minHeaderSize);\n const parsed = parseStreamHeader(headerData);\n\n isPasswordMode = parsed.isPassword;\n // Note: parsed.chunkSize is available but not needed for decryption\n // since each chunk includes its own length prefix\n baseIV = parsed.baseIV;\n headerSize = parsed.headerSize;\n\n // Validate credentials\n if (isPasswordMode && !password) {\n throw new CryptoError('PASSWORD_REQUIRED');\n }\n if (!isPasswordMode && !keyData) {\n throw new CryptoError('KEYFILE_REQUIRED');\n }\n\n // Derive or import key\n onProgress?.({\n phase: 'deriving_key',\n processedBytes: 0,\n processedChunks: 0,\n });\n\n key = isPasswordMode\n ? await deriveKeyFromPassword(password!, parsed.salt!)\n : await importKeyFromKeyfile(keyData!);\n\n // Remove header from buffer\n buffer = buffer.slice(headerSize);\n headerParsed = true;\n }\n\n // Process complete chunks\n // Each encrypted chunk: 4 bytes length + ciphertext + 16 bytes auth tag\n while (buffer.length >= 4) {\n const encryptedChunkSize = new DataView(\n buffer.buffer as ArrayBuffer,\n buffer.byteOffset\n ).getUint32(0, true);\n const totalChunkSize = 4 + encryptedChunkSize;\n\n if (buffer.length < totalChunkSize) break;\n\n const encryptedData = buffer.slice(4, totalChunkSize);\n buffer = buffer.slice(totalChunkSize);\n\n const decrypted = await decryptChunk(encryptedData, key!, baseIV!, chunkIndex);\n controller.enqueue(decrypted);\n\n processedBytes += decrypted.length;\n chunkIndex++;\n\n onProgress?.({\n phase: 'processing',\n processedBytes,\n processedChunks: chunkIndex,\n });\n }\n },\n\n async flush() {\n // Any remaining data should have been processed\n if (buffer.length > 0) {\n throw new CryptoError('INVALID_ENCRYPTED_DATA');\n }\n\n onProgress?.({\n phase: 'complete',\n processedBytes,\n processedChunks: chunkIndex,\n progress: 100,\n });\n },\n });\n}\n\n/**\n * Encrypts a file using streaming encryption.\n *\n * @description\n * Convenience function that encrypts a File or Blob using streaming\n * encryption and returns a ReadableStream of the encrypted data.\n * The stream includes the encryption header followed by encrypted chunks.\n *\n * @param file - The file to encrypt\n * @param options - Streaming encryption options\n * @returns Promise resolving to a ReadableStream of encrypted data\n *\n * @throws {CryptoError} When neither password nor keyData is provided (PASSWORD_REQUIRED)\n * @throws {CryptoError} When encryption fails (ENCRYPTION_FAILED)\n *\n * @example\n * ```typescript\n * const encryptedStream = await encryptFileStream(largeFile, {\n * password: 'my-secret',\n * chunkSize: 1024 * 1024, // 1MB chunks\n * onProgress: ({ processedBytes, totalBytes }) => {\n * const percent = Math.round((processedBytes / totalBytes!) * 100);\n * console.log(`${percent}%`);\n * }\n * });\n *\n * // Convert to Blob\n * const response = new Response(encryptedStream);\n * const encryptedBlob = await response.blob();\n * ```\n *\n * @since 1.1.0\n */\nexport async function encryptFileStream(\n file: File | Blob,\n options: StreamEncryptOptions\n): Promise<ReadableStream<Uint8Array>> {\n const totalBytes = file.size;\n\n // Wrap onProgress to include totalBytes\n const wrappedOnProgress = options.onProgress\n ? (progress: StreamProgress) => {\n options.onProgress!({\n ...progress,\n totalBytes,\n progress:\n progress.phase === 'complete'\n ? 100\n : Math.round((progress.processedBytes / totalBytes) * 100),\n });\n }\n : undefined;\n\n const { stream, header } = await createEncryptStream({\n ...options,\n onProgress: wrappedOnProgress,\n });\n\n // Create a stream that first emits the header, then the encrypted data\n const fileStream = file.stream() as ReadableStream<Uint8Array>;\n const encryptedDataStream = fileStream.pipeThrough(stream);\n\n // Create a combined stream: header + encrypted data\n let headerSent = false;\n const reader = encryptedDataStream.getReader();\n\n return new ReadableStream<Uint8Array>({\n async pull(controller) {\n // Send header first\n if (!headerSent) {\n controller.enqueue(header);\n headerSent = true;\n return;\n }\n\n // Then send encrypted data\n try {\n const { done, value } = await reader.read();\n if (done) {\n controller.close();\n } else {\n controller.enqueue(value);\n }\n } catch (error) {\n controller.error(error);\n }\n },\n\n cancel() {\n reader.releaseLock();\n },\n });\n}\n\n/**\n * Decrypts a streaming-encrypted file.\n *\n * @description\n * Convenience function that decrypts a streaming-encrypted File, Blob,\n * or ReadableStream and returns a ReadableStream of the decrypted data.\n *\n * @param encrypted - The encrypted data (File, Blob, or ReadableStream)\n * @param options - Streaming decryption options\n * @returns A ReadableStream of decrypted data\n *\n * @throws {CryptoError} When password is required but not provided (PASSWORD_REQUIRED)\n * @throws {CryptoError} When keyfile is required but not provided (KEYFILE_REQUIRED)\n * @throws {CryptoError} When decryption fails (DECRYPTION_FAILED)\n *\n * @example\n * ```typescript\n * const decryptedStream = decryptFileStream(encryptedBlob, {\n * password: 'my-secret',\n * onProgress: ({ processedBytes }) => console.log(`${processedBytes} bytes decrypted`)\n * });\n *\n * // Convert to Blob\n * const response = new Response(decryptedStream);\n * const decryptedBlob = await response.blob();\n * ```\n *\n * @since 1.1.0\n */\nexport function decryptFileStream(\n encrypted: File | Blob | ReadableStream<Uint8Array>,\n options: StreamDecryptOptions\n): ReadableStream<Uint8Array> {\n const stream = createDecryptStream(options);\n\n if (encrypted instanceof ReadableStream) {\n return encrypted.pipeThrough(stream);\n }\n\n return (encrypted.stream() as ReadableStream<Uint8Array>).pipeThrough(stream);\n}\n","/**\n * Encryption type detection functions for browser-file-crypto.\n *\n * @module detect\n * @since 1.0.0\n */\n\nimport {\n ENCRYPTION_MARKER_PASSWORD,\n ENCRYPTION_MARKER_KEYFILE,\n ENCRYPTION_MARKER_PASSWORD_STREAM,\n ENCRYPTION_MARKER_KEYFILE_STREAM,\n MIN_ENCRYPTED_SIZE_PASSWORD,\n MIN_ENCRYPTED_SIZE_KEYFILE,\n} from './constants';\nimport type { EncryptionType } from './types';\nimport { normalizeInput } from './utils';\n\n/**\n * Detects the encryption type of encrypted data.\n *\n * @description\n * Reads the first byte (marker) of the encrypted data to determine\n * whether it was encrypted with a password or keyfile, and whether\n * it uses streaming or non-streaming encryption.\n *\n * - Marker 0x01: Password-based encryption (non-streaming)\n * - Marker 0x02: Keyfile-based encryption (non-streaming)\n * - Marker 0x11: Password-based streaming encryption\n * - Marker 0x12: Keyfile-based streaming encryption\n * - Other: Unknown format\n *\n * @param data - The encrypted data (Blob or ArrayBuffer)\n * @returns Promise resolving to encryption type\n *\n * @example\n * ```typescript\n * const type = await getEncryptionType(encryptedBlob);\n *\n * switch (type) {\n * case 'password':\n * // Use decryptFile with password\n * break;\n * case 'keyfile':\n * // Use decryptFile with keyData\n * break;\n * case 'password-stream':\n * // Use decryptFileStream with password\n * break;\n * case 'keyfile-stream':\n * // Use decryptFileStream with keyData\n * break;\n * case 'unknown':\n * // Show error: not encrypted with this library\n * break;\n * }\n * ```\n *\n * @see {@link isEncryptedFile} for simple encryption check\n * @since 1.0.0\n */\nexport async function getEncryptionType(\n data: Blob | ArrayBuffer\n): Promise<EncryptionType> {\n const buffer = await normalizeInput(data);\n const bytes = new Uint8Array(buffer);\n\n if (bytes.length < 1) {\n return 'unknown';\n }\n\n const marker = bytes[0];\n\n if (marker === ENCRYPTION_MARKER_PASSWORD) {\n return 'password';\n }\n\n if (marker === ENCRYPTION_MARKER_KEYFILE) {\n return 'keyfile';\n }\n\n if (marker === ENCRYPTION_MARKER_PASSWORD_STREAM) {\n return 'password-stream';\n }\n\n if (marker === ENCRYPTION_MARKER_KEYFILE_STREAM) {\n return 'keyfile-stream';\n }\n\n return 'unknown';\n}\n\n/**\n * Minimum header size for password-based streaming encryption.\n * marker(1) + version(1) + chunkSize(4) + salt(16) + baseIV(12) = 34 bytes\n */\nconst MIN_STREAM_HEADER_PASSWORD = 34;\n\n/**\n * Minimum header size for keyfile-based streaming encryption.\n * marker(1) + version(1) + chunkSize(4) + baseIV(12) = 18 bytes\n */\nconst MIN_STREAM_HEADER_KEYFILE = 18;\n\n/**\n * Checks if data appears to be encrypted with this library.\n *\n * @description\n * Performs a quick check to determine if the data was likely encrypted\n * with browser-file-crypto. This checks:\n * 1. The marker byte is valid (0x01, 0x02, 0x11, or 0x12)\n * 2. The data meets minimum size requirements\n *\n * Note: This is not a cryptographic verification. It only checks\n * the format markers and size constraints.\n *\n * @param data - The data to check (Blob or ArrayBuffer)\n * @returns Promise resolving to true if data appears to be encrypted\n *\n * @example\n * ```typescript\n * const file = event.target.files[0];\n * const isEncrypted = await isEncryptedFile(file);\n *\n * if (isEncrypted) {\n * showDecryptionUI();\n * } else {\n * showEncryptionUI();\n * }\n * ```\n *\n * @since 1.0.0\n */\nexport async function isEncryptedFile(data: Blob | ArrayBuffer): Promise<boolean> {\n const buffer = await normalizeInput(data);\n const bytes = new Uint8Array(buffer);\n\n if (bytes.length < 1) {\n return false;\n }\n\n const marker = bytes[0];\n\n // Check password-encrypted format (non-streaming)\n if (\n marker === ENCRYPTION_MARKER_PASSWORD &&\n bytes.length >= MIN_ENCRYPTED_SIZE_PASSWORD\n ) {\n return true;\n }\n\n // Check keyfile-encrypted format (non-streaming)\n if (\n marker === ENCRYPTION_MARKER_KEYFILE &&\n bytes.length >= MIN_ENCRYPTED_SIZE_KEYFILE\n ) {\n return true;\n }\n\n // Check password-encrypted streaming format\n if (\n marker === ENCRYPTION_MARKER_PASSWORD_STREAM &&\n bytes.length >= MIN_STREAM_HEADER_PASSWORD\n ) {\n return true;\n }\n\n // Check keyfile-encrypted streaming format\n if (\n marker === ENCRYPTION_MARKER_KEYFILE_STREAM &&\n bytes.length >= MIN_STREAM_HEADER_KEYFILE\n ) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Checks if the encryption type is a streaming format.\n *\n * @description\n * Helper function to determine if the encryption type uses\n * streaming encryption, which requires different decryption methods.\n *\n * @param type - The encryption type\n * @returns true if the type is a streaming format\n *\n * @example\n * ```typescript\n * const type = await getEncryptionType(encryptedBlob);\n *\n * if (isStreamingEncryption(type)) {\n * const decrypted = decryptFileStream(encryptedBlob, { password });\n * } else {\n * const decrypted = await decryptFile(encryptedBlob, { password });\n * }\n * ```\n *\n * @since 1.1.0\n */\nexport function isStreamingEncryption(type: EncryptionType): boolean {\n return type === 'password-stream' || type === 'keyfile-stream';\n}\n","/**\n * Keyfile generation and management functions for browser-file-crypto.\n *\n * @module keyfile\n * @since 1.0.0\n */\n\nimport { KEYFILE_KEY_LENGTH } from './constants';\nimport type { KeyFile } from './types';\nimport { arrayBufferToBase64, base64ToArrayBuffer, generateRandomBytes } from './utils';\n\n/**\n * Generates a new keyfile with a 256-bit random key.\n *\n * @description\n * Creates a cryptographically secure 256-bit (32 bytes) random key\n * using crypto.getRandomValues(). The key is returned as a KeyFile\n * object with metadata including version, algorithm, and creation timestamp.\n *\n * The generated key can be used directly with encryptFile() and decryptFile()\n * without any key derivation (unlike password-based encryption).\n *\n * @returns KeyFile object containing the generated key\n *\n * @example\n * ```typescript\n * const keyFile = generateKeyFile();\n * console.log(keyFile);\n * // {\n * // version: 1,\n * // algorithm: 'AES-256-GCM',\n * // key: 'base64-encoded-32-bytes...',\n * // createdAt: '2025-01-01T12:00:00.000Z'\n * // }\n *\n * // Use for encryption\n * const encrypted = await encryptFile(file, { keyData: keyFile.key });\n *\n * // Save keyfile for later\n * downloadKeyFile(keyFile.key, 'my-encryption-key');\n * ```\n *\n * @see {@link downloadKeyFile} for saving the keyfile\n * @see {@link parseKeyFile} for loading a saved keyfile\n * @since 1.0.0\n */\nexport function generateKeyFile(): KeyFile {\n const keyBytes = generateRandomBytes(KEYFILE_KEY_LENGTH);\n const key = arrayBufferToBase64(keyBytes.buffer as ArrayBuffer);\n\n return {\n version: 1,\n algorithm: 'AES-256-GCM',\n key,\n createdAt: new Date().toISOString(),\n };\n}\n\n/**\n * Parses a keyfile from JSON string content.\n *\n * @description\n * Validates and parses a JSON string into a KeyFile object.\n * Performs validation to ensure:\n * - Valid JSON format\n * - Required fields present (version, algorithm, key)\n * - Correct version (1)\n * - Correct algorithm ('AES-256-GCM')\n * - Key is a non-empty string\n *\n * @param content - JSON string content of the keyfile\n * @returns KeyFile object if valid, null if invalid\n *\n * @example\n * ```typescript\n * // From file input\n * const fileInput = document.querySelector('input[type=\"file\"]');\n * fileInput.addEventListener('change', async (e) => {\n * const file = e.target.files[0];\n * const content = await file.text();\n * const keyFile = parseKeyFile(content);\n *\n * if (keyFile) {\n * const decrypted = await decryptFile(encrypted, { keyData: keyFile.key });\n * } else {\n * console.error('Invalid keyfile format');\n * }\n * });\n * ```\n *\n * @see {@link generateKeyFile} for creating keyfiles\n * @since 1.0.0\n */\nexport function parseKeyFile(content: string): KeyFile | null {\n try {\n const parsed = JSON.parse(content) as unknown;\n\n // Type guard and validation\n if (!isValidKeyFile(parsed)) {\n return null;\n }\n\n return parsed;\n } catch {\n return null;\n }\n}\n\n/**\n * Validates if an object is a valid KeyFile.\n *\n * @internal\n */\nfunction isValidKeyFile(obj: unknown): obj is KeyFile {\n if (typeof obj !== 'object' || obj === null) {\n return false;\n }\n\n const candidate = obj as Record<string, unknown>;\n\n return (\n candidate.version === 1 &&\n candidate.algorithm === 'AES-256-GCM' &&\n typeof candidate.key === 'string' &&\n candidate.key.length > 0 &&\n typeof candidate.createdAt === 'string'\n );\n}\n\n/**\n * Downloads a keyfile as a JSON file with customizable extension.\n *\n * @description\n * Creates a downloadable JSON file containing the keyfile data.\n * The file extension can be customized (default: .key).\n *\n * Note: This function uses browser APIs (URL.createObjectURL, document.createElement)\n * and will not work in Node.js or Web Worker environments.\n *\n * @param keyData - Base64-encoded key string (from KeyFile.key)\n * @param fileName - Name for the downloaded file (without extension)\n * @param extension - File extension without dot (default: 'key')\n *\n * @example\n * ```typescript\n * const keyFile = generateKeyFile();\n *\n * // Downloads as 'my-secret-key.key' (default)\n * downloadKeyFile(keyFile.key, 'my-secret-key');\n *\n * // Downloads as 'my-secret-key.tfkey' (custom extension)\n * downloadKeyFile(keyFile.key, 'my-secret-key', 'tfkey');\n *\n * // The downloaded file contains:\n * // {\n * // \"version\": 1,\n * // \"algorithm\": \"AES-256-GCM\",\n * // \"key\": \"base64...\",\n * // \"createdAt\": \"2025-01-01T00:00:00.000Z\"\n * // }\n * ```\n *\n * @since 1.0.0\n */\nexport function downloadKeyFile(keyData: string, fileName: string, extension: string = 'key'): void {\n const keyFile: KeyFile = {\n version: 1,\n algorithm: 'AES-256-GCM',\n key: keyData,\n createdAt: new Date().toISOString(),\n };\n\n const json = JSON.stringify(keyFile, null, 2);\n const blob = new Blob([json], { type: 'application/json' });\n const url = URL.createObjectURL(blob);\n\n const link = document.createElement('a');\n link.href = url;\n link.download = `${fileName}.${extension}`;\n link.style.display = 'none';\n\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n\n URL.revokeObjectURL(url);\n}\n\n/**\n * Computes SHA-256 hash of a keyfile's key data.\n *\n * @description\n * Generates a SHA-256 hash of the key data for server-side verification.\n * This allows the server to verify that the client has the correct keyfile\n * without ever seeing the actual key.\n *\n * Use case: Store the hash on the server, then verify client-provided\n * hash matches before allowing access to encrypted content.\n *\n * @param keyData - Base64-encoded key string (from KeyFile.key)\n * @returns Promise resolving to hex-encoded SHA-256 hash\n *\n * @example\n * ```typescript\n * const keyFile = generateKeyFile();\n * const hash = await computeKeyFileHash(keyFile.key);\n * console.log(hash); // '3a7bd3e2c1f8...' (64 hex characters)\n *\n * // Send hash to server for storage\n * await fetch('/api/store-key-hash', {\n * method: 'POST',\n * body: JSON.stringify({ hash })\n * });\n *\n * // Later, verify keyfile by comparing hashes\n * const uploadedHash = await computeKeyFileHash(uploadedKeyFile.key);\n * const isValid = uploadedHash === storedHash;\n * ```\n *\n * @since 1.0.0\n */\nexport async function computeKeyFileHash(keyData: string): Promise<string> {\n const keyBuffer = base64ToArrayBuffer(keyData);\n const hashBuffer = await crypto.subtle.digest('SHA-256', keyBuffer);\n const hashArray = new Uint8Array(hashBuffer);\n\n // Convert to hex string\n return Array.from(hashArray)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n}\n","/**\n * Download and decrypt functions for browser-file-crypto.\n *\n * @module download\n * @since 1.0.0\n */\n\nimport { CryptoError } from './errors';\nimport type { DownloadDecryptOptions } from './types';\nimport { decryptFile } from './decrypt';\n\n/**\n * Downloads an encrypted file from URL, decrypts it, and saves to disk.\n *\n * @description\n * Combines file download, decryption, and save into a single operation.\n * Progress callback reports both download and decryption phases.\n *\n * Phases:\n * 1. `downloading` (0-50%): Fetching file from URL\n * 2. `deriving_key` (50-60%): Key derivation (password mode only)\n * 3. `decrypting` (60-95%): AES-GCM decryption\n * 4. `complete` (100%): File saved\n *\n * Note: This function uses browser APIs (fetch, URL.createObjectURL, document.createElement)\n * and will not work in Node.js environments.\n *\n * @param url - URL of the encrypted file to download\n * @param options - Options including password/keyData and fileName\n * @returns Promise that resolves when file is saved\n *\n * @throws {CryptoError} When download fails (DOWNLOAD_FAILED)\n * @throws {CryptoError} When decryption fails (see decryptFile errors)\n *\n * @example\n * ```typescript\n * await downloadAndDecrypt('https://example.com/secret.enc', {\n * password: 'my-secret',\n * fileName: 'document.pdf',\n * onProgress: ({ phase, progress }) => {\n * console.log(`${phase}: ${progress}%`);\n * // downloading: 25%\n * // downloading: 50%\n * // deriving_key: 55%\n * // decrypting: 80%\n * // complete: 100%\n * }\n * });\n * ```\n *\n * @see {@link decryptFile} for decryption only\n * @since 1.0.0\n */\nexport async function downloadAndDecrypt(\n url: string,\n options: DownloadDecryptOptions\n): Promise<void> {\n const { fileName, onProgress, ...decryptOptions } = options;\n\n try {\n // Phase 1: Download file\n onProgress?.({ phase: 'downloading', progress: 0 });\n\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new CryptoError(\n 'DOWNLOAD_FAILED',\n `Download failed: ${response.status} ${response.statusText}`\n );\n }\n\n // Track download progress if content-length is available\n const contentLength = response.headers.get('content-length');\n let encryptedData: ArrayBuffer;\n\n if (contentLength && response.body) {\n encryptedData = await downloadWithProgress(\n response.body,\n parseInt(contentLength, 10),\n (downloadProgress) => {\n // Map download progress to 0-50%\n onProgress?.({ phase: 'downloading', progress: Math.round(downloadProgress * 50) });\n }\n );\n } else {\n // Fallback: no progress tracking\n onProgress?.({ phase: 'downloading', progress: 25 });\n encryptedData = await response.arrayBuffer();\n }\n\n onProgress?.({ phase: 'downloading', progress: 50 });\n\n // Phase 2 & 3: Decrypt with progress mapping\n const decrypted = await decryptFile(encryptedData, {\n ...decryptOptions,\n onProgress: (progress) => {\n // Map decryption progress (0-100) to (50-95)\n const mappedProgress = 50 + Math.round(progress.progress * 0.45);\n onProgress?.({\n phase: progress.phase === 'complete' ? 'complete' : progress.phase,\n progress: progress.phase === 'complete' ? 100 : mappedProgress,\n });\n },\n });\n\n // Phase 4: Save file\n saveFile(decrypted, fileName);\n onProgress?.({ phase: 'complete', progress: 100 });\n } catch (error) {\n if (error instanceof CryptoError) {\n throw error;\n }\n throw new CryptoError('DOWNLOAD_FAILED');\n }\n}\n\n/**\n * Downloads with progress tracking using ReadableStream.\n *\n * @internal\n */\nasync function downloadWithProgress(\n body: ReadableStream<Uint8Array>,\n contentLength: number,\n onProgress: (progress: number) => void\n): Promise<ArrayBuffer> {\n const reader = body.getReader();\n const chunks: Uint8Array[] = [];\n let receivedLength = 0;\n let done = false;\n\n while (!done) {\n const result = await reader.read();\n done = result.done;\n\n if (result.value) {\n chunks.push(result.value);\n receivedLength += result.value.length;\n\n const progress = receivedLength / contentLength;\n onProgress(Math.min(progress, 1));\n }\n }\n\n // Combine chunks into single ArrayBuffer\n const result = new Uint8Array(receivedLength);\n let position = 0;\n for (const chunk of chunks) {\n result.set(chunk, position);\n position += chunk.length;\n }\n\n return result.buffer;\n}\n\n/**\n * Saves a Blob as a file download.\n *\n * @internal\n */\nfunction saveFile(blob: Blob, fileName: string): void {\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n\n link.href = url;\n link.download = fileName;\n link.style.display = 'none';\n\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n\n URL.revokeObjectURL(url);\n}\n"],"names":["ALGORITHM","HASH_ALGORITHM","ERROR_MESSAGES","CryptoError","code","message","isCryptoError","error","normalizeInput","input","sliceBuffer","arr","arrayBufferToBase64","buffer","bytes","binary","i","base64ToArrayBuffer","base64","deriveKeyFromPassword","password","salt","encoder","keyMaterial","importKeyFromKeyfile","keyData","keyBuffer","generateRandomBytes","length","generateSalt","generateIV","PASSWORD_CHARSET","DEFAULT_PASSWORD_LENGTH","generateRandomPassword","safeLength","randomBytes","index","encryptFile","file","options","onProgress","data","encryptWithKeyfile","encryptWithPassword","iv","key","ciphertext","result","decryptFile","encrypted","marker","decryptWithPassword","decryptWithKeyfile","plaintext","deriveChunkIV","baseIV","chunkIndex","view","currentValue","concatArrays","a","b","encryptChunk","chunk","decryptChunk","encryptedChunk","createStreamHeader","isPassword","chunkSize","header","parseStreamHeader","version","createEncryptStream","processedBytes","controller","toProcess","createDecryptStream","headerParsed","isPasswordMode","headerSize","minHeaderSize","headerData","parsed","totalChunkSize","encryptedData","decrypted","encryptFileStream","totalBytes","wrappedOnProgress","progress","stream","encryptedDataStream","headerSent","reader","done","value","decryptFileStream","getEncryptionType","MIN_STREAM_HEADER_PASSWORD","MIN_STREAM_HEADER_KEYFILE","isEncryptedFile","isStreamingEncryption","type","generateKeyFile","keyBytes","parseKeyFile","content","isValidKeyFile","obj","candidate","downloadKeyFile","fileName","extension","keyFile","json","blob","url","link","computeKeyFileHash","hashBuffer","hashArray","downloadAndDecrypt","decryptOptions","response","contentLength","downloadWithProgress","downloadProgress","mappedProgress","saveFile","body","chunks","receivedLength","position"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@time-file/browser-file-crypto",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Zero-dependency file encryption for browsers using Web Crypto API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -65,4 +65,4 @@
|
|
|
65
65
|
"access": "public",
|
|
66
66
|
"registry": "https://registry.npmjs.org/"
|
|
67
67
|
}
|
|
68
|
-
}
|
|
68
|
+
}
|