# Contributing (/docs/guides/contributing) We want this community to be friendly and respectful to each other. Please follow it in all your interactions with the project. ## Development workflow To get started with the project, run `bun install` in the root directory to install the required dependencies for each package: ```bash bun i ``` > While it's possible to use [`npm`](https://github.com/npm/cli), [`yarn`](https://classic.yarnpkg.com/), or [`pnpm`](https://pnpm.io), the tooling is built around [`bun`](https://bun.sh), so you'll have an easier time if you use `bun` for development. ### C++ Development If you are using a VSCode-flavored IDE and the `clangd` extension, you can use the `scripts/setup_clang_env.sh` script to set up the environment for C++ development. This will create a `compile_commands.json` file in the root directory of the project, which will allow the IDE to provide proper includes, better code completion and navigation. After that, add the following to your `.vscode/settings.json` file: ```json { "clangd.arguments": [ "-log=verbose", "-pretty", "--background-index", "--compile-commands-dir=${workspaceFolder}/packages/react-native-quick-crypto/" ], } ``` ### Running the Example App While developing, you can run the [example app](https://github.com/margelo/react-native-quick-crypto/tree/main/example) to test your changes. Any changes you make in your library's JavaScript code will be reflected in the example app without a rebuild. If you change any native code, then you'll need to rebuild the example app. To develop in iOS, build the CocoaPods dependencies: ```bash pod install ``` To start the Metro bundler/packager: ```bash bun start ``` To start the app: ```bash bun ios # or android ``` ### Native Files * To edit the Objective-C files, open `example/ios/QuickCryptoExample.xcworkspace` in XCode and find the source files at `Pods > Development Pods > react-native-quick-crypto`. * To edit the Kotlin files, open `example/android` in Android studio and find the source files at `margelo/quickcrypto` under `Android`. ## Code Quality Make sure your code passes TypeScript and ESLint. Run the following to verify: ```bash bun tsc bun lint bun format ``` To fix formatting errors, run the following: ```bash bun lint:fix bun format:fix ``` Remember to add tests for your change if possible. Run the unit tests by: ```bash bun test ``` ## Commit message convention We follow the [conventional commits specification](https://www.conventionalcommits.org/en) for our commit messages: * `fix`: bug fixes, e.g. fix crash due to deprecated method. * `feat`: new features, e.g. add new method to the module. * `refactor`: code refactor, e.g. migrate from class components to hooks. * `docs`: changes into documentation, e.g. add usage example for the module.. * `test`: adding or updating tests, e.g. add integration tests using detox. * `chore`: tooling changes, e.g. change CI config. ## Documentation Structure The documentation website is built using [Fumadocs](https://fumadocs.vercel.app) and Next.js. The source code for the documentation is located in the `docs` directory. ### Directory Layout * **`docs/content`**: Contains the MDX files for the documentation pages. * **`docs/components`**: React components used within the documentation (e.g., `CoverageTable.tsx`). * **`docs/data`**: Data files used by the components (e.g., `coverage.ts`). ### Adding a New Page To add a new page to the documentation: 1. Create a new `.mdx` file in `docs/content/docs/YOUR_SECTION/YOUR_PAGE.mdx`. 2. Add the required frontmatter at the top of the file: ```yaml --- title: Your Page Title description: A brief description of the page content. --- ``` 3. Register the new page in `docs/content/docs/YOUR_SECTION/meta.json` to ensure it appears in the sidebar navigation. ## Writing Documentation For detailed guidelines on how to structure API pages, use components, and format your documentation, please see the [Writing Documentation](/docs/guides/writing-documentation) guide. It covers: * Standard Page Structure (Theory, Class, Methods, Examples). * Table of Contents generation. * Frontmatter and Code Blocks. * Callouts and Alerts. ## Updating Documentation Coverage The [`docs/data/coverage.ts`](https://github.com/margelo/react-native-quick-crypto/blob/main/docs/data/coverage.ts) file tracks the implementation status of various cryptographic features. It is crucial to keep this file up-to-date so that users know what is supported. > **Note:** This file feeds directly into the [Implementation Coverage](/docs/introduction/coverage) page. Updating this file automatically updates the coverage table on the website. When you implement a new feature (or partially implement it), please find the corresponding entry in `coverage.ts` and update its status. Change `'missing'` to `'implemented'` or `'partial'`. **Status Types:** * `'implemented'`: The feature is fully implemented and supported. * `'missing'`: The feature is not yet implemented. * `'partial'`: The feature is partially implemented. Please add a `note` explaining what is missing or supported. * `'not-in-node'`: The feature is specific to this library and does not exist in the standard Node.js crypto API. **Example:** ```typescript { name: 'MyNewFeature', status: 'implemented', // or 'partial', 'missing' note: 'Supports only specific options for now' // Optional } ``` ## Sending a pull request > **Working on your first pull request?** You can learn how from this *free* series: [How to Contribute to an Open Source Project on GitHub](https://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github). When you're sending a pull request: * Prefer small pull requests focused on one change. * Verify that linters and tests are passing. * Review the documentation to make sure it looks good. * Follow the pull request template when opening a pull request. * If you add a new feature or change the status of an existing one, please update `docs/data/coverage.ts` to reflect the changes. * For pull requests that change the API or implementation, discuss with maintainers first by opening an issue. # Crypto Wallet (/docs/guides/crypto-wallet) `react-native-quick-crypto` is the **engine** that powers many high-performance crypto wallets. While it does not provide high-level wallet standards (like BIP39 wordlists) out of the box, it provides the **fast cryptographic primitives** (`pbkdf2`, `hmac`, `randomBytes`) that make wallet generation instant on mobile devices. ## The Performance Bottleneck Generating a wallet involves **PBKDF2** (Password-Based Key Derivation Function 2) with **2048 iterations**. * **Pure JS**: \~1500ms on older Android phones. ( Blocks UI ) * **RNQC**: \~20ms. ( Instant ) ## Implementation This guide shows how to implement the core cryptographic operations of a wallet manually using RNQC. ### 1. Generate Entropy (Randomness) Every wallet starts with random data. We use the OS's cryptographically secure random number generator. ```ts import { randomBytes } from 'react-native-quick-crypto'; const entropy = randomBytes(16); ``` In a full BIP39 implementation, you would convert this `entropy` into a list of 12 words using a standard wordlist. ### 2. Mnemonic to Seed (PBKDF2) Converting the mnemonic (words) into a binary seed is the "heavy lifting". This is where RNQC shines. ```ts import { pbkdf2 } from 'react-native-quick-crypto'; export function mnemonicToSeed(mnemonic: string, passphrase = ''): Promise { return new Promise((resolve, reject) => { const salt = 'mnemonic' + passphrase; // BIP39 Standard requires SHA-512, 2048 iterations, 64 bytes output pbkdf2(mnemonic, salt, 2048, 64, 'sha512', (err, key) => { if (err) return reject(err); resolve(key); }); }); } ``` ### 3. HD Key Derivation (HMAC-SHA512) Once you have the seed, you derive child keys (for Bitcoin, Ethereum, etc) using **HMAC-SHA512**. ```ts import { createHmac } from 'react-native-quick-crypto'; // A stripped-down example of BIP32 derivation export function deriveChildKey(parentKey: Buffer, chainCode: Buffer, index: number) { const hmac = createHmac('sha512', chainCode); // Data = 0x00 || ParentKey || Index (BigEndian) const data = Buffer.alloc(37); data[0] = 0x00; parentKey.copy(data, 1); data.writeUInt32BE(index, 33); hmac.update(data); const I = hmac.digest(); // Split I into Left (Private Key) and Right (Chain Code) const IL = I.subarray(0, 32); const IR = I.subarray(32); return { privateKey: IL, chainCode: IR }; } ``` ## Frequently Asked Questions ### Do I need `bip39` libraries? Yes, if you need the **English Wordlist** to convert random bytes into words like "horse battery staple". RNQC doesn't bundle dictionary files to keep the app size small. You use a library like `@scure/bip39` for the *words*, and configure it to use RNQC for the *math* (PBKDF2/SHA512) to get the best performance. Hopefully, we will soon see a fully **JSI-based BIP39** library for React Native that integrates directly with these primitives! # End-to-End Encryption (/docs/guides/e2ee-chat) End-to-End Encryption ensures that data is encrypted on the sender's device and only decrypted on the recipient's device. The server only sees encrypted blobs and cannot read the messages. In this guide, we will implement a secure chat flow using **ECDH** ( Elliptic Curve Diffie-Hellman) for key exchange and **AES-256-GCM** for message encryption. ## The Architecture ## Implementation ### Generate Identity Keys Each user needs a permanent (or session) Key Pair. We use **X25519** (Curve25519) because it is highly efficient and secure for key exchange. ```ts import QuickCrypto from 'react-native-quick-crypto'; // user-1.ts const alice = QuickCrypto.createECDH('curve25519'); const aliceKey = alice.generateKeys(); // Public Key to send to server // user-2.ts const bob = QuickCrypto.createECDH('curve25519'); const bobKey = bob.generateKeys(); // Public Key to send to server ``` ### Derive Shared Secret Once Alice has Bob's public key (retrieved from your server), she can derive the shared secret. Bob does the same with Alice's public key. ```ts // Alice's device const aliceSecret = alice.computeSecret(bobKey); // Bob's device const bobSecret = bob.computeSecret(aliceKey); console.log(aliceSecret.equals(bobSecret)); // true ``` The derived secret is usually too long or not uniformly random enough to be used directly as an AES key. Always pass it through a KDF (Key Derivation Function) like **HKDF** first. ### Key Derivation (HKDF) Turn the raw Diffie-Hellman secret into a cryptographically strong Encryption Key. ```ts // Derive a 32-byte (256-bit) AES key const encryptionKey = QuickCrypto.hkdfSync( 'sha256', aliceSecret, // Input Key Material (Shared Secret) 'chat-app-v1', // Salt (application specific constant) 'aes-key', // Info (context) 32 // Output length ); ``` ### Encrypt Message Use **AES-256-GCM** to encrypt the message. GCM provides both confidentiality (they can't read it) and integrity (they can't modify it). ```ts const message = "Hello, Bob! Secret message 🤫"; const iv = QuickCrypto.randomBytes(12); // NIST recommends 12 bytes for GCM const cipher = QuickCrypto.createCipheriv('aes-256-gcm', encryptionKey, iv); let encrypted = cipher.update(message, 'utf8', 'hex'); encrypted += cipher.final('hex'); const authTag = cipher.getAuthTag(); // Send this package to Bob const payload = { iv: iv.toString('hex'), content: encrypted, authTag: authTag.toString('hex') }; ``` ### Decrypt Message Bob receives the payload and decrypts it using the same derived `encryptionKey`. ```ts const decipher = QuickCrypto.createDecipheriv( 'aes-256-gcm', encryptionKey, Buffer.from(payload.iv, 'hex') ); decipher.setAuthTag(Buffer.from(payload.authTag, 'hex')); let decrypted = decipher.update(payload.content, 'hex', 'utf8'); decrypted += decipher.final('utf8'); console.log(decrypted); // "Hello, Bob! Secret message 🤫" ``` ## Security Considerations 1. **Man-in-the-Middle (MITM)**: If the server is malicious, it could swap Bob's public key with its own. To prevent this, implement **Key Verification** (e.g., comparing a "Safety Number" fingerprint derived from the keys) out-of-band (QR code scan). 2. **Forward Secrecy**: The example above uses static keys. If a key is compromised, all past messages are compromised. Real-world apps (Signal, WhatsApp) use the **Double Ratchet Algorithm** to rotate keys with every message. 3. **Storage**: Never store the Private Key (`alice.getPrivateKey()`) in plain text. Use [Secure Storage](/docs/guides/secure-storage) (Keychain/Keystore). This guide demonstrates **core cryptographic primitives** and concepts. A production-grade secure messaging application should implement a comprehensive protocol (like **Signal Protocol**) which includes: * **Double Ratchet Algorithm** for Forward Secrecy. * **X3DH** for asynchronous key exchange. * **Pre-Keys** to allow offline messaging. # Large File Encryption (/docs/guides/large-files) # Encrypting Large Files When encrypting large files (like videos or high-res images), loading the entire file into memory as a single `Buffer` or `string` will likely crash your app (OOM - Out Of Memory error). To handle this, we process the file in **chunks** using `Cipher`'s streaming interface (`.update()`). ## The Concept Instead of converting `File -> Buffer -> Encrypted`, we do: ## Implementation This pattern works with any file system library (e.g., `react-native-fs`, `expo-file-system`, or `react-native-blob-util`). We'll use pseudocode for file IO. ### Initialize Streaming Cipher Create the cipher instance. It maintains internal state between updates, so reuse it for the whole stream. ```ts import QuickCrypto from 'react-native-quick-crypto'; const key = QuickCrypto.randomBytes(32); const iv = QuickCrypto.randomBytes(16); // AES-CBC needs 16 bytes const cipher = QuickCrypto.createCipheriv('aes-256-cbc', key, iv); ``` ### The Processing Loop Read the file in manageable chunks (e.g., 1MB to 5MB). ```ts const CHUNK_SIZE = 1024 * 1024; // 1MB chunks let offset = 0; const fileSize = await FileSystem.getSize(path); // Open output stream/file await FileSystem.write(outputPath, '', 'utf8'); // Clear file while (offset < fileSize) { // Read Chunk const chunkBase64 = await FileSystem.read(path, { position: offset, length: CHUNK_SIZE, encoding: 'base64' }); // Encrypt Chunk const encryptedHex = cipher.update(chunkBase64, 'base64', 'hex'); // Write/Append Chunk await FileSystem.append(outputPath, encryptedHex, 'hex'); offset += CHUNK_SIZE; } ``` ### Finalize After the loop, call `.final()` to process any remaining internal buffer and padding. This step is critical! ```ts // Finalize const finalHex = cipher.final('hex'); if (finalHex.length > 0) { await FileSystem.append(outputPath, finalHex, 'hex'); } console.log('Encryption Complete! 🔒'); ``` ## Memory Usage Comparison | Method | 500MB Video | RAM Usage | Outcome | | :-------------- | :------------------------ | :-------- | :------------ | | **All-At-Once** | `cipher.update(fullFile)` | > 1.2 GB | **CRASH** 💥 | | **Chunked** | `cipher.update(1MB)` | \~5 MB | **SUCCESS** ✅ | Handling critical user data requires robustness: * **Integrity Checks**: Always calculate a Hash (SHA-256) of the file *while* encrypting to verify decryption later. * **Resume Capability**: If the app crashes at 90%, you need logic to effectively resume or clean up partially written files. * **Error Handling**: File I/O can fail (disk full, permissions). Wrap operations in try/catch blocks. # Migration Guide (/docs/guides/migration) If you are coming from `react-native-crypto` (the slow, standard shim) or `expo-crypto`, here is how to upgrade. ## Why Upgrade? * **Performance**: **Hundreds of times faster** (via C++/JSI). * **Compatibility**: Supports modern Node.js 16+ crypto APIs. * **Correctness**: Passes the official Node.js test suite. ## Migrating from `react-native-crypto` `react-native-crypto` (and `rn-nodeify`) relies on the React Native Bridge and pure JS fallbacks, which are extremely slow. ### Uninstall Legacy Libs Remove the old shims. ```bash npm uninstall react-native-crypto rn-nodeify react-native-randombytes ``` ### Install RNQC ```bash npm install react-native-quick-crypto react-native-nitro-modules cd ios && pod install ``` ### Global Polyfill (Optional) If your dependencies (like `ethers.js` or `bitcoinjs-lib`) expect `crypto` to be available globally: ```ts title="index.js" // Top of your entry file import { install } from 'react-native-quick-crypto'; install(); // Patches global.crypto and global.Buffer ``` Alternatively, you can just import `crypto` from the package directly if you don't want global side-effects: ```ts import crypto from 'react-native-quick-crypto'; ``` ## Migrating from `expo-crypto` `expo-crypto` is a lightweight wrapper around system APIs. It is distinct from Node.js `crypto`. | Feature | Expo Crypto | RNQC | | :--------------- | :--------------------------- | :-------------------------------------- | | **API Style** | `Crypto.digestStringAsync()` | `crypto.createHash().update().digest()` | | **Streams** | ❌ No | ✅ Yes | | **Ciphers** | ❌ No (Just Hashing) | ✅ AES, ChaCha20, etc. | | **Sync Methods** | ❌ Limited | ✅ Full Support | If you only need simple hashing (SHA-256), `expo-crypto` is fine. If you need **encryption, decryption, or complex key derivation**, you need RNQC. ### Code Comparison **Expo Crypto (digest):** ```ts const digest = await Crypto.digestStringAsync( Crypto.CryptoDigestAlgorithm.SHA256, 'Hello world' ); ``` **RNQC (digest):** ```ts const digest = createHash('sha256').update('Hello world').digest('hex'); ``` # Nitro Integration (/docs/guides/nitro-integration) **RNQC** is built on the **Nitro Modules** architecture. This means it exposes JSI `HybridObjects` that can be shared across other Nitro libraries with zero serialization cost. ## Why Nitro? Standard React Native modules send data as JSON strings over the Bridge. * **Bridge**: `ArrayBuffer` -> `Base64 String` -> `Bridge` -> `Base64 String` -> `Native Byte Array`. (Slow!) * **Nitro**: `ArrayBuffer` -> `JSI Reference` -> `Native Byte Array`. (Instant!) ## Sharing Buffers When you use `randomBytes` or `Cipher`, you get a `Buffer`. In RNQC (and Nitro), this Buffer is backed by a shared C++ memory region. You can pass this Buffer directly to other Nitro-powered libraries (like `react-native-nitro-image` or custom C++ modules) without copying data. ### Example: Encrypting an Image Imagine you have `react-native-nitro-image` which returns a raw pixel buffer. ## Creating a Nitro Module that uses Crypto If you are building your own [Nitro Module](https://github.com/mrousavy/nitro), you can accept `ArrayBuffer` in your schema. ```typescript // MyModule.nitro.ts processSecureData(data: ArrayBuffer): void; } ``` Then in your app: ```typescript // Generate 1MB of random data const bigKey = randomBytes(1024 * 1024); // Pass to your module // This is virtually instant - no Base64 copy! MyModule.processSecureData(bigKey.buffer); ``` ## Ecosytem RNQC plays well with: * **react-native-mmkv**: Fast, JSI-based storage. * **react-native-wishlist**: Fast layouts. * **react-native-skia**: High-performance graphics (can consume ArrayBuffers). By sticking to the **JSI/Nitro** ecosystem, you ensure that expensive cryptographic operations never block the UI thread from receiving touch events. # Secure Storage (/docs/guides/secure-storage) Building a secure storage solution requires combining **strong encryption** (RNQC) with **fast persistence** (MMKV or FileSystem). This guide shows how to build a production-grade encrypted storage wrapper. ## Architecture We will implement an **Encrypt-then-Store** strategy using `AES-256-GCM`. * **Algorithm**: AES-256-GCM (Authenticated Encryption). * **Key Management**: The encryption key itself must be stored securely (e.g., in the Keychain/Keystore via `react-native-keychain` or `expo-secure-store`). * **Storage**: The encrypted blob is stored in `react-native-mmkv`. ## Implementation ### Install Dependencies We need storage and keychain libraries. ```bash npm install react-native-mmkv react-native-keychain ``` ### Create the Crypto Layer This helper handles the raw encryption. ```ts import { createCipheriv, createDecipheriv, randomBytes } from 'react-native-quick-crypto'; import { Buffer } from 'buffer'; const ALGORITHM = 'aes-256-gcm'; export const encryptData = (key: Buffer, data: string) => { const iv = randomBytes(12); // 96-bit IV for GCM const cipher = createCipheriv(ALGORITHM, key, iv); let encrypted = cipher.update(data, 'utf8', 'base64'); encrypted += cipher.final('base64'); const authTag = cipher.getAuthTag(); // Return a combined payload return JSON.stringify({ iv: iv.toString('base64'), tag: authTag.toString('base64'), content: encrypted }); }; export const decryptData = (key: Buffer, payloadStr: string) => { const { iv, tag, content } = JSON.parse(payloadStr); const decipher = createDecipheriv( ALGORITHM, key, Buffer.from(iv, 'base64') ); decipher.setAuthTag(Buffer.from(tag, 'base64')); let decrypted = decipher.update(content, 'base64', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; }; ``` ### Integrate with MMKV Now we wrap MMKV with this encryption layer. ```ts import { MMKV } from 'react-native-mmkv'; import * as Keychain from 'react-native-keychain'; import { encryptData, decryptData } from './crypto-helper'; import { randomBytes } from 'react-native-quick-crypto'; const storage = new MMKV(); // Get or Create the Master Key async function getMasterKey(): Promise { const credentials = await Keychain.getGenericPassword({ service: 'app-secret' }); if (credentials) { return Buffer.from(credentials.password, 'hex'); } // Create new random key const newKey = randomBytes(32); await Keychain.setGenericPassword('master', newKey.toString('hex'), { service: 'app-secret' }); return newKey; } // The Storage API export const SecureStorage = { async set(key: string, value: string) { const masterKey = await getMasterKey(); const encrypted = encryptData(masterKey, value); storage.set(key, encrypted); }, async get(key: string) { const encrypted = storage.getString(key); if (!encrypted) return null; const masterKey = await getMasterKey(); try { return decryptData(masterKey, encrypted); } catch (e) { console.error('Decryption failed - data tampering detected!'); return null; // or throw } } }; ``` ## Why not just use MMKV encryption? MMKV has built-in encryption, but it uses AES-CFB and requires the key to be passed during initialization. Managing that lifecycle on the native side can be complex. **RNQC + MMKV** gives you: 1. **Full Control**: You decide the algorithm (GCM is safer than CFB). 2. **Key Rotation**: You can re-encrypt data with a new key easily in JS. 3. **Portability**: This logic works with any storage backend (SQLite, Realm, FileSystem). # TOTP (2FA) (/docs/guides/totp-2fa) # Building a 2FA Authenticator Time-based One-Time Passwords (TOTP) are the industry standard for Two-Factor Authentication (RFC 6238). They are used by Google Authenticator, Authy, and almost every login system. At its core, TOTP is just **HMAC-SHA1** hashing a **Secret Key** mixed with the **Current Time**. ## How It Works ## Implementation We can implement a full TOTP generator using only `react-native-quick-crypto`. ### The Time Counter TOTP codes change every 30 seconds. We need to calculate the number of 30-second intervals since the Unix Epoch. ```ts function getCounter(): Buffer { const time = Math.floor(Date.now() / 1000 / 30); const buffer = Buffer.alloc(8); // Write time as a Big-Endian 64-bit integer buffer.writeUInt32BE(0, 0); // High 32 bits buffer.writeUInt32BE(time, 4); // Low 32 bits return buffer; } ``` ### HMAC Hashing Hash the counter with the user's secret key using **SHA-1** (standard for TOTP). ```ts import QuickCrypto from 'react-native-quick-crypto'; function generateTOTP(secret: Buffer) { const counter = getCounter(); const hmac = QuickCrypto.createHmac('sha1', secret); hmac.update(counter); const digest = hmac.digest(); // Returns Buffer return dynamicTruncation(digest); } ``` ### Dynamic Truncation Extract 4 bytes from the hash to turn it into a number. ```ts function dynamicTruncation(digest: Buffer): string { // Get the offset from the last nibble (0-15) const offset = digest[digest.length - 1] & 0x0f; // Read 4 bytes starting at the offset const binary = ((digest[offset] & 0x7f) << 24) | ((digest[offset + 1] & 0xff) << 16) | ((digest[offset + 2] & 0xff) << 8) | (digest[offset + 3] & 0xff); // Modulo 1,000,000 to get 6 digits const code = binary % 1000000; return code.toString().padStart(6, '0'); } ``` ### Usage ```ts // In real apps, decode the Base32 secret from the QR code first! // calculating 'secret' from a string for demo purposes: const secret = Buffer.from('MY_SECRET_KEY', 'utf8'); const code = generateTOTP(secret); console.log(`Your 2FA code is: ${code}`); // "849102" ``` Implementing 2FA requires more than just generating codes. Consider: * **Time Synchronization**: Devices might have incorrect clocks. Implement a "window" verification (check code ±1 step). * **Backup Codes**: Users lose devices. Always provide static backup codes. * **Rate Limiting**: Prevent brute-force attacks on your verification endpoint. # Writing Documentation (/docs/guides/writing-documentation) To maintain consistency across the `react-native-quick-crypto` documentation, all API reference pages should follow a standard structure. This ensures that users can easily find the information they need, whether it's theoretical background, API signatures, or practical examples. The best way to understand the structure is to look at existing well-documented pages. Check out the [Cipher](/docs/api/cipher) (Symmetric Encryption) or [Hash](/docs/api/hash) pages for excellent examples of this structure in action. ## Page Structure A typical API documentation page should follow this order: 1. **Introduction**: A brief overview of what the module or class does. 2. **Table of Contents**: Automatically generated, but ensure your headers are structured correctly. 3. **Theory** (Optional): Specific cryptographic concepts relevant to the module. 4. **Class / Module**: The main class or module description. 5. **Methods**: Detailed API signatures. 6. **Real-World Examples**: clear, copy-pasteable examples. ### 1. Theory Provide a high-level explanation of the cryptographic concept. This helps users who might be new to crypto understand *what* they are using before they learn *how* to use it. * Explain the algorithm or concept (e.g., "Symmetric ciphers use the same key..."). * Mention key properties (e.g., "Block size", "Key size"). * Add security warnings if applicable (e.g., "Never reuse an IV"). ### 2. Class / Module Describe the main class or entry point. If the module exports a class (like `Hash` or `Cipher`), document it here. ### 3. Methods For each method, provide: * **Signature**: The method name and arguments. * **Description**: What the method does. * **Parameters**: Use the `` component to document parameters. * **Returns**: What the method returns. **Example TypeTable:** ```tsx ``` ### 4. Real-World Examples Provide complete, runnable examples. Users should be able to copy the code and run it to see it working. * Use **TypeScript** code blocks. * Include necessary imports. * Show common use cases (e.g., "Encrypting a string", "Hashing a file"). ## Formatting Guidelines ### Frontmatter Every MDX file must start with a frontmatter block: ```yaml --- title: Page Title description: Brief description for SEO and previews. --- ``` ### Headings & TOC The Table of Contents is generated from your headings. * Use **Heading 2 (`##`)** for main sections (Theory, Class, Examples). * Use **Heading 3 (`###`)** for specific methods or subsections. * **Avoid** Heading 1 (`#`); it is reserved for the page title. ### Code Blocks Use standard Markdown code blocks with language identifiers. ```typescript const buf = randomBytes(32); ``` ### Callouts Use the Fumadocs `Callout` component for alerts, notes, or warnings. ```tsx Useful information goes here. Critical security information. Do not do this. ``` ## Updating Coverage If you are documenting a new feature, remember to update [`docs/data/coverage.ts`](https://github.com/margelo/react-native-quick-crypto/blob/main/docs/data/coverage.ts) to reflect the new implementation status. This powers the [Implementation Coverage](/docs/introduction/coverage) page. # Argon2 (/docs/api/argon2) **Argon2** is the winner of the Password Hashing Competition (PHC) and the recommended algorithm for password hashing and key derivation. It is designed to resist GPU, ASIC, and side-channel attacks. **Argon2** is the modern choice for password hashing. Use **PBKDF2** only for FIPS compliance, and **scrypt** when Argon2 is unavailable. Argon2 offers the best resistance to hardware-accelerated attacks. ## Table of Contents * [Theory](#theory) * [Variants](#variants) * [Module Methods](#module-methods) * [WebCrypto API](#webcrypto-api) * [Real-World Examples](#real-world-examples) ## Theory Argon2 fills a large block of memory with pseudorandom data derived from the password and salt, then performs multiple passes over it. This makes brute-force attacks expensive on both time and memory dimensions. Key parameters: | Parameter | Description | Typical Value | | :------------ | :--------------- | :------------ | | `memoryCost` | Memory in KiB | 65536 (64 MB) | | `timeCost` | Number of passes | 3 | | `parallelism` | Parallel threads | 4 | ## Variants | Variant | Best For | Properties | | :--------- | :---------------------- | :----------------------------------------------------------------- | | `argon2d` | Cryptocurrency mining | Fastest, data-dependent memory access (vulnerable to side-channel) | | `argon2i` | Password hashing | Data-independent memory access (side-channel resistant) | | `argon2id` | **Recommended default** | Hybrid of argon2d and argon2i | *** ## Module Methods ### argon2(algorithm, parameters, callback) Asynchronous Argon2 hashing. **Parameters:** **Example:** ```ts const password = 'user-password'; const salt = randomBytes(16); argon2('argon2id', { pass: password, salt, memoryCost: 65536, // 64 MB timeCost: 3, parallelism: 4, hashLength: 32 }, (err, hash) => { if (err) throw err; console.log(hash.toString('hex')); }); ``` ### argon2Sync(algorithm, parameters) Synchronous version. Returns `Buffer`. The synchronous version blocks the JS thread. Use the async version for UI-facing operations. ```ts const hash = argon2Sync('argon2id', { pass: 'password', salt: randomBytes(16), memoryCost: 65536, timeCost: 3, parallelism: 4, hashLength: 32 }); ``` *** ## WebCrypto API Argon2 is available through `subtle.deriveBits()` and `subtle.deriveKey()`. ### Import a Password Key ```ts const passwordKey = await subtle.importKey( 'raw-secret', new TextEncoder().encode('user-password'), { name: 'Argon2id' }, false, ['deriveBits', 'deriveKey'] ); ``` ### deriveBits ```ts const salt = crypto.getRandomValues(new Uint8Array(16)); const bits = await subtle.deriveBits( { name: 'Argon2id', salt, memoryCost: 65536, timeCost: 3, parallelism: 4 }, passwordKey, 256 // bits ); ``` ### deriveKey Derive an AES key directly from a password: ```ts const aesKey = await subtle.deriveKey( { name: 'Argon2id', salt: crypto.getRandomValues(new Uint8Array(16)), memoryCost: 65536, timeCost: 3, parallelism: 4 }, passwordKey, { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ); ``` *** ## Real-World Examples ### Secure Password Storage ```ts interface StoredPassword { hash: string; salt: string; algorithm: string; memoryCost: number; timeCost: number; parallelism: number; } function hashPassword(password: string): Promise { return new Promise((resolve, reject) => { const salt = randomBytes(16); const params = { pass: password, salt, memoryCost: 65536, timeCost: 3, parallelism: 4, hashLength: 32 }; argon2('argon2id', params, (err, hash) => { if (err) return reject(err); resolve({ hash: hash.toString('hex'), salt: salt.toString('hex'), algorithm: 'argon2id', memoryCost: 65536, timeCost: 3, parallelism: 4 }); }); }); } function verifyPassword(password: string, stored: StoredPassword): Promise { return new Promise((resolve, reject) => { argon2(stored.algorithm, { pass: password, salt: Buffer.from(stored.salt, 'hex'), memoryCost: stored.memoryCost, timeCost: stored.timeCost, parallelism: stored.parallelism, hashLength: 32 }, (err, hash) => { if (err) return reject(err); const expected = Buffer.from(stored.hash, 'hex'); resolve(timingSafeEqual(hash, expected)); }); }); } ``` ### Deriving an Encryption Key from a Password ```ts import { argon2Sync, randomBytes, createCipheriv, createDecipheriv } from 'react-native-quick-crypto'; function encryptWithPassword(plaintext: string, password: string) { const salt = randomBytes(16); const iv = randomBytes(12); const key = argon2Sync('argon2id', { pass: password, salt, memoryCost: 65536, timeCost: 3, parallelism: 4, hashLength: 32 }); const cipher = createCipheriv('aes-256-gcm', key, iv); let encrypted = cipher.update(plaintext, 'utf8', 'base64'); encrypted += cipher.final('base64'); const tag = cipher.getAuthTag(); return { encrypted, salt: salt.toString('base64'), iv: iv.toString('base64'), tag: tag.toString('base64') }; } ``` # BLAKE3 (/docs/api/blake3) **BLAKE3** is a cryptographic hash function that is roughly **15x faster** than SHA-2 on modern processors, while maintaining a high security margin. ## Table of Contents * [Theory](#theory) * [Class: Blake3](#class-blake3) * [Real-World Examples](#real-world-examples) ## Theory Unlike MD5 or SHA-2 which process data sequentially, BLAKE3 uses a **Merkle Tree**. * The data stream is cut into 1 KiB chunks. * Each chunk is hashed independently. * Each chunk is hashed independently. * The hashes are combined in a binary tree. This structure allows massive parallelism (using SIMD instructions like AVX2/NEON), enabling multi-GB/s throughput. It naturally supports: * **Keyed Hashing**: Acts as a MAC without HMAC construction overhead. * **Key Derivation**: Using "context strings" to separate domains. * **XOF**: Extendable Output Function (variable length output). *** ## Class: Blake3 ### createBlake3(\[options]) Creates a `Blake3` instance. **Parameters:** **Returns:** `Blake3` ### blake3.update(data) Updates the hash state. **Parameters:** ### blake3.digest(\[encoding]) Returns the standard 32-byte (256-bit) digest. ### blake3.digestLength(length\[, encoding]) Returns a digest of arbitrary `length` bytes (XOF). **Example:** ```ts const keyAndIV = hash.digestLength(44); // 32 byte key + 12 byte IV ``` ### blake3.reset() Resets the hash state. *** ## Real-World Examples ### Key Derivation (KDF) Using contexts to derive different keys from one master secret. ```ts const masterKey = Buffer.from('...'); // 32 bytes high-entropy function deriveKeys(master: Buffer) { // Derive Encryption Key const enc = createBlake3({ context: 'my-app-v1-encryption' }); enc.update(master); const encryptionKey = enc.digest(); // Derive Signing Key const sign = createBlake3({ context: 'my-app-v1-signing' }); sign.update(master); const signingKey = sign.digest(); return { encryptionKey, signingKey }; } ``` # Certificate (SPKAC) (/docs/api/certificate) The `Certificate` class provides static methods for working with **SPKAC** (Signed Public Key and Challenge) data, a format originally created by Netscape for certificate request generation. ## Table of Contents * [Theory](#theory) * [Static Methods](#static-methods) * [Real-World Examples](#real-world-examples) ## Theory SPKAC is a certificate signing request mechanism that bundles a public key with a challenge string, signed by the corresponding private key. While largely superseded by PKCS#10 CSRs, SPKAC is still used in some legacy systems and the HTML `` element. *** ## Static Methods ### Certificate.exportChallenge(spkac\[, encoding]) Extracts the challenge string from the SPKAC data. **Parameters:** **Returns:** `Buffer` containing the challenge component. ```ts const challenge = Certificate.exportChallenge(spkacData); console.log(challenge.toString()); // the challenge string ``` ### Certificate.exportPublicKey(spkac\[, encoding]) Extracts the public key from the SPKAC data, returned as a PEM-encoded SubjectPublicKeyInfo. **Parameters:** **Returns:** `Buffer` containing the PEM-encoded public key. ```ts const publicKey = Certificate.exportPublicKey(spkacData); console.log(publicKey.toString()); // -----BEGIN PUBLIC KEY----- // ... // -----END PUBLIC KEY----- ``` ### Certificate.verifySpkac(spkac\[, encoding]) Verifies the signature on the SPKAC data, confirming that the data was signed by the corresponding private key. **Parameters:** **Returns:** `boolean` — `true` if the SPKAC signature is valid. ```ts const isValid = Certificate.verifySpkac(spkacData); console.log(isValid); // true or false ``` *** ## Real-World Examples ### Validating a Certificate Request ```ts function processCertificateRequest(spkac: Buffer) { // Verify the SPKAC signature if (!Certificate.verifySpkac(spkac)) { throw new Error('Invalid SPKAC signature'); } // Extract the public key and challenge const publicKey = Certificate.exportPublicKey(spkac); const challenge = Certificate.exportChallenge(spkac); return { publicKey: publicKey.toString(), challenge: challenge.toString() }; } ``` # Cipher (/docs/api/cipher) The `Cipher` module provides implementations of symmetric cipher algorithms. It supports standard Block Ciphers (AES), Stream Ciphers (ChaCha20), and extended ciphers via libsodium (XChaCha20, XSalsa20). ## Table of Contents * [Theory](#theory) * [Supported Algorithms](#supported-algorithms) * [Class: Cipher](#class-cipher) * [Class: Decipher](#class-decipher) * [Module Methods](#module-methods) * [Real-World Examples](#real-world-examples) ## Theory Symmetric ciphers use the same key for encryption and decryption. * **Block Ciphers** (e.g., AES) operate on fixed-size blocks of data (16 bytes). * **Modes of Operation** determine how to encrypt data larger than a single block. * **GCM (Galois/Counter Mode)**: Provides both encryption and integrity (AEAD). **Recommended.** * **CBC (Cipher Block Chaining)**: Older standard. Malleable and requires padding. **AEAD (Authenticated Encryption with Associated Data)** ensures that the data cannot be modified by an attacker. It produces an **Authentication Tag**. If the tag doesn't match upon decryption, the operation fails. **Never reuse an IV/Nonce.** Using the same IV with the same Key for two different messages allows attackers to break the encryption (e.g., recovering the XOR of the plaintexts in GCM/CTR modes). *** ## Supported Algorithms ### Block Ciphers (AES) | Algorithm | Key Size | IV Size | Mode | AEAD | | ------------- | -------- | ---------- | ---- | :--: | | `aes-128-cbc` | 16 bytes | 16 bytes | CBC | No | | `aes-192-cbc` | 24 bytes | 16 bytes | CBC | No | | `aes-256-cbc` | 32 bytes | 16 bytes | CBC | No | | `aes-128-ctr` | 16 bytes | 16 bytes | CTR | No | | `aes-192-ctr` | 24 bytes | 16 bytes | CTR | No | | `aes-256-ctr` | 32 bytes | 16 bytes | CTR | No | | `aes-128-gcm` | 16 bytes | 12 bytes | GCM | Yes | | `aes-192-gcm` | 24 bytes | 12 bytes | GCM | Yes | | `aes-256-gcm` | 32 bytes | 12 bytes | GCM | Yes | | `aes-128-ccm` | 16 bytes | 7-13 bytes | CCM | Yes | | `aes-192-ccm` | 24 bytes | 7-13 bytes | CCM | Yes | | `aes-256-ccm` | 32 bytes | 7-13 bytes | CCM | Yes | | `aes-128-ocb` | 16 bytes | 12 bytes | OCB | Yes | | `aes-192-ocb` | 24 bytes | 12 bytes | OCB | Yes | | `aes-256-ocb` | 32 bytes | 12 bytes | OCB | Yes | ### Stream Ciphers (ChaCha20) | Algorithm | Key Size | Nonce Size | Tag Size | AEAD | AAD | | ------------------- | -------- | ---------- | -------- | :--: | :-: | | `chacha20` | 32 bytes | 16 bytes | - | No | No | | `chacha20-poly1305` | 32 bytes | 12 bytes | 16 bytes | Yes | Yes | ### Extended Ciphers (libsodium) These ciphers require `SODIUM_ENABLED=1` on both iOS and Android. They are **not available in Node.js** and are provided as extensions for mobile use cases. | Algorithm | Key Size | Nonce Size | Tag Size | AEAD | AAD | Notes | | -------------------- | -------- | ---------- | -------- | :--: | :-: | ---------------------- | | `xchacha20-poly1305` | 32 bytes | 24 bytes | 16 bytes | Yes | Yes | Extended nonce variant | | `xsalsa20-poly1305` | 32 bytes | 24 bytes | 16 bytes | Yes | No | NaCl secretbox | | `xsalsa20` | 32 bytes | 24 bytes | - | No | No | Stream cipher only | The extended nonce (24 bytes vs 12 bytes) in XChaCha20 and XSalsa20 variants allows safe random nonce generation without risk of collision, making them ideal for high-volume encryption scenarios. *** ## Class: Cipher Instances of the `Cipher` class are used to encrypt data. ### cipher.update(data\[, inputEncoding]\[, outputEncoding]) Encrypts data. **Parameters:** **Returns:** `Buffer | string` ### cipher.final(\[outputEncoding]) Returns any remaining encrypted data. Cipher instances are single-use. Calling `update()` or `final()` after `final()` throws an error. Create a new cipher instance for each encryption operation. When using string output encoding (e.g. `'base64'`), the encoding must be consistent between `update()` and `final()` calls. ### cipher.getAuthTag() For AEAD modes (GCM, CCM, Poly1305), returns the authentication tag. **Must be called after `final()`.** **Returns:** `Buffer` ### cipher.setAAD(buffer\[, options]) Sets "Additional Authenticated Data" (AAD). This is data that is **not encrypted** but is **authenticated** (integrity protected). *** ## Class: Decipher Instances of the `Decipher` class are used to decrypt data. ### decipher.update(data\[, inputEncoding]\[, outputEncoding]) ### decipher.final(\[outputEncoding]) ### decipher.setAuthTag(buffer) Sets the tag to verify. **Must be called before `final()`.** ### decipher.setAAD(buffer) Sets AAD to verify. **Must be called before `final()`.** *** ## Module Methods ### createCipheriv(algorithm, key, iv\[, options]) Creates and returns a `Cipher` object. **Parameters:** **Returns:** `Cipher` ### createDecipheriv(algorithm, key, iv\[, options]) Creates and returns a `Decipher` object. **Returns:** `Decipher` *** ## Real-World Examples ### Authenticated Encryption (GCM) Complete encryption flow with integrity check. ```ts const key = randomBytes(32); function encrypt(text: string) { const iv = randomBytes(12); const cipher = createCipheriv('aes-256-gcm', key, iv); let enc = cipher.update(text, 'utf8', 'hex'); enc += cipher.final('hex'); const tag = cipher.getAuthTag(); return { data: enc, iv: iv.toString('hex'), tag: tag.toString('hex') }; } ``` ### File Encryption (Streaming) Encrypting a file using streams with AES-CTR (counter mode). ```ts const fs = require('fs'); // Mock const key = randomBytes(32); const iv = randomBytes(16); const cipher = createCipheriv('aes-256-ctr', key, iv); const input = fs.createReadStream('input.txt'); const output = fs.createWriteStream('output.enc'); input.pipe(cipher).pipe(output); ``` ### XChaCha20-Poly1305 (Extended Nonce) XChaCha20-Poly1305 uses a 24-byte nonce, making random nonce generation safe for high-volume encryption. Set `SODIUM_ENABLED=1` environment variable before building. ```ts const key = randomBytes(32); function encrypt(plaintext: Buffer, aad?: Buffer) { // 24-byte nonce - safe to generate randomly const nonce = randomBytes(24); const cipher = createCipheriv('xchacha20-poly1305', key, nonce); if (aad) cipher.setAAD(aad); const ciphertext = Buffer.concat([ cipher.update(plaintext), cipher.final() ]); const tag = cipher.getAuthTag(); return { ciphertext, nonce, tag }; } function decrypt(ciphertext: Buffer, nonce: Buffer, tag: Buffer, aad?: Buffer) { const decipher = createDecipheriv('xchacha20-poly1305', key, nonce); if (aad) decipher.setAAD(aad); decipher.setAuthTag(tag); return Buffer.concat([ decipher.update(ciphertext), decipher.final() ]); } ``` ### XSalsa20-Poly1305 (NaCl Secretbox) XSalsa20-Poly1305 provides authenticated encryption without AAD support (similar to NaCl's secretbox). ```ts const key = randomBytes(32); const nonce = randomBytes(24); const message = Buffer.from('Secret message'); // Encrypt const cipher = createCipheriv('xsalsa20-poly1305', key, nonce); const ciphertext = Buffer.concat([cipher.update(message), cipher.final()]); const tag = cipher.getAuthTag(); // Decrypt const decipher = createDecipheriv('xsalsa20-poly1305', key, nonce); decipher.setAuthTag(tag); const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]); ``` # DiffieHellman (/docs/api/diffie-hellman) The Diffie-Hellman module implements the Diffie-Hellman key exchange protocol, allowing two parties to establish a shared secret over an insecure channel without ever transmitting the secret itself. This is fundamental to secure communication protocols like TLS, SSH, and VPNs. **TLS/SSL handshakes**, **VPN connections** (IPsec, WireGuard), **secure messaging protocols** (Signal, WhatsApp), **SSH key exchange**, and **P2P encrypted file sharing**. ## Table of Contents * [Theory](#theory) * [Class: DiffieHellman](#class-diffiehellman) * [Module Methods](#module-methods) * [Standard Groups](#standard-groups) * [Real-World Examples](#real-world-examples) * [Security Considerations](#security-considerations) ## Theory The Diffie-Hellman protocol allows two parties (Alice and Bob) to agree on a shared secret even when all their communication is being monitored: 1. **Public Parameters**: Both parties agree on a large prime number $p$ and a generator $g$ 2. **Private Keys**: Alice picks random secret $a$, Bob picks random secret $b$ (never shared) 3. **Public Keys**: Alice computes $A = g^a \pmod p$, Bob computes $B = g^b \pmod p$ 4. **Exchange**: Alice sends $A$ to Bob, Bob sends $B$ to Alice (safe even if intercepted!) 5. **Shared Secret**: Both compute the same secret $s$: ```math s = B^a \pmod p = (g^b)^a \pmod p = g^{ba} \pmod p ``` ```math s = A^b \pmod p = (g^a)^b \pmod p = g^{ab} \pmod p ``` The security relies on the **Discrete Logarithm Problem**: Given `g`, `p`, and `g^a`, it's computationally infeasible to find `a`. Think of it like mixing paint: Alice and Bob each start with a common color (yellow), add their secret color (Alice adds red, Bob adds blue), exchange the mixed colors publicly, then add their secret color again. Both end up with the same final color (brown), but an observer can't recreate it without knowing the secret colors. *** ## Class: DiffieHellman The `DiffieHellman` class implements the Diffie-Hellman key agreement protocol. Instances are created using `getDiffieHellman()` for standard groups or `createDiffieHellman()` for custom parameters. ### dh.generateKeys(\[encoding]) Generates private and public Diffie-Hellman key values. Must be called before `computeSecret()`. If a private key was previously set via `setPrivateKey()`, `generateKeys()` preserves it and only computes the corresponding public key. Subsequent calls are idempotent — they won't regenerate the key pair. **Parameters:** | Name | Type | Description | | :--------- | :------- | :---------------------------------------------------------------------------------------------------------- | | `encoding` | `string` | Optional encoding for the return value: `'hex'`, `'base64'`, or `'base64url'`. If omitted, returns `Buffer` | **Returns:** `Buffer` or `string` - The public key **Examples:** ```ts const dh = getDiffieHellman('modp15'); // Generate keys and get public key as Buffer const publicKey = dh.generateKeys(); console.log('Public key:', publicKey.toString('hex')); // Generate keys and get public key as hex string const publicKeyHex = dh.generateKeys('hex'); // Generate keys and get public key as base64 const publicKeyB64 = dh.generateKeys('base64'); ``` **Important:** Each call to `generateKeys()` creates a **new** key pair. Call it only once per DH instance. *** ### dh.computeSecret(otherPublicKey\[, inputEncoding]\[, outputEncoding]) Computes the shared secret using `otherPublicKey` as the other party's public key. **Parameters:** | Name | Type | Description | | :--------------- | :------------------------------------------- | :------------------------------------------------------------------------------------------------- | | `otherPublicKey` | `string \| Buffer \| TypedArray \| DataView` | The other party's public key | | `inputEncoding` | `string` | Encoding of `otherPublicKey` if it's a string: `'hex'`, `'base64'`, or `'base64url'` | | `outputEncoding` | `string` | Encoding for the return value: `'hex'`, `'base64'`, or `'base64url'`. If omitted, returns `Buffer` | **Returns:** `Buffer` or `string` - The computed shared secret **Important:** The shared secret should be **hashed** before use as an encryption key to remove structural weaknesses. **Examples:** ```ts // Alice's side const alice = getDiffieHellman('modp15'); const alicePublicKey = alice.generateKeys(); // Bob's side const bob = getDiffieHellman('modp15'); const bobPublicKey = bob.generateKeys(); // Compute shared secret (both get the same value!) const aliceSecret = alice.computeSecret(bobPublicKey); const bobSecret = bob.computeSecret(alicePublicKey); console.log(aliceSecret.equals(bobSecret)); // true // With hex encoding const aliceSecretHex = alice.computeSecret(bobPublicKey.toString('hex'), 'hex', 'hex'); ``` *** ### dh.getPublicKey(\[encoding]) Returns the Diffie-Hellman public key. **Parameters:** | Name | Type | Description | | :--------- | :------- | :------------------------------------------------------- | | `encoding` | `string` | Optional encoding: `'hex'`, `'base64'`, or `'base64url'` | **Returns:** `Buffer` or `string` **Examples:** ```ts const dh = getDiffieHellman('modp14'); dh.generateKeys(); const publicKey = dh.getPublicKey(); // Buffer const publicKeyHex = dh.getPublicKey('hex'); // string ``` *** ### dh.getPrivateKey(\[encoding]) Returns the Diffie-Hellman private key. **Never transmit this value!** **Parameters:** | Name | Type | Description | | :--------- | :------- | :------------------------------------------------------- | | `encoding` | `string` | Optional encoding: `'hex'`, `'base64'`, or `'base64url'` | **Returns:** `Buffer` or `string` **Security Warning:** The private key must be kept secret. Anyone who obtains it can compute the shared secret. *** ### dh.getPrime(\[encoding]) Returns the Diffie-Hellman prime. **Parameters:** | Name | Type | Description | | :--------- | :------- | :------------------------------------------------------- | | `encoding` | `string` | Optional encoding: `'hex'`, `'base64'`, or `'base64url'` | **Returns:** `Buffer` or `string` *** ### dh.getGenerator(\[encoding]) Returns the Diffie-Hellman generator. **Parameters:** | Name | Type | Description | | :--------- | :------- | :------------------------------------------------------- | | `encoding` | `string` | Optional encoding: `'hex'`, `'base64'`, or ` base64url'` | **Returns:** `Buffer` or `string` *** ### dh.setPublicKey(publicKey\[, encoding]) Sets the Diffie-Hellman public key. Useful for restoring a DH instance from saved state. **Parameters:** | Name | Type | Description | | :---------- | :----------------- | :--------------------------------------- | | `publicKey` | `string \| Buffer` | The public key to set | | `encoding` | `string` | Encoding of `publicKey` if it's a string | *** ### dh.setPrivateKey(privateKey\[, encoding]) Sets the Diffie-Hellman private key. Useful for restoring a DH instance from saved state. **Parameters:** | Name | Type | Description | | :----------- | :----------------- | :---------------------------------------- | | `privateKey` | `string \| Buffer` | The private key to set | | `encoding` | `string` | Encoding of `privateKey` if it's a string | *** ## Module Methods ### getDiffieHellman(groupName) Creates a `DiffieHellman` instance using a standardized, well-known group. **Recommended** for most applications. **Parameters:** | Name | Type | Description | | :---------- | :------- | :------------------------------------------------------------------- | | `groupName` | `string` | Name of the standard group (see [Standard Groups](#standard-groups)) | **Returns:** `DiffieHellman` instance **Examples:** ```ts // 2048-bit group (minimum for modern use) const dh2048 = getDiffieHellman('modp14'); // 3072-bit group (recommended) const dh3072 = getDiffieHellman('modp15'); // 4096-bit group (high security) const dh4096 = getDiffieHellman('modp16'); ``` *** ### diffieHellman(options) Computes a shared secret using a private key and the other party's public key. This is the modern API that works with `KeyObject`s and supports EC, X25519, X448, and DH key types. **Parameters:** | Name | Type | Description | | :------------------- | :---------- | :--------------------------- | | `options.privateKey` | `KeyObject` | Your private key | | `options.publicKey` | `KeyObject` | The other party's public key | **Returns:** `Buffer` — the computed shared secret. ```ts // X25519 key exchange const alice = generateKeyPairSync('x25519'); const bob = generateKeyPairSync('x25519'); const aliceSecret = diffieHellman({ privateKey: alice.privateKey, publicKey: bob.publicKey }); const bobSecret = diffieHellman({ privateKey: bob.privateKey, publicKey: alice.publicKey }); console.log(aliceSecret.equals(bobSecret)); // true ``` **Supported key types:** `dh`, `ec`, `x25519`, `x448` *** ### createDiffieHellman(prime\[, primeEncoding]\[, generator]\[, generatorEncoding]) Creates a `DiffieHellman` instance with custom parameters. **Advanced use only** - use standard groups unless you have specific requirements. **Parameters:** | Name | Type | Description | | :------------------ | :--------------------------- | :----------------------------------------------------------- | | `prime` | `number \| string \| Buffer` | Prime number (if number, generates prime of that bit length) | | `primeEncoding` | `string` | Encoding of `prime` if it's a string: `'hex'`, `'base64'` | | `generator` | `number \| string \| Buffer` | Generator (default: 2) | | `generatorEncoding` | `string` | Encoding of `generator` if it's a string | **Returns:** `DiffieHellman` instance **Examples:** ```ts // Generate new 2048-bit prime (slow! - do once and save) const dh = createDiffieHellman(2048); const prime = dh.getPrime('hex'); const generator = dh.getGenerator('hex'); // Later, reuse the same parameters const dh2 = createDiffieHellman(prime, 'hex', generator, 'hex'); ``` **Warning:** Generating custom primes is **very slow** and requires cryptographic expertise to avoid weak parameters. Use standard groups instead. *** ## Standard Groups Standardized Diffie-Hellman groups from RFC 3526 and RFC 5114. These are well-tested, secure, and widely compatible. | Group | Bits | Security Level | Use Case | | :------- | :--- | :------------- | :---------------------------------- | | `modp14` | 2048 | \~112-bit | **Minimum** for modern applications | | `modp15` | 3072 | \~128-bit | **Recommended** general purpose | | `modp16` | 4096 | \~152-bit | High security applications | | `modp17` | 6144 | \~176-bit | Very high security | | `modp18` | 8192 | \~192-bit | Maximum security | **Recommendation:** Use `modp15` (3072-bit) or higher for new applications. ```ts // ✅ Good - Modern security const dh = getDiffieHellman('modp15'); // ✅ Better - High security const dh = getDiffieHellman('modp16'); // ⚠️ Acceptable but minimum - Upgrade if possible const dh = getDiffieHellman('modp14'); ``` *** ## Real-World Examples ### Example 1: Secure Chat Application End-to-end encrypted messaging using ephemeral Diffie-Hellman: ```ts import { getDiffieHellman, createHash, createCipheriv, createDecipheriv, randomBytes } from 'react-native-quick-crypto'; class SecureChatSession { private dh: any; private sessionKey?: Buffer; constructor(curveName: string = 'modp15') { // Create new DH instance for this session (Perfect Forward Secrecy) this.dh = getDiffieHellman('modp15'); this.dh.generateKeys(); } getPublicKey(): string { return this.dh.getPublicKey('base64'); } establishSession(peerPublicKeyB64: string): void { const peerPublicKey = Buffer.from(peerPublicKeyB64, 'base64'); const sharedSecret = this.dh.computeSecret(peerPublicKey); // Derive session key using SHA-256 const hash = createHash('sha256'); hash.update(sharedSecret); hash.update('chat-session-key'); // Application-specific salt this.sessionKey = hash.digest(); console.log('Secure session established!'); } encryptMessage(message: string): { iv: string; ciphertext: string } { if (!this.sessionKey) throw new Error('Session not established'); const iv = randomBytes(16); const cipher = createCipheriv('aes-256-cbc', this.sessionKey, iv); let ciphertext = cipher.update(message, 'utf8', 'base64'); ciphertext += cipher.final('base64'); return { iv: iv.toString('base64'), ciphertext }; } decryptMessage(encrypted: { iv: string; ciphertext: string }): string { if (!this.sessionKey) throw new Error('Session not established'); const iv = Buffer.from(encrypted.iv, 'base64'); const decipher = createDecipheriv('aes-256-cbc', this.sessionKey, iv); let message = decipher.update(encrypted.ciphertext, 'base64', 'utf8'); message += decipher.final('utf8'); return message; } } // Usage const alice = new SecureChatSession(); const bob = new SecureChatSession(); // Exchange public keys const alicePubKey = alice.getPublicKey(); const bobPubKey = bob.getPublicKey(); // Establish sessions alice.establishSession(bobPubKey); bob.establishSession(alicePubKey); // Send encrypted message const encrypted = alice.encryptMessage('Hello Bob!'); const decrypted = bob.decryptMessage(encrypted); console.log(decrypted); // "Hello Bob!" ``` ### Example 2: Perfect Forward Secrecy Implement Perfect Forward Secrecy by using ephemeral (one-time) DH keys: ```ts class SecureConnection { private longTermPublicKey: Buffer; private longTermPrivateKey: Buffer; constructor() { // Long-term identity keys (saved, reused) const identity = getDiffieHellman('modp15'); identity.generateKeys(); this.longTermPublicKey = identity.getPublicKey(); this.longTermPrivateKey = identity.getPrivateKey(); } createEphemeralSession(peerLongTermPublicKey: Buffer): { ephemeralPublicKey: Buffer; sessionKey: Buffer; } { // Generate NEW ephemeral DH keys for this session only const ephemeral = getDiffieHellman('modp15'); const ephemeralPublicKey = ephemeral.generateKeys(); // Compute shared secret using ephemeral keys const ephemeralSecret = ephemeral.computeSecret(peerLongTermPublicKey); // Derive session key const hash = createHash('sha256'); hash.update(ephemeralSecret); hash.update(this.longTermPublicKey); // Mix in identity hash.update(peerLongTermPublicKey); const sessionKey = hash.digest(); // Discard ephemeral private key after use // Even if long-term keys are later compromised, // this session cannot be decrypted! return { ephemeralPublicKey, sessionKey }; } } // Usage const alice = new SecureConnection(); const bob = new SecureConnection(); // Alice creates session const aliceSession = alice.createEphemeralSession(bob.longTermPublicKey); // Each session uses different ephemeral keys // Past sessions remain secure even if current keys are compromised ``` ### Example 3: Authenticated Diffie-Hellman Prevent Man-in-the-Middle attacks by signing public keys: ```ts import { getDiffieHellman, createSign, createVerify, createHash, generateKeyPairSync } from 'react-native-quick-crypto'; class AuthenticatedDH { private dh: any; private signingKeys: { publicKey: any; privateKey: any }; constructor() { // DH for key exchange this.dh = getDiffieHellman('modp15'); this.dh.generateKeys(); // RSA for authentication this.signingKeys = generateKeyPairSync('rsa', { modulusLength: 2048 }); } getSignedPublicKey(): { dhPublicKey: string; signature: string; signingPublicKey: any; } { const dhPublicKey = this.dh.getPublicKey('base64'); // Sign DH public key with RSA key const sign = createSign('SHA256'); sign.update(dhPublicKey); const signature = sign.sign(this.signingKeys.privateKey, 'base64'); return { dhPublicKey, signature, signingPublicKey: this.signingKeys.publicKey }; } verifyAndEstablishSecret(peerData: { dhPublicKey: string; signature: string; signingPublicKey: any; }): Buffer { // Verify peer's signature const verify = createVerify('SHA256'); verify.update(peerData.dhPublicKey); const isValid = verify.verify( peerData.signingPublicKey, peerData.signature, 'base64' ); if (!isValid) { throw new Error('Peer authentication failed! MITM attack detected.'); } // Compute shared secret const peerDHKey = Buffer.from(peerData.dhPublicKey, 'base64'); const sharedSecret = this.dh.computeSecret(peerDHKey); // Hash the secret const hash = createHash('sha256'); hash.update(sharedSecret); return hash.digest(); } } // Usage const alice = new AuthenticatedDH(); const bob = new AuthenticatedDH(); // Exchange signed public keys const aliceData = alice.getSignedPublicKey(); const bobData = bob.getSignedPublicKey(); // Establish secrets (verified!) try { const aliceSecret = alice.verifyAndEstablishSecret(bobData); const bobSecret = bob.verifyAndEstablishSecret(aliceData); console.log(aliceSecret.equals(bobSecret)); // true console.log('Authenticated secure channel established!'); } catch (error) { console.error('Authentication failed:', error.message); } ``` ### Example 4: Multi-Party Key Agreement Three-party Diffie-Hellman for group messaging: ```ts // Simple 3-party DH using pairwise secrets class ThreePartyDH { private dh: any; private name: string; constructor(name: string) { this.name = name; this.dh = getDiffieHellman('modp14'); this.dh.generateKeys(); } getPublicKey(): Buffer { return this.dh.getPublicKey(); } computeGroupKey( peer1PublicKey: Buffer, peer2PublicKey: Buffer ): Buffer { // Compute pairwise secrets const secret1 = this.dh.computeSecret(peer1PublicKey); const secret2 = this.dh.computeSecret(peer2PublicKey); // Combine secrets deterministically const combined = Buffer.concat([ secret1.length < secret2.length ? secret1 : secret2, secret1.length < secret2.length ? secret2 : secret1 ]); // Derive group key const hash = createHash('sha256'); hash.update(combined); hash.update('group-key-v1'); return hash.digest(); } } // Usage: Alice, Bob, and Charlie all compute the same group key const alice = new ThreePartyDH('Alice'); const bob = new ThreePartyDH('Bob'); const charlie = new ThreePartyDH('Charlie'); const alicePub = alice.getPublicKey(); const bobPub = bob.getPublicKey(); const charliePub = charlie.getPublicKey(); const aliceGroupKey = alice.computeGroupKey(bobPub, charliePub); const bobGroupKey = bob.computeGroupKey(alicePub, charliePub); const charlieGroupKey = charlie.computeGroupKey(alicePub, bobPub); // All three compute the same group key! console.log(aliceGroupKey.equals(bobGroupKey)); // true console.log(bobGroupKey.equals(charlieGroupKey)); // true ``` *** ## Security Considerations 1. **Use standard groups** - `modp15` or higher for new applications 2. **Hash the shared secret** - Never use raw DH output as encryption key 3. **Authenticate peers** - Vanilla DH is vulnerable to MITM attacks 4. **Use ephemeral keys** - Generate new DH keys per session (Perfect Forward Secrecy) 5. **Minimum 2048 bits** - Smaller groups are vulnerable to pre-computation attacks ### Best Practices **1. Group Selection:** ```ts // ✅ Good - Modern security const dh = getDiffieHellman('modp15'); // ✅ Better - High security const dh = getDiffieHellman('modp16'); // ❌ Bad - Too small const dh = createDiffieHellman(1024); // Vulnerable! ``` **2. Secret Derivation:** ```ts // ✅ Good - Hash the shared secret const sharedSecret = dh.computeSecret(peerPublicKey); const hash = createHash('sha256'); hash.update(sharedSecret); const encryptionKey = hash.digest(); // ❌ Bad - Use raw secret const sharedSecret = dh.computeSecret(peerPublicKey); const iv = Buffer.alloc(16); // Example IV const cipher = createCipheriv('aes-256-cbc', sharedSecret, iv); // Weak! ``` **3. Authentication:** ```ts // ✅ Good: Sign public keys with long-term identity // const signature = signWithIdentityKey(d hPublicKey); // send({ dhPublicKey, signature }); // ❌ Bad: No authentication (vulnerable to MITM) // send({ dhPublicKey }); ``` **4. Perfect Forward Secrecy:** ```ts // ✅ Good - New DH instance per session function newSession() { const dh = getDiffieHellman('modp15'); dh.generateKeys(); return dh; } // ❌ Bad - Reuse same DH instance const dh = getDiffieHellman('modp15'); dh.generateKeys(); // ... reuse for multiple sessions ``` **4. Public Key Validation:** ```ts // ✅ Good - Validate received public keys const { createECDH } = require('react-native-quick-crypto'); const ecdh = createECDH('prime256v1'); ecdh.generateKeys(); const receivedPublicKey = Buffer.from('...', 'hex'); // From peer try { const secret = ecdh.computeSecret(receivedPublicKey); } catch (error) { console.error('Invalid public key:', (error as Error).message); // Reject the key exchange } // ❌ Bad - No validation const untrustedPublicKey = Buffer.from('...', 'hex'); // const secret = ecdh.computeSecret(untrustedPublicKey); // Could throw! ``` ### Man-in-the-Middle (MITM) Vulnerability Basic Diffie-Hellman doesn't authenticate the parties. An attacker can perform two separate key exchanges: ``` Alice ← Attacker → Bob ↓ ↓ ↓ K1 K1 + K2 K2 ``` **Protection:** Combine with authentication (certificates, pre-shared keys, or signed DH public keys). *** ## Common Errors ### Different shared secrets **Cause:** Both parties must use the same group and exchange keys correctly. ```ts // ❌ Wrong - Different groups const alice = getDiffieHellman('modp14'); const bob = getDiffieHellman('modp15'); // Different! // ✅ Correct - Same group const group = 'modp15'; const alice = getDiffieHellman(group); const bob = getDiffieHellman(group); ``` **Cause:** Using own public key instead of peer's. ```ts // ❌ Wrong: Using own public key // const secret = alice.computeSecret(alice.getPublicKey()); // Wrong! // ✅ Correct: Using peer's public key const alice = getDiffieHellman('modp15'); const bob = getDiffieHellman('modp15'); alice.generateKeys(); bob.generateKeys(); const secret = alice.computeSecret(bob.getPublicKey()); ``` *** ### Error: `Supplied key is too small` **Cause:** Using a group with insufficient bit length. **Solution:** Use `modp14` (2048-bit) or larger: ```ts // ❌ Wrong - Too small const dh = createDiffieHellman(1024); // ✅ Correct - Adequate size const dh = getDiffieHellman('modp14'); // 2048-bit ``` *** ### Error: `ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY` **Cause:** The provided public key is invalid or corrupted. **Solutions:** * Verify encoding matches (`'hex'`, `'base64'`, etc.) * Check for transmission errors * Ensure both parties use the same group ```ts // ✅ Correct - Matching encodings const publicKey = dh.getPublicKey('base64'); const secret = peer.computeSecret(publicKey, 'base64'); ``` *** ## Performance Notes **Key Generation Performance** (modp15/3072-bit, typical mobile device): * Key generation: \~50-100ms * Secret computation: \~10-20ms **Recommendations:** 1. **Generate keys once per session**, not per message 2. **Use smaller groups for low-power devices** (but minimum modp14) 3. **Consider ECDH for better performance** - 10-100× faster than DH 4. **Pre-generate DH instances** if you know you'll need them ```ts // Example: Background key generation async function prepareSecureSession(): Promise { return new Promise((resolve) => { setTimeout(() => { const dh = getDiffieHellman('modp15'); dh.generateKeys(); resolve(dh); }, 0); }); } // Usage const dh = await prepareSecureSession(); // DH instance ready for immediate use ``` ### Performance Comparison | Group | Key Gen | Compute | Total | Security | | :------------ | :------ | :------ | :------ | :------------------- | | modp14 (2048) | \~30ms | \~8ms | \~38ms | Minimum | | modp15 (3072) | \~80ms | \~15ms | \~95ms | Recommended | | modp16 (4096) | \~200ms | \~30ms | \~230ms | High | | ECDH P-256 | \~5ms | \~2ms | \~7ms | Equivalent to modp15 | **Conclusion:** For mobile applications with frequent rekeying, consider using ECDH instead of traditional DH for better performance. # ECDH (Elliptic Curve Diffie-Hellman) (/docs/api/ecdh) ECDH (Elliptic Curve Diffie-Hellman) is the elliptic curve variant of the Diffie-Hellman key exchange. It provides the same security guarantees as traditional DH but with **much smaller key sizes**, making it ideal for mobile devices, IoT, and bandwidth-constrained networks. **256-bit ECDH ≈ 3072-bit DH** in security, but ECDH is 10-100× faster. Used in **TLS 1.3**, **Bitcoin**, **Ethereum**, **Signal Protocol**, **WhatsApp**, and modern VPNs. ## Table of Contents * [Theory](#theory) * [Class: ECDH](#class-ecdh) * [Module Methods](#module-methods) * [Supported Curves](#supported-curves) * [Real-World Examples](#real-world-examples) * [Security Considerations](#security-considerations) ## Theory | Feature | ECDH | Traditional DH | | :------------------------------ | :------------------ | :-------------------- | | **Key Size** (128-bit security) | 256 bits (32 bytes) | 3072 bits (384 bytes) | | **Public Key Size** | 32-65 bytes | 384 bytes | | **Key Generation** | \~5ms | \~80ms | | **Secret Computation** | \~2ms | \~15ms | | **Performance** | 10-100× faster | Slower | | **Bandwidth** | Minimal | High | | **Battery Impact** | Lower | Higher | | **Mobile Suitability** | Excellent | Poor | **Conclusion:** ECDH is the modern choice for key exchange on mobile and resource-constrained devices. *** ## Class: ECDH The `ECDH` class implements the Elliptic Curve Diffie-Hellman protocol. Instances are created using the `createECDH()` function. ### Static: ECDH.convertKey(key, curve\[, inputEncoding\[, outputEncoding\[, format]]]) Converts an ECDH public key between formats (compressed, uncompressed, hybrid). **Parameters:** | Name | Type | Description | | :--------------- | :----------------- | :-------------------------------------------------------------------- | | `key` | `string \| Buffer` | The public key to convert | | `curve` | `string` | The curve name (e.g., `'prime256v1'`) | | `inputEncoding` | `string` | Encoding of `key` if string: `'hex'`, `'base64'` | | `outputEncoding` | `string` | Encoding for return value | | `format` | `string` | Output format: `'uncompressed'` (default), `'compressed'`, `'hybrid'` | **Returns:** `Buffer | string` ```ts const ecdh = createECDH('prime256v1'); ecdh.generateKeys(); // Get uncompressed public key (65 bytes for P-256) const uncompressed = ecdh.getPublicKey('hex'); // Convert to compressed format (33 bytes) const compressed = ECDH.convertKey( uncompressed, 'prime256v1', 'hex', 'hex', 'compressed' ); // Convert back to uncompressed const back = ECDH.convertKey( compressed, 'prime256v1', 'hex', 'hex', 'uncompressed' ); ``` *** ### ecdh.generateKeys() Generates private and public ECDH key pair. Must be called before `computeSecret()`. **Returns:** `Buffer` - The public key in uncompressed format (0x04 + x + y coordinates) **Examples:** ```ts const ecdh = createECDH('prime256v1'); // Generate key pair const publicKey = ecdh.generateKeys(); console.log('Public key length:', publicKey.length); // 65 bytes for prime256v1 // Public key format: 0x04 || x-coordinate || y-coordinate console.log('Format byte:', publicKey[0].toString(16)); // '04' ``` *** ### ecdh.computeSecret(otherPublicKey\[, inputEncoding]) Computes the shared secret using the other party's public key. **Parameters:** | Name | Type | Description | | :--------------- | :----------------- | :----------------------------------------------------------------------------------- | | `otherPublicKey` | `string \| Buffer` | The other party's public key | | `inputEncoding` | `string` | Encoding of `otherPublicKey` if it's a string: `'hex'`, `'base64'`, or `'base64url'` | **Returns:** `Buffer` - The computed shared secret (x-coordinate of the resulting elliptic curve point) **Important:** Hash the shared secret before using it as an encryption key. **Examples:** ```ts // Alice's side const alice = createECDH('secp256k1'); const alicePublicKey = alice.generateKeys(); // Bob's side const bob = createECDH('secp256k1'); const bobPublicKey = bob.generateKeys(); // Compute shared secret const aliceSecret = alice.computeSecret(bobPublicKey); const bobSecret = bob.computeSecret(alicePublicKey); console.log(aliceSecret.equals(bobSecret)); // true // With hex encoding const bobPublicKeyHex = bobPublicKey.toString('hex'); const aliceSecretFromHex = alice.computeSecret(bobPublicKeyHex, 'hex'); ``` *** ### ecdh.getPublicKey(\[encoding]) Returns the ECDH public key. **Parameters:** | Name | Type | Description | | :--------- | :------- | :------------------------------------------------------- | | `encoding` | `string` | Optional encoding: `'hex'`, `'base64'`, or `'base64url'` | **Returns:** `Buffer` or `string` **Examples:** ```ts const ecdh = createECDH('prime256v1'); ecdh.generateKeys(); const publicKey = ecdh.getPublicKey(); // Buffer const publicKeyHex = ecdh.getPublicKey('hex'); // string const publicKeyB64 = ecdh.getPublicKey('base64'); // string ``` *** ### ecdh.getPrivateKey() Returns the ECDH private key. **Never transmit this value!** **Returns:** `Buffer` **Security Warning:** Protect the private key - anyone who obtains it can compute all your shared secrets. *** ### ecdh.setPublicKey(publicKey\[, encoding]) Sets the ECDH public key. Useful for restoring state or testing. **Parameters:** | Name | Type | Description | | :---------- | :----------------- | :--------------------------------------- | | `publicKey` | `string \| Buffer` | The public key to set | | `encoding` | `string` | Encoding of `publicKey` if it's a string | *** ### ecdh.setPrivateKey(privateKey\[, encoding]) Sets the ECDH private key. Useful for restoring state or key derivation scenarios. **Parameters:** | Name | Type | Description | | :----------- | :----------------- | :---------------------------------------- | | `privateKey` | `string \| Buffer` | The private key to set | | `encoding` | `string` | Encoding of `privateKey` if it's a string | *** ## Module Methods ### createECDH(curveName) Creates an `ECDH` instance using the specified elliptic curve. **Parameters:** | Name | Type | Description | | :---------- | :------- | :--------------------------------------------------------------------- | | `curveName` | `string` | Name of the elliptic curve (see [Supported Curves](#supported-curves)) | **Returns:** `ECDH` instance **Examples:** ```ts // P-256 (NIST standard, widely supported) const ecdh = createECDH('prime256v1'); // secp256k1 (Bitcoin/Ethereum) const ecdh = createECDH('secp256k1'); // P-384 (higher security) const ecdh = createECDH('secp384r1'); ``` *** ## Supported Curves ### NIST Curves (Recommended for General Use) | Curve Name | Also Known As | Key Size | Security Level | Use Case | | :----------- | :--------------- | :------- | :------------- | :---------------------------------- | | `prime256v1` | P-256, secp256r1 | 256-bit | \~128-bit | **General purpose**, TLS, most APIs | | `secp384r1` | P-384 | 384-bit | \~192-bit | High security applications | | `secp521r1` | P-521 | 521-bit | \~256-bit | Maximum security | ### Koblitz Curves | Curve Name | Key Size | Security Level | Use Case | | :---------- | :------- | :------------- | :------------------------------------------------- | | `secp256k1` | 256-bit | \~128-bit | **Bitcoin**, **Ethereum**, blockchain applications | **Recommendations:** * **General purpose**: Use `prime256v1` (P-256) for maximum compatibility * **Blockchain**: Use `secp256k1` for Bitcoin/Ethereum compatibility * **High security**: Use `secp384r1` or `secp521r1` ```ts // ✅ Good - Widely supported, fast const ecdh = createECDH('prime256v1'); // ✅ Good - If you need blockchain compatibility const ecdh = createECDH('secp256k1'); // ✅ Better - Higher security const ecdh = createECDH('secp384r1'); ``` *** ## Real-World Examples ### Example 1: Secure WebSocket Connection Establish encrypted WebSocket channel without pre-shared keys: ```ts import { createECDH, createHash, createCipheriv, createDecipheriv, randomBytes } from 'react-native-quick-crypto'; class SecureWebSocket { private ecdh: any; private sessionKey?: Buffer; constructor() { this.ecdh = createECDH('prime256v1'); this.ecdh.generateKeys(); } getPublicKey(): string { return this.ecdh.getPublicKey('base64'); } async establishEncryption( ws: WebSocket, peerPublicKeyB64: string ): Promise { const peerPublicKey = Buffer.from(peerPublicKeyB64, 'base64'); const sharedSecret = this.ecdh.computeSecret(peerPublicKey); // Derive session key using HKDF-like approach const hash = createHash('sha256'); hash.update(sharedSecret); hash.update('websocket-encryption-v1'); this.sessionKey = hash.digest(); console.log('WebSocket encryption established'); } encryptMessage(message: string): string { if (!this.sessionKey) throw new Error('Encryption not established'); const iv = randomBytes(12); const cipher = createCipheriv('aes-256-gcm', this.sessionKey, iv); let encrypted = cipher.update(message, 'utf8', 'base64'); encrypted += cipher.final('base64'); const authTag = cipher.getAuthTag(); // Format: iv:authTag:ciphertext return `${iv.toString('base64')}:${authTag.toString('base64')}:${encrypted}`; } decryptMessage(encryptedMessage: string): string { if (!this.sessionKey) throw new Error('Encryption not established'); const [ivB64, tagB64, ciphertext] = encryptedMessage.split(':'); const iv = Buffer.from(ivB64, 'base64'); const authTag = Buffer.from(tagB64, 'base64'); const decipher = createDecipheriv('aes-256-gcm', this.sessionKey, iv); decipher.setAuthTag(authTag); let message = decipher.update(ciphertext, 'base64', 'utf8'); message += decipher.final('utf8'); return message; } } // Client usage const client = new SecureWebSocket(); const ws = new WebSocket('wss://example.com'); ws.onopen = () => { // Send public key ws.send(JSON.stringify({ type: 'key-exchange', publicKey: client.getPublicKey() })); }; ws.onmessage = async (event) => { const data = JSON.parse(event.data); if (data.type === 'key-exchange') { await client.establishEncryption(ws, data.publicKey); // Now send encrypted messages const encrypted = client.encryptMessage('Hello Server!'); ws.send(JSON.stringify({ type: 'encrypted', data: encrypted })); } else if (data.type === 'encrypted') { const decrypted = client.decryptMessage(data.data); console.log('Received:', decrypted); } }; ``` ### Example 2: End-to-End Encrypted Chat Full E2E encrypted messaging with key rotation: ```ts import { createECDH, createHash, randomBytes, createCipheriv, createDecipheriv } from 'react-native-quick-crypto'; interface EncryptedMessage { sessionId: string; iv: string; authTag: string; ciphertext: string; timestamp: number; } class E2EMessenger { private sessions = new Map(); // sessionId -> key private currentSessionId?: string; // Create new session with Perfect Forward Secrecy createSession(peerPublicKeyB64: string): { sessionId: string; publicKey: string; } { // Generate ephemeral ECDH keys for this session (PFS!) const ecdh = createECDH('prime256v1'); const publicKey = ecdh.generateKeys(); // Compute shared secret const peerPublicKey = Buffer.from(peerPublicKeyB64, 'base64'); const sharedSecret = ecdh.computeSecret(peerPublicKey); // Derive session key const hash = createHash('sha256'); hash.update(sharedSecret); hash.update(randomBytes(16)); // Add randomness const sessionKey = hash.digest(); // Store session const sessionId = randomBytes(16).toString('hex'); this.sessions.set(sessionId, sessionKey); this.currentSessionId = sessionId; // Ephemeral keys are discarded after this function // Even if compromised later, this session remains secure! return { sessionId, publicKey: publicKey.toString('base64') }; } encryptMessage( message: string, sessionId?: string ): EncryptedMessage { const sid = sessionId || this.currentSessionId; if (!sid) throw new Error('No active session'); const sessionKey = this.sessions.get(sid); if (!sessionKey) throw new Error('Session not found'); const iv = randomBytes(12); const cipher = createCipheriv('aes-256-gcm', sessionKey, iv); let ciphertext = cipher.update(message, 'utf8', 'base64'); ciphertext += cipher.final('base64'); const authTag = cipher.getAuthTag(); return { sessionId: sid, iv: iv.toString('base64'), authTag: authTag.toString('base64'), ciphertext, timestamp: Date.now() }; } decryptMessage(encrypted: EncryptedMessage): string { const sessionKey = this.sessions.get(encrypted.sessionId); if (!sessionKey) throw new Error('Session not found or expired'); const iv = Buffer.from(encrypted.iv, 'base64'); const authTag = Buffer.from(encrypted.authTag, 'base64'); const decipher = createDecipheriv('aes-256-gcm', sessionKey, iv); decipher.setAuthTag(authTag); let message = decipher.update(encrypted.ciphertext, 'base64', 'utf8'); message += decipher.final('utf8'); return message; } rotateSession(peerPublicKeyB64: string) { // Create new session (Perfect Forward Secrecy) return this.createSession(peerPublicKeyB64); } expireSession(sessionId: string) { this.sessions.delete(sessionId); } } // Usage const alice = new E2EMessenger(); const bob = new E2EMessenger(); // Initial key exchange const aliceSession = alice.createSession(bob.createSession( alice.createSession('dummy').publicKey ).publicKey); const bobPublicKey = aliceSession.publicKey; const bobSession = bob.createSession(bobPublicKey); // Send encrypted message const encrypted = alice.encryptMessage('Secret message', aliceSession.sessionId); const decrypted = bob.decryptMessage(encrypted); console.log(decrypted); // "Secret message" // Rotate session after some time (PFS!) setTimeout(() => { const newSession = alice.rotateSession(bob.createSession('...').publicKey); alice.expireSession(aliceSession.sessionId); }, 3600000); // Rotate every hour ``` ### Example 3: P2P File Encryption Encrypt files for specific peers: ```ts import { createECDH, createHash, randomBytes, createCipheriv, createDecipheriv } from 'react-native-quick-crypto'; class P2PFileEncryption { private ecdh: any; private publicKey: Buffer; constructor() { this.ecdh = createECDH('secp256k1'); this.publicKey = this.ecdh.generateKeys(); } getPublicKey(): string { return this.publicKey.toString('hex'); } async encryptFile( filePath: string, recipientPublicKeyHex: string ): Promise<{ encryptedPath: string; metadata: any }> { // Compute shared secret with recipient const recipientPublicKey = Buffer.from(recipientPublicKeyHex, 'hex'); const sharedSecret = this.ecdh.computeSecret(recipientPublicKey); // Derive file encryption key const hash = createHash('sha256'); hash.update(sharedSecret); hash.update('file-encryption'); const fileKey = hash.digest(); // Read file const fileData = await RNFS.readFile(filePath, 'base64'); const fileBuffer = Buffer.from(fileData, 'base64'); // Encrypt file const iv = randomBytes(16); const cipher = createCipheriv('aes-256-cbc', fileKey, iv); const encrypted = Buffer.concat([ cipher.update(fileBuffer), cipher.final() ]); // Save encrypted file const encryptedPath = filePath + '.encrypted'; await RNFS.writeFile( encryptedPath, encrypted.toString('base64'), 'base64' ); return { encryptedPath, metadata: { iv: iv.toString('hex'), originalName: filePath.split('/').pop(), size: fileBuffer.length, encryptedSize: encrypted.length } }; } async decryptFile( encryptedPath: string, senderPublicKeyHex: string, metadata: any, outputPath: string ): Promise { // Compute shared secret with sender const senderPublicKey = Buffer.from(senderPublicKeyHex, 'hex'); const sharedSecret = this.ecdh.computeSecret(senderPublicKey); // Derive file encryption key (same as sender) const hash = createHash('sha256'); hash.update(sharedSecret); hash.update('file-encryption'); const fileKey = hash.digest(); // Read encrypted file const encryptedData = await RNFS.readFile(encryptedPath, 'base64'); const encrypted = Buffer.from(encryptedData, 'base64'); // Decrypt file const iv = Buffer.from(metadata.iv, 'hex'); const decipher = createDecipheriv('aes-256-cbc', fileKey, iv); const decrypted = Buffer.concat([ decipher.update(encrypted), decipher.final() ]); // Save decrypted file await RNFS.writeFile(outputPath, decrypted.toString('base64'), 'base64'); } } // Usage const alice = new P2PFileEncryption(); const bob = new P2PFileEncryption(); // Alice encrypts file for Bob const { encryptedPath, metadata } = await alice.encryptFile( '/path/to/document.pdf', bob.getPublicKey() ); // Send: encryptedPath, metadata, alice.getPublicKey() to Bob // Bob decrypts await bob.decryptFile( encryptedPath, alice.getPublicKey(), metadata, '/path/to/decrypted.pdf' ); ``` ### Example 4: Derive Multiple Keys from Single Secret Use ECDH secret to derive multiple purpose-specific keys: ```ts function deriveMultipleKeys( sharedSecret: Buffer, ...purposes: string[] ): Map { const keys = new Map(); for (const purpose of purposes) { const hash = createHash('sha256'); hash.update(sharedSecret); hash.update(purpose); keys.set(purpose, hash.digest()); } return keys; } // Setup const alice = createECDH('prime256v1'); const bob = createECDH('prime256v1'); const alicePublic = alice.generateKeys(); const bobPublic = bob.generateKeys(); const sharedSecret = alice.computeSecret(bobPublic); // Derive multiple keys for different purposes const keys = deriveMultipleKeys( sharedSecret, 'encryption', // For message encryption 'authentication', // For message authentication 'file-encryption', // For file encryption 'metadata' // For metadata encryption ); // Use purpose-specific keys const encryptionKey = keys.get('encryption')!; const authKey = keys.get('authentication')!; const fileKey = keys.get('file-encryption')!; console.log('Encryption key:', encryptionKey.toString('hex')); console.log('Auth key:', authKey.toString('hex')); console.log('File key:', fileKey.toString('hex')); ``` *** ## Security Considerations 1. **Use standard curves** - P-256 (prime256v1) for general use 2. **Hash the shared secret** - Never use raw ECDH output as encryption key 3. **Ephemeral keys** - Generate new ECDH keys per session (Perfect Forward Secrecy) 4. **Validate public keys** - Ensure received keys are valid curve points 5. **Authenticate peers** - ECDH doesn't provide authentication ### Best Practices **1. Curve Selection:** ```ts // ✅ Good - Widely supported, secure const ecdh = createECDH('prime256v1'); // ✅ Good - If you need blockchain compatibility const ecdh = createECDH('secp256k1'); // ❌ Avoid - Non-standard curves unless required ``` **2. Secret Derivation:** ```ts // ✅ Good - Hash the shared secret const sharedSecret = ecdh.computeSecret(peerPublicKey); const hash = createHash('sha256'); hash.update(sharedSecret); const encryptionKey = hash.digest(); // ❌ Bad - Use raw secret const sharedSecret = ecdh.computeSecret(peerPublicKey); const cipher = createCipheriv('aes-256-cbc', sharedSecret, iv); // Weak! ``` **3. Perfect Forward Secrecy:** ```ts // ✅ Good - New ECDH instance per session function newSession(peerPublicKey: Buffer) { const ecdh = createECDH('prime256v1'); ecdh.generateKeys(); return ecdh.computeSecret(peerPublicKey); } // ❌ Bad - Reuse same ECDH instance const ecdh = createECDH('prime256v1'); ecdh.generateKeys(); // ... reuse for multiple sessions ``` **4. Public Key Validation:** ```ts // ✅ Good: Validate received public keys // try { // const secret = ecdh.computeSecret(receivedPublicKey); // } catch (error) { // console.error('Invalid public key'); // } // ❌ Bad: No validation // const secret = ecdh.computeSecret(untrustedPublicKey); // Could throw! ``` *** ## Common Errors ### Error: `Invalid key size` **Cause:** Public key has incorrect size for the curve. **Solution:** Ensure both parties use the same curve: ```ts // ❌ Wrong - Different curves const alice = createECDH('prime256v1'); const bob = createECDH('secp384r1'); // Different! // ✅ Correct - Same curve const curve = 'prime256v1'; const alice = createECDH(curve); const bob = createECDH(curve); ``` *** ### Error: `Point is not on curve` **Cause:** The provided public key is invalid or corrupted. **Solutions:** * Verify encoding consistency * Check for transmission errors * Ensure same curve on both sides ```ts // ✅ Correct - Consistent encoding const publicKey = alice.getPublicKey('hex'); const secret = bob.computeSecret(publicKey, 'hex'); ``` *** ### Different shared secrets **Cause:** Using different curves or wrong public keys. ```ts // ❌ Wrong: Using own public key // const secret = alice.computeSecret(alice.getPublicKey()); // Wrong! // ✅ Correct: Using peer's public key const alice = createECDH('prime256v1'); const bob = createECDH('prime256v1'); alice.generateKeys(); bob.generateKeys(); const secret = alice.computeSecret(bob.getPublicKey()); ``` *** ## Performance Notes **ECDH Performance** (prime256v1, typical mobile device): * Key generation: \~5ms * Secret computation: \~2ms * **Total**: \~7ms per key exchange **Comparison with Traditional DH** (modp15/3072-bit): * Key generation: \~80ms * Secret computation: \~15ms * **Total**: \~95ms **ECDH is 13× faster!** ### Performance Tips 1. **Reuse ECDH instances** within a session (but not across sessions) 2. **Pre-generate keys** in background for immediate use 3. **Use prime256v1** for hardware acceleration on many devices 4. **Batch operations** when establishing multiple connections ```ts // Example: Pre-generate ECDH instances const ecdhPool: any[] = []; // Background task for (let i = 0; i < 10; i++) { const ecdh = createECDH('prime256v1'); ecdh.generateKeys(); ecdhPool.push(ecdh); } // Instant key exchange function quickKeyExchange(peerPublicKey: Buffer): Buffer { const ecdh = ecdhPool.pop(); if (!ecdh) throw new Error('Pool empty'); return ecdh.computeSecret(peerPublicKey); } ``` # Edwards & Montgomery Curves (/docs/api/ed25519) # Edwards & Montgomery Curves ## Table of Contents * [Theory](#theory) * [Algorithm Comparison](#algorithm-comparison) * [Node.js Compatible API](#nodejs-compatible-api) * [Ed Class (Noble-style API)](#ed-class-noble-style-api) * [Best Practices](#best-practices) ## Theory **Edwards curves** (Ed25519, Ed448) provide digital signatures. **Montgomery curves** (X25519, X448) provide key exchange (ECDH). These are state-of-the-art cryptographic primitives that offer high security, high performance, and small key sizes. **Ed25519** is the gold standard for signatures (used in SSH, TLS 1.3, Signal). **X25519** is the gold standard for key exchange (ECDH). **Ed448/X448** provide higher security (224-bit vs 128-bit) at the cost of larger keys and slightly slower operations. ## Algorithm Comparison | Algorithm | Type | Security | Key Size | Signature/Secret | Use Case | | :-------- | :----------- | :------- | :------- | :--------------- | :---------------------------- | | `Ed25519` | Signature | 128-bit | 32 B | 64 B | **Recommended** for most apps | | `Ed448` | Signature | 224-bit | 57 B | 114 B | High security requirements | | `X25519` | Key Exchange | 128-bit | 32 B | 32 B | **Recommended** ECDH | | `X448` | Key Exchange | 224-bit | 56 B | 56 B | High security ECDH | ## Node.js Compatible API Use `generateKeyPair` or `generateKeyPairSync` for Node.js-compatible key generation. ```ts // Ed25519 (Signatures) const edKeys = generateKeyPairSync('ed25519'); console.log(edKeys.publicKey.export({ format: 'pem', type: 'spki' })); // X25519 (Key Exchange) const xKeys = generateKeyPairSync('x25519'); console.log(xKeys.publicKey.export({ format: 'pem', type: 'spki' })); ``` ### Ed25519 Signatures (Node.js API) ```ts const { publicKey, privateKey } = generateKeyPairSync('ed25519'); const message = Buffer.from('Hello, secure world!'); // Sign (one-shot) const signature = sign(null, message, privateKey); console.log('Signature:', signature.toString('hex')); // Verify (one-shot) const isValid = verify(null, message, publicKey, signature); console.log('Valid:', isValid); // true ``` ### X25519 Key Exchange (Node.js API) ```ts // Alice generates her key pair const alice = generateKeyPairSync('x25519'); // Bob generates his key pair const bob = generateKeyPairSync('x25519'); // Both compute the same shared secret const aliceSecret = diffieHellman({ privateKey: alice.privateKey, publicKey: bob.publicKey }); const bobSecret = diffieHellman({ privateKey: bob.privateKey, publicKey: alice.publicKey }); // aliceSecret and bobSecret are identical ``` ### Ed448 Signatures (Node.js API) Ed448 provides 224-bit security (vs 128-bit for Ed25519). The API is identical: ```ts const { publicKey, privateKey } = generateKeyPairSync('ed448'); const message = Buffer.from('high-security message'); const signature = sign(null, message, privateKey); const isValid = verify(null, message, publicKey, signature); ``` ### X448 Key Exchange (Node.js API) X448 provides 224-bit security for key exchange: ```ts const alice = generateKeyPairSync('x448'); const bob = generateKeyPairSync('x448'); const aliceSecret = diffieHellman({ privateKey: alice.privateKey, publicKey: bob.publicKey }); const bobSecret = diffieHellman({ privateKey: bob.privateKey, publicKey: alice.publicKey }); console.log(aliceSecret.equals(bobSecret)); // true ``` *** ## Ed Class (Noble-style API) The `Ed` class provides a simpler, more direct API inspired by the [@noble/curves](https://github.com/paulmillr/noble-curves) library. This is **not** part of the Node.js crypto API but offers a convenient alternative for developers familiar with noble libraries. ### Creating an Ed Instance ```ts // For Ed25519 signatures const ed = new Ed('ed25519', {}); // For X25519 key exchange const x = new Ed('x25519', {}); // For Ed448 signatures const ed448 = new Ed('ed448', {}); // For X448 key exchange const x448 = new Ed('x448', {}); ``` ### Key Generation ```ts const ed = new Ed('ed25519', {}); // Synchronous key generation ed.generateKeyPairSync(); // Or async await ed.generateKeyPair(); // Get the keys as ArrayBuffer const publicKey = ed.getPublicKey(); const privateKey = ed.getPrivateKey(); ``` ### Signing and Verifying ```ts const ed = new Ed('ed25519', {}); ed.generateKeyPairSync(); const message = new TextEncoder().encode('Hello, world!'); // Sign (async) const signature = await ed.sign(message); // Verify (async) const isValid = await ed.verify(signature, message); console.log('Valid:', isValid); // true // Sync versions also available const signatureSync = ed.signSync(message); const isValidSync = ed.verifySync(signatureSync, message); ``` ### X25519 Shared Secret (Diffie-Hellman) ```ts // Alice const alice = new Ed('x25519', {}); alice.generateKeyPairSync(); // Bob const bob = new Ed('x25519', {}); bob.generateKeyPairSync(); // Compute shared secret const aliceSecret = alice.getSharedSecret( alice.getPrivateKey(), bob.getPublicKey() ); const bobSecret = bob.getSharedSecret( bob.getPrivateKey(), alice.getPublicKey() ); // Both secrets are identical - use for symmetric encryption ``` ### Ed Class Methods Reference | Method | Description | | :--------------------------------------- | :------------------------------------------------ | | `generateKeyPair()` | Async key pair generation | | `generateKeyPairSync()` | Sync key pair generation | | `getPublicKey()` | Returns public key as `ArrayBuffer` | | `getPrivateKey()` | Returns private key as `ArrayBuffer` | | `sign(message, key?)` | Async signing (uses internal key if not provided) | | `signSync(message, key?)` | Sync signing | | `verify(signature, message, key?)` | Async verification | | `verifySync(signature, message, key?)` | Sync verification | | `getSharedSecret(privateKey, publicKey)` | X25519/X448 Diffie-Hellman | | `diffieHellman(options, callback?)` | Node.js-style DH with KeyObjects | *** ## Best Practices Never confuse Ed25519 (signing) with X25519 (encryption/exchange). They use the same underlying curve but different coordinates and formulas. 1. **Use Ed25519 for Signatures**: It's faster and safer than RSA. 2. **Use X25519 for ECDH**: It's efficient and secure. 3. **Don't use Ed25519 keys for X25519** directly without conversion (and vice versa). 4. **Choose the right API**: Use Node.js API for compatibility, Ed class for simplicity. ## Common Errors ### Wrong Algorithm Trying to use RSA options with Ed25519. ```ts // ❌ Wrong - Ed25519 hashes internally const signer = createSign('SHA256'); // ✅ Correct - use null for Ed25519 const signature = sign(null, data, ed25519PrivateKey); ``` ### Key Mismatch Mixing Ed25519 and X25519 keys. ```ts // ❌ Wrong - can't sign with X25519 key sign(null, data, x25519PrivateKey); // Error: invalid key type // ✅ Correct - use Ed25519 for signing sign(null, data, ed25519PrivateKey); ``` ### Wrong Ed Class Type ```ts // ❌ Wrong - can't sign with x25519 Ed instance const x = new Ed('x25519', {}); x.generateKeyPairSync(); await x.sign(message); // Error! // ✅ Correct - use ed25519 for signing const ed = new Ed('ed25519', {}); ed.generateKeyPairSync(); await ed.sign(message); ``` # Hash (/docs/api/hash) The `Hash` class is a utility for creating fixed-size message digests from arbitrary data. It is fully compatible with the Node.js `crypto.createHash` API and implements the `stream.Transform` interface. ## Table of Contents * [Theory](#theory) * [Class: Hash](#class-hash) * [Stream API](#stream-api) * [Module Methods](#module-methods) * [Supported Algorithms](#supported-algorithms) * [Real-World Examples](#real-world-examples) * [Security Considerations](#security-considerations) ## Theory A cryptographic hash function `H(x)` takes an input of arbitrary length and produces a fixed-length output (the "digest"). Key properties include: 1. **Deterministic**: The same input always produces the exact same output. 2. **Efficient**: It is computationally fast to calculate the hash for any given message. 3. **Avalanche Effect**: A small change to the input (e.g., flipping 1 bit) should change the output so significantly that it appears uncorrelated. 4. **Pre-image Resistance**: Given a hash `h`, it should be computationally infeasible to find any message `m` such that `H(m) = h`. 5. **Collision Resistance**: It should be computationally infeasible to find two different messages `m1` and `m2` such that `H(m1) = H(m2)`. Algorithms like **MD5**, **SHA-1**, and **SHA-2** (SHA-256, SHA-512) use the Merkle-Damgård construction. The input is padded and split into fixed-size blocks. A compression function iteratively processes these blocks. **SHA-3** (Keccak) uses a "Sponge" construction. It "absorbs" data into a large internal state, permutes it, and then "squeezes" out the hash. This allows for arbitrary output lengths (XOFs), utilized in SHAKE128/SHAKE256. RNQC optimizes passing strings to native code. If you have string data, pass it directly to `update()` rather than converting it to a Buffer first. This avoids an unnecessary round-trip copy across the React Native bridge. *** ## Class: Hash The `Hash` class creates digest streams. Instances are created using `createHash()`. ### hash.update(data\[, inputEncoding]) Updates the hash content with the given `data`. This method can be called multiple times with new data as it is streamed. **Parameters:** **Returns:** `Hash` (this, for chaining) **Example:** ```ts const hash = createHash('sha512'); hash.update('utf8 string'); hash.update('48656c6c6f', 'hex'); // "Hello" ``` *** ### hash.digest(\[encoding]) Calculates the digest of all of the data passed to be hashed (using the `hash.update()` method). **Parameters:** **Returns:** `Buffer | string` The `Hash` object can not be used again after `hash.digest()` method has been called. Attempting to call `update` or `digest` again will throw a native error (`ERR_CRYPTO_HASH_FINALIZED`). **Example:** ```ts twoslash // @noErrors const hash = createHash('sha256'); hash.update('hello'); const digest = hash.digest('hex'); // ^? ``` *** ### hash.copy(\[options]) Creates a new `Hash` object that contains a **deep copy** of the internal state of the current `Hash` object. **Parameters:** **Returns:** `Hash` (New instance) **Example:** ```ts const hash = createHash('sha256'); hash.update('block1'); const snapshot = hash.copy(); hash.update('block2-A'); snapshot.update('block2-B'); ``` *** ## Stream API Since `Hash` implements `stream.Transform`, you can use standard Node.js stream methods. ### hash.write(chunk\[, encoding]\[, callback]) Writes data to the stream. ### hash.pipe(destination) Pipes the output digest to a destination stream. **Example:** ```ts const hash = createHash('sha256'); const input = fs.createReadStream('file.txt'); input.pipe(hash).pipe(process.stdout); ``` *** ## Module Methods ### hash(algorithm, data\[, outputEncoding]) One-shot hashing — see [Utilities](/docs/api/utilities#one-shot-hashing) for full details. *** ### createHash(algorithm\[, options]) Creates and returns a `Hash` object that can be used to generate hash digests using the given `algorithm`. **Parameters:** **Returns:** `Hash` ### getHashes() Returns an array of the names of the supported hash algorithms. **Returns:** `string[]` *** ## Supported Algorithms Support depends on the version of OpenSSL bundled with the OS (iOS/Android), but typically includes: | Algorithm | Digest Size | Security | Notes | | :------------------- | :--------------- | :----------- | :----------------------------------------------- | | **MD5** | 128 bits | ❌ **Broken** | Use only for non-crypto checksums. | | **SHA-1** | 160 bits | ❌ **Broken** | Do not use for new signatures. | | **SHA-256** | 256 bits | ✅ **Secure** | Industry standard. | | **SHA-512** | 512 bits | ✅ **Secure** | Faster on 64-bit CPUs. | | **SHA-3** | Variable | ✅ **Secure** | NIST standard (Keccak). | | **SHA3-256/384/512** | 256/384/512 bits | ✅ **Secure** | Fixed-output SHA-3 variants. | | **SHAKE128/256** | Variable | ✅ **Secure** | XOF (Extendable Output) via cSHAKE in WebCrypto. | | **BLAKE2b** | 512 bits | ✅ **Secure** | High speed. | *** ## Real-World Examples ### File Checksum (Streaming) Computing the hash of a large file without loading it entirely into memory. ```ts async function computeFileHash(filePath: string): Promise { const hash = createHash('sha256'); const stats = await RNFS.stat(filePath); const fileSize = stats.size; const chunkSize = 1024 * 1024; // 1MB let offset = 0; while (offset < fileSize) { const chunk = await RNFS.read(filePath, chunkSize, offset, 'base64'); hash.update(chunk, 'base64'); offset += chunkSize; } return hash.digest('hex'); } ``` ### Git Object ID (SHA-1) Git calculates Object IDs (OIDs) by taking the SHA-1 hash of: `type + space + length + null byte + content`. ```ts function calculateGitBlobId(content: string): string { const hash = createHash('sha1'); const size = Buffer.byteLength(content, 'utf8'); const header = `blob ${size}\0`; hash.update(header); hash.update(content); return hash.digest('hex'); } ``` *** ## Security Considerations ### Algorithm Selection Always prefer SHA-256 or SHA-512 for new applications. Avoid MD5 and SHA-1 unless required for compatibility with legacy systems. ### Password Hashing **Do not** use `createHash` (SHA-256 etc.) for passwords. They are too fast and vulnerable to brute-force attacks. Use `scrypt` or `pbkdf2` instead. # HKDF (/docs/api/hkdf) ## Table of Contents * [Theory](#theory) * [Module Methods](#module-methods) ## Theory HKDF (RFC 5869) is a simple key derivation function based on HMAC. It is often used to derive multiple session keys from a single master secret. It follows a standard "Extract-then-Expand" paradigm: 1. **Extract**: Derives a fixed-length pseudorandom key ($PRK$) from the input keying material ($IKM$) and optional salt. 2. **Expand**: Expands the $PRK$ into multiple cryptographically strong output keys. ```math PRK = \text{HMAC-Hash}(salt, IKM) ``` ```math OKM = \text{HMAC-Hash}(PRK, info \parallel 0x01) \parallel \dots ``` ## Module Methods ### hkdfSync(digest, key, salt, info, keylen) Synchronous HKDF derivation. ```ts // Combined Extract + Expand const derived = QuickCrypto.hkdfSync( 'sha256', // Digest 'ikm-secret', // Input Key Material 'salt', // Salt 'context-info', // Context Info 64 // Output length ); ``` ### hkdf(digest, key, salt, info, keylen, callback) # HMAC (/docs/api/hmac) The `Hmac` module provides support for creating cryptographic **Hash-based Message Authentication Codes** (HMAC). It uses a cryptographic hash function (like SHA-256) in combination with a secret key to verify both the integrity and authenticity of a message. ## Table of Contents * [Theory](#theory) * [Class: Hmac](#class-hmac) * [Module Methods](#module-methods) * [Real-World Examples](#real-world-examples) * [Security Considerations](#security-considerations) ## Theory A Message Authentication Code (MAC) allows two parties who share a secret key to verify that a message has not been tampered with. 1. **Sender**: Computes `Tag = HMAC(Key, Message)` and sends `[Message, Tag]`. 2. **Receiver**: Computes `ExpectedTag = HMAC(Key, ReceivedMessage)`. 3. **Verify**: If `Tag === ExpectedTag`, the message is authentic. HMAC is defined in **RFC 2104**. It avoids simple "length extension attacks" that plague naive constructions like `Hash(Key + Message)` by using a nested hashing structure: ```math HMAC(K, m) = H((K' \oplus opad) \parallel H((K' \oplus ipad) \parallel m)) ``` Where: * `H` is the hash function. * `K'` is the key padded to block size. * `opad` is the outer padding (0x5c repeated). * `ipad` is the inner padding (0x36 repeated). *** ## Class: Hmac The `Hmac` class creates HMAC streams. Instances are created using `createHmac()`. ### hmac.update(data\[, inputEncoding]) Updates the HMAC content with the given `data`. This method can be called multiple times. **Parameters:** **Returns:** `Hmac` **Example:** ```ts hmac.update('part 1'); hmac.update('part 2'); ``` *** ### hmac.digest(\[encoding]) Calculates the HMAC digest of all of the data passed. **Parameters:** **Returns:** `Buffer | string` **Example:** ```ts const hmac = createHmac('sha256', 'secret'); hmac.update('data'); const sig = hmac.digest('hex'); ``` *** ## Module Methods ### createHmac(algorithm, key\[, options]) Creates and returns an `Hmac` object. **Parameters:** **Returns:** `Hmac` **Example:** ```ts const hmac = createHmac('sha256', 'super-secret-key'); ``` *** ## Real-World Examples ### API Request Signing (AWS Style) Rest APIs often use HMAC to authenticate requests. ```ts function signApiRequest( method: string, path: string, body: string, timestamp: string, secret: string ): string { // Create the canonical string: method + path + timestamp + bodyHash const bodyHash = createHmac('sha256', secret).update(body).digest('hex'); const stringToSign = `${method}\n${path}\n${timestamp}\n${bodyHash}`; // Sign the canonical string with the secret const hmac = createHmac('sha256', secret); hmac.update(stringToSign); return hmac.digest('hex'); } ``` ### Webhook Signature Validation When receiving a webhook (e.g., from Stripe or GitHub), you must verify it came from them using `timingSafeEqual`. ```ts function verifyWebhook( payload: string, signatureHeader: string, secret: string ): boolean { // Compute expected signature const hmac = createHmac('sha256', secret); hmac.update(payload); const expectedInfo = hmac.digest('hex'); // Constants-time comparison const expectedBuf = Buffer.from(expectedInfo, 'hex'); const receivedBuf = Buffer.from(signatureHeader, 'hex'); if (expectedBuf.length !== receivedBuf.length) { return false; } return timingSafeEqual(expectedBuf, receivedBuf); } ``` ### TOTP (Google Authenticator) Time-Based One-Time Passwords use HMAC-SHA1. ``` TOTP = Truncate(HMAC-SHA1(K, Floor(Time / 30))) ``` ```ts function generateTOTP(secretBytes: Buffer): string { const time = Math.floor(Date.now() / 1000 / 30); const timeBuf = Buffer.alloc(8); timeBuf.writeBigUInt64BE(BigInt(time)); const hmac = createHmac('sha1', secretBytes); hmac.update(timeBuf); const hash = hmac.digest(); // Dynamic Truncation const offset = hash[hash.length - 1] & 0x0f; const binary = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16) | ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff); const otp = binary % 1000000; return otp.toString().padStart(6, '0'); } ``` *** ## Security Considerations ### Timing Attacks When comparing HMAC signatures, **never** use standard string comparisons (`===` or `==`). This creates a timing side-channel where the comparison returns faster if the first byte is wrong, leaking information to an attacker. usage of `crypto.timingSafeEqual()` is mandatory for verifying signatures. ### Key Strength The security of the HMAC is directly tied to the strength of the secret key. If the key is weak (e.g., a short password), an attacker can brute-force the key offline by observing a single valid `(message, tag)` pair. # API Reference (/docs/api) RNQC mirrors the Node.js `crypto` API while adding specialized high-performance modules. ## Core Modules Global polyfill injection for drop-in compatibility. Symmetric encryption (AES, ChaCha20, XChaCha20). Message digests (SHA-2, SHA-3, MD5). Keyed-hash message authentication. CSPRNG, UUIDs, and random integers. Key generation (RSA, EC) and KeyObject management. Digital signatures (RSA, ECDSA, Ed25519). RSA asymmetric encryption/decryption. One-shot hashing, timing-safe comparison, primes, introspection. ## Key Exchange Classic DH key exchange with standard groups. Elliptic Curve Diffie-Hellman (P-256, secp256k1). Ed25519, Ed448, X25519, X448 signatures and key exchange. ## Key Derivation Password-based key derivation (RFC 2898). Memory-hard key derivation. Extract-and-Expand KDF (RFC 5869). Memory-hard password hashing (PHC winner). Next-gen high-performance hashing and KDF. ## Advanced ML-DSA signatures and ML-KEM key encapsulation. Keccak Message Authentication Code (KMAC128/KMAC256). SPKAC certificate request processing. X.509 certificate parsing and validation. W3C Web Cryptography API implementation. # Install Polyfills (/docs/api/install) The `install()` function is a helper that patches the global JavaScript environment to make `react-native-quick-crypto` behave as a drop-in replacement for Node.js `crypto`. This modifies `global.Buffer` and `global.crypto`. Ensure you want this global side-effect before calling. ## Installation Flow ### Import Import the install function at the very top of your application's entry file (e.g., `index.js`). ```ts title="index.ts" import { install } from 'react-native-quick-crypto'; ``` ### Execute Call `install()` **before** any other imports that might depend on crypto. ```ts title="index.ts" install(); // Now import your App import App from './src/App'; ``` ### Verify You can now use global crypto without imports. ```ts // Anywhere in your app const id = crypto.randomUUID(); const buf = Buffer.from('hello'); ``` ## What gets polyfilled? 1. **`global.Buffer`**: Re-exported from [`@craftzdog/react-native-buffer`](https://github.com/craftzdog/react-native-buffer). You can also import it directly: `import { Buffer } from 'react-native-quick-crypto'`. 2. **`global.crypto`**: Points to `QuickCrypto`. 3. **`global.process`**: Adds `process.nextTick` (mapped to `setImmediate`). 4. **`global.base64ToArrayBuffer` / `global.base64FromArrayBuffer`**: Native base64 encoding/decoding from [`react-native-quick-base64`](https://github.com/craftzdog/react-native-quick-base64). # Keys (/docs/api/keys) The `Keys` module manages the creation, import, export, and conversion of cryptographic keys. It supports asymmetric keys (RSA, EC, Ed25519) and symmetric keys (secret objects). ## Table of Contents * [Theory](#theory) * [Class: KeyObject](#class-keyobject) * [Module Methods](#module-methods) * [Real-World Examples](#real-world-examples) ## Theory Cryptography relies on **Symmetric** and **Asymmetric** keys. * **Symmetric Keys**: A single secret string (e.g., 32 random bytes) used for both encryption and decryption (AES). * **Asymmetric Keys**: A pair of keys. * **Public Key**: Shared openly. Used to encrypt messages for the owner or verify the owner's signature. * **Private Key**: Kept secret. Used to decrypt messages sent to the owner or create digital signatures. Keys are stored in various container formats: * **PEM**: Textual format (`-----BEGIN PUBLIC KEY-----`). * **DER**: Binary format. * **PKCS#8**: The standard container for private keys. * **SPKI**: The standard container for public keys. *** ## Class: KeyObject The `KeyObject` class is a handle to a native C++ key. It allows the JavaScript layer to refer to keys without copying the key material (which might be sensitive) into the JS garbage-collected heap. ### Static Methods #### KeyObject.from(key) Creates a `KeyObject` from a WebCrypto `CryptoKey`. ```ts const cryptoKey = await subtle.generateKey( { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ); const keyObject = KeyObject.from(cryptoKey); console.log(keyObject.type); // 'secret' ``` ### Properties | Property | Type | Description | | :--------------------- | :---------------------------------- | :----------------------------------------------------------------------------------- | | `type` | `'secret' \| 'public' \| 'private'` | The type of the key. | | `asymmetricKeyType` | `string` | The algorithm name (e.g., `'rsa'`, `'ec'`, `'ed25519'`). `undefined` if symmetric. | | `asymmetricKeyDetails` | `Object` | Algorithm-specific key details (e.g., `modulusLength` for RSA, `namedCurve` for EC). | | `symmetricKeySize` | `number` | Key size in bytes (for secret keys only). | ### keyObject.equals(otherKeyObject) Returns `true` if the underlying key material is identical. ```ts const key1 = createSecretKey(randomBytes(32)); const key2 = createSecretKey(key1.export()); console.log(key1.equals(key2)); // true ``` ### keyObject.toCryptoKey(algorithm, extractable, keyUsages) Converts a `KeyObject` to a WebCrypto `CryptoKey`. ```ts const keyObject = createSecretKey(randomBytes(32)); const cryptoKey = keyObject.toCryptoKey( { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ); ``` ### keyObject.export(options) Exports the key to a `Buffer` or `string`. Symmetric key exports return a safe copy of the key material, so the returned buffer won't be affected by garbage collection. **Parameters:** **Returns:** `string | Buffer` **Example:** ```ts const { privateKey, publicKey } = generateKeyPairSync('rsa', { modulusLength: 2048, }); const pem = privateKey.export({ type: 'pkcs8', format: 'pem' }); ``` *** ## Module Methods ### generateKeyPair(type, options, callback) Generates a new asymmetric key pair on a background thread. **Parameters:** **Example (RSA 4096):** ```ts generateKeyPair('rsa', { modulusLength: 4096, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem', cipher: 'aes-256-cbc', passphrase: 'strong-pass' } }, (err, pubKey, privKey) => { // keys are PEM strings }); ``` ### createPublicKey(key) Converts a key representation into a `KeyObject`. It automatically detects if the input is PEM, DER, or another KeyObject. **Parameters:** **Returns:** `KeyObject` (type: 'public') ### createPrivateKey(key) Converts a key representation into a `KeyObject`. **Parameters:** **Example (Decrypting a Key):** ```ts const privKey = createPrivateKey({ key: encryptedPemString, passphrase: 'user-password' }); ``` ### createSecretKey(key) Creates a symmetric `KeyObject`. **Example:** ```ts const rawKey = randomBytes(32); const secret = createSecretKey(rawKey); ``` ### generateKey(type, options, callback) Asynchronously generates a new symmetric key of the given `type`. **Parameters:** **Example:** ```ts generateKey('aes', { length: 256 }, (err, key) => { // key is a KeyObject (type: 'secret') console.log(key.export().toString('hex')); }); ``` ### generateKeySync(type, options) Synchronously generates a new symmetric key. **Returns:** `KeyObject` *** ## Real-World Examples ### Rotating API Keys Generating a new Ed25519 signing pair for an API client. ```ts function rotateIdentity() { const { publicKey, privateKey } = generateKeyPairSync('ed25519'); return { pub: publicKey.export({ type: 'spki', format: 'pem' }), priv: privateKey.export({ type: 'pkcs8', format: 'pem' }) }; } ``` # KMAC (/docs/api/kmac) **KMAC** (Keccak Message Authentication Code) is a MAC function based on the Keccak (SHA-3) permutation. Unlike HMAC, KMAC is a purpose-built MAC that doesn't need a nested hash construction, making it simpler and more efficient. HMAC wraps a hash function in a nested construction. KMAC uses Keccak's sponge directly as a MAC, avoiding length-extension attacks by design. KMAC also supports customization strings for domain separation. ## Table of Contents * [Variants](#variants) * [WebCrypto API](#webcrypto-api) * [Real-World Examples](#real-world-examples) ## Variants | Algorithm | Security Level | Based On | Output Size | | :-------- | :------------- | :--------- | :-------------------------- | | `KMAC128` | 128-bit | Keccak-256 | Variable (default 256 bits) | | `KMAC256` | 256-bit | Keccak-512 | Variable (default 512 bits) | *** ## WebCrypto API KMAC is available through the SubtleCrypto interface for key generation, signing, and verification. ### Generate a KMAC Key ```ts const key = await subtle.generateKey( { name: 'KMAC256', length: 256 }, true, ['sign', 'verify'] ); ``` ### Sign (MAC) ```ts const data = new TextEncoder().encode('message to authenticate'); const mac = await subtle.sign( { name: 'KMAC256' }, key, data ); ``` ### Verify ```ts const isValid = await subtle.verify( { name: 'KMAC256' }, key, mac, data ); console.log(isValid); // true ``` ### Import/Export Keys ```ts // Export as JWK const jwk = await subtle.exportKey('jwk', key); // Export as raw bytes const raw = await subtle.exportKey('raw', key); // Import from raw const imported = await subtle.importKey( 'raw', raw, { name: 'KMAC256', length: 256 }, true, ['sign', 'verify'] ); ``` *** ## Real-World Examples ### API Request Authentication Use KMAC to authenticate API requests with a shared secret: ```ts async function signApiRequest( key: CryptoKey, method: string, path: string, body: string, timestamp: number ): Promise { const canonical = `${method}\n${path}\n${timestamp}\n${body}`; const data = new TextEncoder().encode(canonical); const mac = await subtle.sign({ name: 'KMAC256' }, key, data); return Buffer.from(mac).toString('base64'); } async function verifyApiRequest( key: CryptoKey, method: string, path: string, body: string, timestamp: number, signature: string ): Promise { const canonical = `${method}\n${path}\n${timestamp}\n${body}`; const data = new TextEncoder().encode(canonical); const sig = Buffer.from(signature, 'base64'); return subtle.verify({ name: 'KMAC256' }, key, sig, data); } ``` # PBKDF2 (/docs/api/pbkdf2) **PBKDF2** (Password-Based Key Derivation Function 2) is a standard algorithm for securely deriving keys from passwords. ## Table of Contents * [Theory](#theory) * [Module Methods](#module-methods) * [Real-World Examples](#real-world-examples) ## Theory PBKDF2 applies a Pseudorandom Function (typically **HMAC-SHA256**) to the password along with a salt value, and repeats this process many times (iterations). ```math DK = T_1 \parallel T_2 \parallel \dots \parallel T_{dklen/hlen} ``` ```math T_i = F(Password, Salt, c, i) ``` ```math F(P, S, c, i) = U_1 \oplus U_2 \oplus \dots \oplus U_c ``` **Iterations**: Repeated hashing forces an attacker to spend significantly more computing power to verify each password guess. **Salt**: A random value added to the password. It prevents the use of "Rainbow Tables" (pre-computed hash databases) and ensures that two users with the same password have different hashes. *** ## Module Methods ### pbkdf2(password, salt, iterations, keylen, digest, callback) Asynchronous key derivation. **Parameters:** **Returns:** `void` **Example:** ```ts const pass = 'password123'; const salt = randomBytes(16); pbkdf2(pass, salt, 600000, 64, 'sha512', (err, derivedKey) => { if (err) throw err; console.log(derivedKey.toString('hex')); }); ``` ### pbkdf2Sync(password, salt, iterations, keylen, digest) Synchronous version. The `digest` parameter is required (unlike some older Node.js versions that defaulted to `'sha1'`). `iterations` must be a positive integer (`>= 1`), and `keylen` must be a non-negative integer. Non-numeric, `NaN`, `Infinity`, or out-of-range values throw a `TypeError` rather than crashing. **Returns:** `Buffer` *** ## Real-World Examples ### User Registration Securely hashing a user's password before storing it in a database. ```ts function hashUserPassword(password: string): Promise<{ salt: string, hash: string }> { return new Promise((resolve, reject) => { const salt = randomBytes(16); const iterations = 600000; pbkdf2(password, salt, iterations, 64, 'sha512', (err, key) => { if (err) return reject(err); resolve({ salt: salt.toString('hex'), hash: key.toString('hex') }); }); }); } ``` # Post-Quantum Cryptography (/docs/api/pqc) **Post-Quantum Cryptography (PQC)** provides cryptographic algorithms that are secure against both classical and quantum computers. RNQC implements the NIST standardized lattice-based algorithms via OpenSSL 3.6+. Quantum computers threaten RSA, ECDSA, and ECDH. The PQC algorithms below are NIST-standardized replacements designed to resist quantum attacks while running efficiently on classical hardware. ## Table of Contents * [Algorithms](#algorithms) * [ML-DSA (Digital Signatures)](#ml-dsa-digital-signatures) * [ML-KEM (Key Encapsulation)](#ml-kem-key-encapsulation) * [WebCrypto API](#webcrypto-api) * [Real-World Examples](#real-world-examples) ## Algorithms ### ML-DSA (FIPS 204) Module Lattice Digital Signature Algorithm. Replacement for RSA and ECDSA signatures. | Parameter Set | Security Level | Public Key | Signature | Use Case | | :------------ | :------------- | :--------- | :-------- | :--------------- | | `ML-DSA-44` | NIST Level 2 | 1,312 B | 2,420 B | General purpose | | `ML-DSA-65` | NIST Level 3 | 1,952 B | 3,309 B | Recommended | | `ML-DSA-87` | NIST Level 5 | 2,592 B | 4,627 B | Maximum security | ### ML-KEM (FIPS 203) Module Lattice Key Encapsulation Mechanism. Replacement for ECDH key exchange. | Parameter Set | Security Level | Public Key | Ciphertext | Shared Secret | | :------------ | :------------- | :--------- | :--------- | :------------ | | `ML-KEM-512` | NIST Level 1 | 800 B | 768 B | 32 B | | `ML-KEM-768` | NIST Level 3 | 1,184 B | 1,088 B | 32 B | | `ML-KEM-1024` | NIST Level 5 | 1,568 B | 1,568 B | 32 B | *** ## ML-DSA (Digital Signatures) ### Node.js API Generate ML-DSA key pairs and sign/verify using the standard `crypto` API: ```ts import { generateKeyPairSync, sign, verify } from 'react-native-quick-crypto'; // Generate ML-DSA-65 key pair const { publicKey, privateKey } = generateKeyPairSync('ml-dsa-65'); // Sign const message = Buffer.from('quantum-safe message'); const signature = sign(null, message, privateKey); // Verify const isValid = verify(null, message, publicKey, signature); console.log('Valid:', isValid); // true ``` ### Key Export/Import ML-DSA keys support multiple export formats: ```ts // Export as PEM const pubPem = publicKey.export({ type: 'spki', format: 'pem' }); const privPem = privateKey.export({ type: 'pkcs8', format: 'pem' }); // Export as DER const pubDer = publicKey.export({ type: 'spki', format: 'der' }); // Re-import const imported = createPublicKey({ key: pubDer, format: 'der', type: 'spki' }); ``` *** ## ML-KEM (Key Encapsulation) ML-KEM uses **encapsulation** rather than key exchange. One party encapsulates a shared secret using the other's public key, producing a ciphertext. The other party decapsulates the ciphertext with their private key to recover the same shared secret. ### Node.js API ```ts import { generateKeyPairSync, encapsulate, decapsulate } from 'react-native-quick-crypto'; // Generate ML-KEM-768 key pair const { publicKey, privateKey } = generateKeyPairSync('ml-kem-768'); // Encapsulate: produces shared secret + ciphertext const { sharedSecret, ciphertext } = encapsulate(publicKey); // Decapsulate: recovers the same shared secret const recovered = decapsulate(privateKey, ciphertext); console.log(sharedSecret.equals(recovered)); // true ``` *** ## WebCrypto API PQC algorithms are fully supported through the SubtleCrypto interface. ### ML-DSA via SubtleCrypto ```ts // Generate key pair const keyPair = await subtle.generateKey( { name: 'ML-DSA-65' }, true, ['sign', 'verify'] ); // Sign const data = new TextEncoder().encode('quantum-safe data'); const signature = await subtle.sign( { name: 'ML-DSA-65' }, keyPair.privateKey, data ); // Verify const isValid = await subtle.verify( { name: 'ML-DSA-65' }, keyPair.publicKey, signature, data ); ``` ### ML-KEM via SubtleCrypto ```ts // Generate encapsulation key pair const keyPair = await subtle.generateKey( { name: 'ML-KEM-768' }, true, ['deriveBits', 'deriveKey'] ); // Encapsulate: get shared secret bits + ciphertext const { sharedSecret, ciphertext } = await subtle.encapsulateBits( { name: 'ML-KEM-768' }, keyPair.publicKey ); // Decapsulate: recover shared secret const recovered = await subtle.decapsulateBits( { name: 'ML-KEM-768' }, keyPair.privateKey, ciphertext ); // Or derive a key directly from encapsulation const { key: aesKey, ciphertext: ct } = await subtle.encapsulateKey( { name: 'ML-KEM-768' }, keyPair.publicKey, { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ); // Decapsulate to get the same AES key const recoveredKey = await subtle.decapsulateKey( { name: 'ML-KEM-768' }, keyPair.privateKey, ct, { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ); ``` ### Key Export Formats | Algorithm | `spki` | `pkcs8` | `jwk` | `raw-public` | `raw-seed` | | :------------------ | :----: | :-----: | :---: | :----------: | :--------: | | ML-DSA-44/65/87 | ✅ | ✅ | ✅ | ✅ | ✅ | | ML-KEM-512/768/1024 | ✅ | ✅ | | ✅ | ✅ | ```ts // Export ML-DSA public key as JWK const jwk = await subtle.exportKey('jwk', keyPair.publicKey); // Export raw public key bytes const rawPub = await subtle.exportKey('raw-public', keyPair.publicKey); // Export seed (deterministic private key material) const seed = await subtle.exportKey('raw-seed', keyPair.privateKey); // Import from raw-public const imported = await subtle.importKey( 'raw-public', rawPub, { name: 'ML-DSA-65' }, true, ['verify'] ); ``` *** ## Real-World Examples ### Hybrid Signature (Classical + PQC) Combine Ed25519 with ML-DSA for defense-in-depth during the quantum transition: ```ts import { generateKeyPairSync, sign, verify } from 'react-native-quick-crypto'; function hybridSign(message: Buffer) { const ed = generateKeyPairSync('ed25519'); const pqc = generateKeyPairSync('ml-dsa-65'); const edSig = sign(null, message, ed.privateKey); const pqcSig = sign(null, message, pqc.privateKey); return { message, signatures: { ed25519: edSig, mlDsa65: pqcSig }, publicKeys: { ed25519: ed.publicKey, mlDsa65: pqc.publicKey } }; } function hybridVerify(signed: ReturnType): boolean { const edValid = verify( null, signed.message, signed.publicKeys.ed25519, signed.signatures.ed25519 ); const pqcValid = verify( null, signed.message, signed.publicKeys.mlDsa65, signed.signatures.mlDsa65 ); // Both must pass return edValid && pqcValid; } ``` ### Quantum-Safe Key Exchange Use ML-KEM to establish a shared secret for symmetric encryption: ```ts import { generateKeyPairSync, encapsulate, decapsulate, createCipheriv, createDecipheriv, createHash, randomBytes } from 'react-native-quick-crypto'; // Server publishes its ML-KEM public key const server = generateKeyPairSync('ml-kem-768'); // Client encapsulates a shared secret const { sharedSecret, ciphertext } = encapsulate(server.publicKey); // Derive AES key from shared secret const aesKey = createHash('sha256').update(sharedSecret).digest(); const iv = randomBytes(12); // Encrypt with AES-GCM const cipher = createCipheriv('aes-256-gcm', aesKey, iv); let encrypted = cipher.update('secret message', 'utf8', 'base64'); encrypted += cipher.final('base64'); const tag = cipher.getAuthTag(); // Server decapsulates to get the same shared secret const serverSecret = decapsulate(server.privateKey, ciphertext); const serverKey = createHash('sha256').update(serverSecret).digest(); // Server decrypts const decipher = createDecipheriv('aes-256-gcm', serverKey, iv); decipher.setAuthTag(tag); let decrypted = decipher.update(encrypted, 'base64', 'utf8'); decrypted += decipher.final('utf8'); console.log(decrypted); // "secret message" ``` # Public Cipher (RSA Encryption) (/docs/api/public-cipher) The public cipher module provides asymmetric encryption/decryption using RSA. In asymmetric cryptography, data encrypted with a **public key** can only be decrypted with the corresponding **private key**, enabling secure communication without pre-shared secrets. **Secure messaging** (encrypt messages for specific recipients), **key exchange** (encrypt symmetric keys for bulk data encryption), **password-less authentication**, and **secure file sharing**. ## Table of Contents * [Theory](#theory) * [Module Methods](#module-methods) * [Hybrid Encryption Pattern](#hybrid-encryption-pattern) * [Padding Modes](#padding-modes) * [Real-World Examples](#real-world-examples) * [Security Considerations](#security-considerations) ## Theory RSA (Rivest–Shamir–Adleman) is a public-key cryptosystem that is widely used for secure data transmission. It relies on the practical difficulty of factoring the product of two large prime numbers, the "factoring problem". ### Key Concepts * **Public Key**: Shared openly. Used for **encrypting** data intended for the key owner, or **verifying** signatures from the key owner. * **Private Key**: Kept secret. Used for **decrypting** data sent to the key owner, or **signing** data to prove authenticity. * **Padding**: Crucial for security. Raw RSA is deterministic and unsafe; padding schemes like OAEP introduce randomness. *** ## Module Methods ### publicEncrypt(key, buffer) Encrypts `buffer` with a public key. Anyone with the public key can encrypt, but only the private key holder can decrypt. **Parameters:** | Name | Type | Description | | :------- | :------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------- | | `key` | `KeyObject \| string \| Buffer \| Object` | Public key for encryption. Can be a `KeyObject`, PEM-encoded string, DER buffer, or an object with encryption options | | `buffer` | `string \| Buffer \| TypedArray \| DataView` | Data to encrypt. **Maximum size** depends on key size and padding (\~190 bytes for RSA-2048 with OAEP) | When `key` is an object, it may contain: **Returns:** `Buffer` - The encrypted data **Examples:** Basic encryption: ```ts const { publicKey, privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048, }); // Encrypt with public key const message = 'Secret message'; const encrypted = publicEncrypt(publicKey, Buffer.from(message)); console.log('Encrypted:', encrypted.toString('base64')); ``` Encryption with OAEP padding (recommended): ```ts const encrypted = publicEncrypt({ key: publicKeyPEM, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256' // More secure than default sha1 }, Buffer.from('secret data')); ``` Encryption with OAEP label: ```ts // Label provides additional context/domain separation const encrypted = publicEncrypt({ key: publicKey, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256', oaepLabel: Buffer.from('user-messages-v1') }, Buffer.from('message')); ``` *** ### privateDecrypt(key, buffer) Decrypts `buffer` with a private key. Only the private key holder can decrypt data encrypted with the corresponding public key. **Parameters:** | Name | Type | Description | | :------- | :---------------------------------------- | :--------------------------------------------------------------------------------------------------------------------- | | `key` | `KeyObject \| string \| Buffer \| Object` | Private key for decryption. Can be a `KeyObject`, PEM-encoded string, DER buffer, or an object with decryption options | | `buffer` | `Buffer \| TypedArray \| DataView` | Encrypted data to decrypt | When `key` is an object, it may contain: **Returns:** `Buffer` - The decrypted plaintext **Examples:** Basic decryption: ```ts const decrypted = privateDecrypt(privateKey, encryptedBuffer); console.log('Decrypted:', decrypted.toString()); ``` Decryption with encrypted private key: ```ts const decrypted = privateDecrypt({ key: encryptedPrivateKeyPEM, passphrase: 'my-secret-password' }, encryptedData); ``` Decryption with OAEP parameters: ```ts const decrypted = privateDecrypt({ key: privateKey, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256', oaepLabel: Buffer.from('user-messages-v1') }, encryptedData); ``` *** ### privateEncrypt(key, buffer) Encrypts `buffer` with a private key. This is used for **signature-like operations** where you want to prove possession of the private key. Not recommended for actual signing - use `createSign()` instead. **Parameters:** | Name | Type | Description | | :------- | :------------------------------------------- | :------------------------------- | | `key` | `KeyObject \| string \| Buffer \| Object` | Private key for encryption | | `buffer` | `string \| Buffer \| TypedArray \| DataView` | Data to encrypt with private key | **Returns:** `Buffer` - The encrypted data **Note:** For digital signatures, prefer `createSign()` over `privateEncrypt()`. *** ### publicDecrypt(key, buffer) Decrypts `buffer` that was encrypted with `privateEncrypt()`. Anyone with the public key can decrypt, proving the data came from the private key holder. **Parameters:** | Name | Type | Description | | :------- | :---------------------------------------- | :------------------------------------- | | `key` | `KeyObject \| string \| Buffer \| Object` | Public key for decryption | | `buffer` | `Buffer \| TypedArray \| DataView` | Data encrypted with `privateEncrypt()` | **Returns:** `Buffer` - The decrypted data *** ## Hybrid Encryption Pattern RSA can only encrypt small amounts of data (\< key size). For larger data, use **hybrid encryption**: encrypt the data with AES, then encrypt the AES key with RSA. ### Pattern: Encrypt Large Data ```ts import { publicEncrypt, randomBytes, createCipheriv, constants } from 'react-native-quick-crypto'; function hybridEncrypt( publicKey: any, data: Buffer ): { encryptedKey: Buffer; iv: Buffer; encryptedData: Buffer } { // Generate AES key and IV const aesKey = randomBytes(32); const iv = randomBytes(16); // Encrypt data with AES const cipher = createCipheriv('aes-256-cbc', aesKey, iv); const encryptedData = Buffer.concat([ cipher.update(data), cipher.final() ]); // Encrypt AES key with RSA const encryptedKey = publicEncrypt({ key: publicKey, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256' }, aesKey); return { encryptedKey, iv, encryptedData }; } // Usage const largeFile = Buffer.alloc(10 * 1024 * 1024); // 10 MB const encrypted = hybridEncrypt(publicKey, largeFile); // Transmit: encrypted.encryptedKey, encrypted.iv, encrypted.encryptedData ``` ### Pattern: Decrypt Large Data ```ts import { privateDecrypt, createDecipheriv, constants } from 'react-native-quick-crypto'; function hybridDecrypt( privateKey: any, encryptedKey: Buffer, iv: Buffer, encryptedData: Buffer ): Buffer { // Decrypt AES key with RSA const aesKey = privateDecrypt({ key: privateKey, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256' }, encryptedKey); // Decrypt data with AES const decipher = createDecipheriv('aes-256-cbc', aesKey, iv); const decrypted = Buffer.concat([ decipher.update(encryptedData), decipher.final() ]); return decrypted; } // Usage const decrypted = hybridDecrypt( privateKey, encrypted.encryptedKey, encrypted.iv, encrypted.encryptedData ); ``` *** ## Padding Modes RSA requires padding for security. The padding mode **must match** between encryption and decryption. ### Available Padding Modes | Padding | Constant | Security | Use Case | | :-------------- | :----------------------- | :-------------- | :------------------------------------ | | **OAEP** | `RSA_PKCS1_OAEP_PADDING` | **Recommended** | Modern applications, maximum security | | **PKCS#1 v1.5** | `RSA_PKCS1_PADDING` | Legacy | Compatibility with older systems | | **None** | `RSA_NO_PADDING` | ⚠️ **Insecure** | Never use in production | ### OAEP Padding (Recommended) Optimal Asymmetric Encryption Padding provides the best security: ```ts // Encrypt with OAEP const encrypted = publicEncrypt({ key: publicKey, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256' // Recommended over default 'sha1' }, data); // Decrypt with OAEP (must use same hash!) const decrypted = privateDecrypt({ key: privateKey, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256' }, encrypted); ``` ### PKCS#1 v1.5 Padding (Legacy) Only for compatibility with older systems: ```ts const encrypted = publicEncrypt({ key: publicKey, padding: constants.RSA_PKCS1_PADDING }, data); const decrypted = privateDecrypt({ key: privateKey, padding: constants.RSA_PKCS1_PADDING }, encrypted); ``` *** ## Real-World Examples ### Example 1: Secure Messaging End-to-end encrypted messages between users: ```ts import { publicEncrypt, privateDecrypt, randomBytes, createCipheriv, createDecipheriv, constants } from 'react-native-quick-crypto'; class SecureMessenger { constructor( private myPrivateKey: any, private myPublicKey: any ) {} // Encrypt message for recipient encryptFor( recipientPublicKey: any, message: string ): { encryptedKey: string; iv: string; ciphertext: string } { // Generate ephemeral AES key const aesKey = randomBytes(32); const iv = randomBytes(16); // Encrypt message with AES const cipher = createCipheriv('aes-256-gcm', aesKey, iv); let ciphertext = cipher.update(message, 'utf8', 'base64'); ciphertext += cipher.final('base64'); const authTag = cipher.getAuthTag(); // Encrypt AES key with recipient's public key const encryptedKey = publicEncrypt({ key: recipientPublicKey, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256' }, aesKey); return { encryptedKey: encryptedKey.toString('base64'), iv: iv.toString('base64') + ':' + authTag.toString('base64'), ciphertext }; } // Decrypt message from sender decryptFrom( encrypted: { encryptedKey: string; iv: string; ciphertext: string } ): string { // Decrypt AES key with my private key const aesKey = privateDecrypt({ key: this.myPrivateKey, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256' }, Buffer.from(encrypted.encryptedKey, 'base64')); // Parse IV and auth tag const [ivB64, tagB64] = encrypted.iv.split(':'); const iv = Buffer.from(ivB64, 'base64'); const authTag = Buffer.from(tagB64, 'base64'); // Decrypt message with AES const decipher = createDecipheriv('aes-256-gcm', aesKey, iv); decipher.setAuthTag(authTag); let message = decipher.update(encrypted.ciphertext, 'base64', 'utf8'); message += decipher.final('utf8'); return message; } } // Usage const alice = new SecureMessenger(alicePrivateKey, alicePublicKey); const bob = new SecureMessenger(bobPrivateKey, bobPublicKey); // Alice sends encrypted message to Bob const encrypted = alice.encryptFor(bobPublicKey, 'Meet at 3pm'); // Bob decrypts const message = bob.decryptFrom(encrypted); console.log(message); // "Meet at 3pm" ``` ### Example 2: Secure File Sharing Share encrypted files with specific users: ```ts import { publicEncrypt, privateDecrypt, randomBytes, createCipheriv, createDecipheriv, constants } from 'react-native-quick-crypto'; interface EncryptedFile { encryptedKeys: Map; // User ID -> encrypted AES key iv: string; encryptedPath: string; } async function encryptFileForMultipleUsers( filePath: string, recipientPublicKeys: Map // User ID -> Public Key ): Promise { // Read file const fileData = await RNFS.readFile(filePath, 'base64'); const fileBuffer = Buffer.from(fileData, 'base64'); // Generate single AES key for file const aesKey = randomBytes(32); const iv = randomBytes(16); // Encrypt file with AES const cipher = createCipheriv('aes-256-cbc', aesKey, iv); const encryptedData = Buffer.concat([ cipher.update(fileBuffer), cipher.final() ]); // Save encrypted file const encryptedPath = filePath + '.encrypted'; await RNFS.writeFile( encryptedPath, encryptedData.toString('base64'), 'base64' ); // Encrypt AES key for each recipient const encryptedKeys = new Map(); for (const [userId, publicKey] of recipientPublicKeys.entries()) { const encryptedKey = publicEncrypt({ key: publicKey, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256' }, aesKey); encryptedKeys.set(userId, encryptedKey.toString('base64')); } return { encryptedKeys, iv: iv.toString('base64'), encryptedPath }; } async function decryptFile( userId: string, privateKey: any, encryptedFile: EncryptedFile, outputPath: string ): Promise { // Get my encrypted key const encryptedKeyB64 = encryptedFile.encryptedKeys.get(userId); if (!encryptedKeyB64) { throw new Error('You do not have access to this file'); } // Decrypt AES key const aesKey = privateDecrypt({ key: privateKey, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256' }, Buffer.from(encryptedKeyB64, 'base64')); // Read encrypted file const encryptedData = await RNFS.readFile( encryptedFile.encryptedPath, 'base64' ); // Decrypt file const decipher = createDecipheriv( 'aes-256-cbc', aesKey, Buffer.from(encryptedFile.iv, 'base64') ); const decrypted = Buffer.concat([ decipher.update(Buffer.from(encryptedData, 'base64')), decipher.final() ]); // Save decrypted file await RNFS.writeFile(outputPath, decrypted.toString('base64'), 'base64'); } // Usage const recipients = new Map([ ['alice', alicePublicKey], ['bob', bobPublicKey], ['charlie', charliePublicKey] ]); const encrypted = await encryptFileForMultipleUsers( '/path/to/document.pdf', recipients ); // Bob decrypts await decryptFile('bob', bobPrivateKey, encrypted, '/path/to/decrypted.pdf'); ``` ### Example 3: Password-less Authentication Authenticate without sending passwords: ```ts import { publicEncrypt, privateDecrypt, randomBytes, constants } from 'react-native-quick-crypto'; class AuthChallenge { // Server generates challenge static createChallenge(userPublicKey: any): { challenge: string; encrypted: string; } { // Generate random challenge const challenge = randomBytes(32).toString('hex'); // Encrypt with user's public key const encrypted = publicEncrypt({ key: userPublicKey, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256' }, Buffer.from(challenge)); return { challenge, // Store server-side encrypted: encrypted.toString('base64') // Send to client }; } // Client decrypts and signs response static respondToChallenge( encryptedChallenge: string, privateKey: any ): string { // Decrypt challenge const challenge = privateDecrypt({ key: privateKey, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256' }, Buffer.from(encryptedChallenge, 'base64')); // Return decrypted challenge (proves key ownership) return challenge.toString('hex'); } // Server verifies response static verifyResponse( originalChallenge: string, response: string ): boolean { return originalChallenge === response; } } // Authentication flow const { challenge: serverChallenge, encrypted } = AuthChallenge.createChallenge(userPublicKey); const response = AuthChallenge.respondToChallenge(encrypted, userPrivateKey); const authenticated = AuthChallenge.verifyResponse(serverChallenge, response); console.log('Authenticated:', authenticated); ``` *** ## Security Considerations 1. **Minimum 2048-bit keys** - Smaller keys are vulnerable to factorization 2. **Use OAEP padding** - PKCS#1 v1.5 has known vulnerabilities 3. **Never encrypt large data directly** - Use hybrid encryption 4. **Protect private keys** - Store in device Keychain/KeyStore 5. **Use sha256 or better** - Default sha1 in OAEP is weak ### Best Practices **1. Key Size:** ```ts // ✅ Good - 2048-bit minimum generateKeyPairSync('rsa', { modulusLength: 2048 }); // ✅ Better - 3072-bit for high security generateKeyPairSync('rsa', { modulusLength: 3072 }); // ❌ Bad - 1024-bit is breakable generateKeyPairSync('rsa', { modulusLength: 1024 }); ``` **2. Padding Selection:** ```ts // ✅ Good - OAEP with SHA-256 publicEncrypt({ key: publicKey, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256' }, data); // ⚠️ Acceptable - OAEP with default SHA-1 (upgrade to SHA-256) publicEncrypt({ key: publicKey, padding: constants.RSA_PKCS1_OAEP_PADDING }, data); // ❌ Bad - PKCS#1 v1.5 (vulnerable to attacks) publicEncrypt({ key: publicKey, padding: constants.RSA_PKCS1_PADDING }, data); // ❌ Never - No padding (completely insecure) publicEncrypt({ key: publicKey, padding: constants.RSA_NO_PADDING }, data); ``` **3. Data Size Limits:** ```ts // ✅ Good - Use hybrid encryption for large data // const { encryptedKey, iv, encryptedData } = hybridEncrypt(publicKey, largeFile); // ❌ Bad: Direct encryption of large data will fail // publicEncrypt(publicKey, largeFile); // Error: data too large ``` **4. Key Storage:** ```ts // ✅ Good - Secure storage await Keychain.setGenericPassword( 'privateKey', privateKeyPEM, { service: 'com.myapp.keys', accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED } ); // ❌ Bad - Plain storage await AsyncStorage.setItem('privateKey', privateKeyPEM); ``` *** ## Common Errors ### Error: `data too large for key size` **Cause:** You're trying to encrypt more data than RSA can handle. **Maximum data sizes:** * RSA-2048 with OAEP: \~190 bytes * RSA-2048 with PKCS#1: \~245 bytes * RSA-4096 with OAEP: \~446 bytes **Solution:** Use hybrid encryption: ```ts // ❌ Wrong - Direct encryption of large data const encrypted = publicEncrypt(publicKey, largeData); // Error! // ✅ Correct - Hybrid encryption const { encryptedKey, iv, encryptedData } = hybridEncrypt(publicKey, largeData); ``` *** ### Error: `error:04800074:PEM routines::bad password read` **Cause:** Private key is encrypted but passphrase wasn't provided. **Solution:** ```ts // ❌ Wrong: Encrypted key without passphrase // privateDecrypt(encryptedPrivateKey, data); // Error! // ✅ Correct privateDecrypt({ key: encryptedPrivateKey, passphrase: 'my-password' }, data); ``` *** ### Decryption produces garbage **Possible causes:** 1. **Padding mismatch:** ```ts // ❌ Wrong: Different padding modes // publicEncrypt({ padding: RSA_PKCS1_OAEP_PADDING }, data); // privateDecrypt({ padding: RSA_PKCS1_PADDING }, encrypted); // Different! // ✅ Correct: Same padding on both sides const { publicKey, privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 }); const data = Buffer.from('test'); const padding = constants.RSA_PKCS1_OAEP_PADDING; const encrypted = publicEncrypt({ key: publicKey, padding }, data); const decrypted = privateDecrypt({ key: privateKey, padding }, encrypted); ``` 2. **OAEP hash mismatch:** ```ts // ❌ Wrong: Different OAEP hash // publicEncrypt({ oaepHash: 'sha256' }, data); // privateDecrypt({ oaepHash: 'sha1' }, encrypted); // Different hash! // ✅ Correct: Same hash on both sides const { publicKey, privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 }); const data = Buffer.from('test'); const oaepHash = 'sha256'; const encrypted = publicEncrypt({ key: publicKey, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash }, data); const decrypted = privateDecrypt({ key: privateKey, padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash }, encrypted); ``` 3. **Wrong key pair:** ```ts // ❌ Wrong: Different key pairs // publicEncrypt(publicKeyA, data); // privateDecrypt(privateKeyB, encrypted); // Unrelated keys! // ✅ Correct: Matching key pair const { publicKey, privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 }); const encrypted = publicEncrypt(publicKey, data); const decrypted = privateDecrypt(privateKey, encrypted); ``` *** ## Performance Notes **Encryption/Decryption performance** (RSA-2048, typical mobile device): * Encryption (public key): \~2ms * Decryption (private key): \~20ms **Key observations:** * **Private key operations are 10× slower** than public key operations * **Consider ECDH/X25519 for key exchange** - much faster than RSA * **Batch operations when possible** to amortize setup costs * **Use hybrid encryption** for large data (AES is 1000× faster than RSA for bulk data) ```ts // Performance comparison const start = performance.now(); const encrypted = publicEncrypt(publicKey, data); console.log('Encryption:', performance.now() - start, 'ms'); // ~2ms const start2 = performance.now(); const decrypted = privateDecrypt(privateKey, encrypted); console.log('Decryption:', performance.now() - start2, 'ms'); // ~20ms ``` # Random (/docs/api/random) The `Random` module provides functionality to generate cryptographically strong pseudo-random data. ## Table of Contents * [Theory](#theory) * [Module Methods](#module-methods) * [Real-World Examples](#real-world-examples) * [Security Considerations](#security-considerations) ## Theory Standard random number generators (like `Math.random()`) are **Pseudo-Random Number Generators (PRNGs)**. They use a mathematical formula starting from a "seed". If you know the seed or enough outputs, you can predict all future outputs. Cryptographically secure systems require **CSPRNGs (Cryptographically Strong PRNGs)**. These are designed to be unpredictable even if an attacker knows the algorithm. RNQC delegates randomness to the underlying Operating System's entropy pool: * **iOS/macOS**: `SecRandomCopyBytes` * **Android**: `SecureRandom` This ensures that generated keys, salts, and nonces are secure. *** ## Module Methods ### randomBytes(size\[, callback]) Generates cryptographically strong pseudo-random data. **Parameters:** **Returns:** `Buffer` (if sync) or `void` (if async). **Example:** ```ts twoslash // @noErrors // Sync (Blocking) const buf = randomBytes(16); // ^? // Async (Non-Blocking) randomBytes(256, (err, buf) => { if (err) throw err; console.log(`${buf.length} bytes generated.`); }); ``` *** ### randomFill(buffer\[, offset]\[, size], callback) ### randomFillSync(buffer\[, offset]\[, size]) Populates an *existing* buffer with random data. Works correctly with TypedArray views over larger ArrayBuffers — `offset` and `size` are relative to the view, not the underlying buffer. **Parameters:** *** ### randomInt(\[min], max\[, callback]) Returns a random integer `n` such that `min <= n < max`. The implementation avoids **modulo bias**. **Parameters:** **Example:** ```ts twoslash // @noErrors // Range: [0, 100) const n = randomInt(100); // ^? // Range: [10, 50) const m = randomInt(10, 50); ``` *** ### randomUUID(\[options]) Generates a random RFC 4122 Version 4 UUID. **Parameters:** **Returns:** `string` e.g. `'f47ac10b-58cc-4372-a567-0e02b2c3d479'` *** ## Real-World Examples ### API Key Generation Generating a URL-safe random string. ```ts function generateApiKey(lengthBytes = 32): string { const buffer = randomBytes(lengthBytes); return buffer.toString('base64') .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=/g, ''); } ``` ### Nonce Generation For encryption algorithms like AES-GCM or ChaCha20-Poly1305, you need a nonce. ```ts const NONCE_SIZE = 12; function generateNonce(): Buffer { return randomBytes(NONCE_SIZE); } ``` ### Secure Shuffle Shuffling an array using the Fisher-Yates algorithm with CSPRNG. ```ts async function secureShuffle(array: T[]): Promise { const arr = [...array]; for (let i = arr.length - 1; i > 0; i--) { const j = await new Promise((resolve, reject) => { randomInt(0, i + 1, (err, n) => err ? reject(err) : resolve(n)); }); [arr[i], arr[j]] = [arr[j], arr[i]]; } return arr; } ``` *** ## Security Considerations ### Blocking the Event Loop `randomBytes` (synchronous) taps into system sources. While generally fast, requesting large amounts of entropy on a constrained device could potentially block the Main/UI thread. For generating 4KB or less (keys, nonces), sync is fine. For larger buffers, use the asynchronous version or `randomUUID`. # Scrypt (/docs/api/scrypt) **Scrypt** is a password-based key derivation function (KDF) that is designed to be **memory-hard**. It prevents custom hardware attacks (like ASICs) by requiring large amounts of memory to solve. ## Table of Contents * [Theory](#theory) * [Module Methods](#module-methods) * [Real-World Examples](#real-world-examples) ## Theory Standard hashes are computationally cheap. Specialized hardware (ASICs, GPUs) can calculate billions of SHA-256 hashes per second, making them effective at brute-forcing passwords. Scrypt resists this by requiring large amounts of **Memory (RAM)** to compute. * It generates a large random dataset in memory. * It reads excessively from random locations in that dataset. * This defeats hardware acceleration because memory is expensive and difficult to parallelize on a massive scale. **Parameters:** 1. **N (Cost)**: CPU/Memory cost. 2. **r (Block Size)**: Memory block size. 3. **p (Parallelization)**: Independent threads. ```math \text{Memory} \approx 128 \times r \times N \text{ bytes} ``` ```math \text{CPU Cost} \approx 4 \times N \times r \times p ``` *** ## Module Methods ### scrypt(password, salt, keylen\[, options], callback) Asynchronously derives a key. **Parameters:** **Returns:** `void` **Example:** ```ts const pass = 'correct horse battery staple'; const salt = randomBytes(16); const opts = { N: 32768, r: 8, p: 1, maxmem: 64 * 1024 * 1024 }; scrypt(pass, salt, 64, opts, (err, key) => { if (err) throw err; console.log('Derived Key:', key.toString('hex')); }); ``` ### scryptSync(password, salt, keylen\[, options]) Synchronous version. **Warning**: This will block the entire JS thread. Use with caution in React Native. **Returns:** `Buffer` *** ## Real-World Examples ### Wallet Encryption Encouraging high-security parameters (\~32MB RAM) for encrypting crypto wallets. ```ts function encryptWallet(privateKey: Buffer, password: string): Promise { return new Promise((resolve, reject) => { const salt = randomBytes(32); const iv = randomBytes(16); // Derive encryption key scrypt(password, salt, 32, { N: 32768, r: 8, p: 1 }, (err, derivedKey) => { if (err) return reject(err); const cipherKey = derivedKey.slice(0, 32); const cipher = createCipheriv('aes-256-ctr', cipherKey, iv); let ciphertext = cipher.update(privateKey); ciphertext = Buffer.concat([ciphertext, cipher.final()]); resolve({ kdf: 'scrypt', ciphertext: ciphertext.toString('hex') }); }); }); } ``` # Signing & Verification (/docs/api/signing) The signing module provides implementations for creating and verifying digital signatures using various cryptographic algorithms. Digital signatures provide **authentication** (proof of origin) and **integrity** (proof data hasn't changed). **JWT authentication tokens**, **API request signing** (AWS Signature V4, OAuth), **code signing** for mobile apps, **document verification** in legal/financial systems, and **blockchain transactions**. ## Table of Contents * [Theory](#theory) * [Class: Sign](#class-sign) * [Class: Verify](#class-verify) * [Module Methods](#module-methods) * [Real-World Examples](#real-world-examples) * [Supported Algorithms](#supported-algorithms) ## Theory Digital signatures provide three core security guarantees: 1. **Authentication**: Confirms the identity of the signer (only the holder of the private key could have created it). 2. **Integrity**: Guarantees the data has not been altered since it was signed. 3. **Non-repudiation**: The signer cannot deny having signed the data. They work by hashing the data and then encrypting that hash with the signer's private key. The recipient validates it by decrypting the hash with the signer's public key and comparing it to their own hash of the data. *** ## Class: Sign The `Sign` class creates digital signatures. Instances are created using the `createSign()` factory function. ```ts // Generate RSA key pair const { publicKey, privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048, }); // Create signer const sign = createSign('SHA256'); sign.update('some data to sign'); sign.end(); const signature = sign.sign(privateKey); ``` ### sign.update(data\[, inputEncoding]) Updates the `Sign` content with the given `data`. This method can be called multiple times with new data as it is streamed. **Parameters:** | Name | Type | Description | | :-------------- | :------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------- | | `data` | `string \| Buffer \| TypedArray \| DataView` | The data to sign | | `inputEncoding` | `string` | The encoding of the data string (if `data` is a string). One of `'utf8'`, `'ascii'`, or `'latin1'`. Default: `'utf8'` | **Returns:** `this` (for method chaining) **Examples:** ```ts const sign = createSign('SHA256'); // Single update sign.update('Hello World'); // Multiple updates (useful for streaming) sign.update('Hello'); sign.update(' '); sign.update('World'); // With encoding sign.update('48656c6c6f', 'hex'); // "Hello" in hex ``` **Streaming large files:** ```ts async function signLargeFile(filePath: string, privateKey: any) { const sign = createSign('SHA256'); // Read file in chunks const chunkSize = 1024 * 1024; // 1MB chunks const fileSize = (await RNFS.stat(filePath)).size; for (let offset = 0; offset < fileSize; offset += chunkSize) { const chunk = await RNFS.read(filePath, chunkSize, offset, 'base64'); sign.update(chunk, 'base64'); } return sign.sign(privateKey, 'hex'); } ``` *** ### sign.sign(privateKey\[, outputEncoding]) Calculates the signature on all the data passed through using either `sign.update()` or by the stream interface. **Parameters:** | Name | Type | Description | | :--------------- | :---------------------------------------- | :------------------------------------------------------------------------------------------------------------------ | | `privateKey` | `KeyObject \| string \| Buffer \| Object` | Private key for signing. Can be a `KeyObject`, PEM-encoded string, DER buffer, or an object with additional options | | `outputEncoding` | `string` | Encoding for the return value. One of `'hex'`, `'base64'`, or `'base64url'`. If not provided, returns `Buffer` | When `privateKey` is an object, it may contain: **Returns:** `Buffer` (if no encoding) or `string` **Important:** The `Sign` object cannot be used again after `sign()` is called. Create a new instance if you need to sign more data. **Examples:** Basic signing: ```ts const sign = createSign('SHA256'); sign.update('data to sign'); // As Buffer const signatureBuffer = sign.sign(privateKey); // As hex string const signatureHex = sign.sign(privateKey, 'hex'); // As base64 const signatureB64 = sign.sign(privateKey, 'base64'); ``` Signing with encrypted private key: ```ts const encryptedPrivateKeyPEM = `-----BEGIN ENCRYPTED PRIVATE KEY----- ... -----END ENCRYPTED PRIVATE KEY-----`; const sign = createSign('SHA256'); sign.update('secure data'); const signature = sign.sign({ key: encryptedPrivateKeyPEM, passphrase: 'my-secret-password' }, 'hex'); ``` RSA-PSS signing (more secure than PKCS#1 v1.5): ```ts const sign = createSign('SHA256'); sign.update('important message'); const signature = sign.sign({ key: rsaPrivateKey, padding: constants.RSA_PKCS1_PSS_PADDING, saltLength: constants.RSA_PSS_SALTLEN_MAX_SIGN }, 'base64'); ``` ECDSA with IEEE-P1363 format: ```ts const sign = createSign('SHA256'); sign.update('data'); // IEEE-P1363 format (r || s) - used in some blockchain systems const signature = sign.sign({ key: ecPrivateKey, dsaEncoding: 'ieee-p1363' }, 'hex'); ``` *** ## Class: Verify The `Verify` class verifies digital signatures. Instances are created using the `createVerify()` factory function. ```ts const verify = createVerify('SHA256'); verify.update('some data to sign'); verify.end(); const isValid = verify.verify(publicKey, signature); console.log(isValid); // true or false ``` ### verify.update(data\[, inputEncoding]) Updates the `Verify` content with the given `data`. Must be called with the **exact same data** that was signed, in the **exact same order**. **Parameters:** | Name | Type | Description | | :-------------- | :------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------- | | `data` | `string \| Buffer \| TypedArray \| DataView` | The data to verify | | `inputEncoding` | `string` | The encoding of the data string (if `data` is a string). One of `'utf8'`, `'ascii'`, or `'latin1'`. Default: `'utf8'` | **Returns:** `this` (for method chaining) **Examples:** ```ts const verify = createVerify('SHA256'); // Must match signing data exactly verify.update('Hello World'); // Or multiple updates (must match signing order) verify.update('Hello'); verify.update(' '); verify.update('World'); ``` *** ### verify.verify(publicKey, signature\[, signatureEncoding]) Verifies the provided `signature` data using the given `publicKey`. **Parameters:** | Name | Type | Description | | :------------------ | :------------------------------------------- | :----------------------------------------------------------------------------------------------------------- | | `publicKey` | `KeyObject \| string \| Buffer \| Object` | Public key for verification. Can be a `KeyObject`, PEM-encoded string, DER buffer, or an object with options | | `signature` | `string \| Buffer \| TypedArray \| DataView` | The signature to verify | | `signatureEncoding` | `string` | Encoding of `signature` if it's a string. One of `'hex'`, `'base64'`, or `'base64url'` | When `publicKey` is an object, it may contain: **Returns:** `boolean` - `true` if the signature is valid for the given data and public key, `false` otherwise. **Examples:** Basic verification: ```ts const verify = createVerify('SHA256'); verify.update('data to sign'); // Verify hex signature const isValid = verify.verify(publicKey, signatureHex, 'hex'); // Verify base64 signature const isValid2 = verify.verify(publicKey, signatureB64, 'base64'); // Verify buffer signature const isValid3 = verify.verify(publicKey, signatureBuffer); ``` Verifying RSA-PSS signatures: ```ts const verify = createVerify('SHA256'); verify.update('important message'); const isValid = verify.verify({ key: rsaPublicKey, padding: constants.RSA_PKCS1_PSS_PADDING, saltLength: constants.RSA_PSS_SALTLEN_AUTO // Auto-detect salt length }, signature, 'base64'); ``` Verifying ECDSA with IEEE-P1363: ```ts const verify = createVerify('SHA256'); verify.update('data'); const isValid = verify.verify({ key: ecPublicKey, dsaEncoding: 'ieee-p1363' }, signature, 'hex'); ``` *** ## Module Methods ### sign(algorithm, data, key\[, callback]) One-shot signing function. Signs data without creating a `Sign` object. Required for Ed25519/Ed448 which hash internally. **Parameters:** | Name | Type | Description | | :---------- | :---------------------------------------- | :------------------------------------------ | | `algorithm` | `string \| null` | Hash algorithm, or `null` for Ed25519/Ed448 | | `data` | `Buffer \| TypedArray \| DataView` | Data to sign | | `key` | `KeyObject \| string \| Buffer \| Object` | Private key | | `callback` | `Function` | Optional `(err, signature)` callback | **Returns:** `Buffer` (sync) or `void` (with callback) ```ts // Ed25519 (must use null algorithm) const { publicKey, privateKey } = generateKeyPairSync('ed25519'); const data = Buffer.from('message'); const signature = sign(null, data, privateKey); const isValid = verify(null, data, publicKey, signature); ``` ```ts // RSA with one-shot API const { publicKey, privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048, }); const signature = sign('sha256', Buffer.from('data'), privateKey); const isValid = verify('sha256', Buffer.from('data'), publicKey, signature); ``` ### verify(algorithm, data, key, signature\[, callback]) One-shot verification function. Pairs with `sign()`. **Parameters:** | Name | Type | Description | | :---------- | :---------------------------------------- | :------------------------------------------ | | `algorithm` | `string \| null` | Hash algorithm, or `null` for Ed25519/Ed448 | | `data` | `Buffer \| TypedArray \| DataView` | Data that was signed | | `key` | `KeyObject \| string \| Buffer \| Object` | Public key | | `signature` | `Buffer \| TypedArray \| DataView` | Signature to verify | | `callback` | `Function` | Optional `(err, result)` callback | **Returns:** `boolean` *** ### createSign(algorithm) Creates and returns a `Sign` object that uses the given algorithm. **Parameters:** | Name | Type | Description | | :---------- | :------- | :-------------------------------------------------------------------------------------------------- | | `algorithm` | `string` | The hash algorithm to use. Common values: `'SHA256'`, `'SHA384'`, `'SHA512'`, `'SHA1'` (deprecated) | **Returns:** `Sign` instance **Algorithm Selection Guide:** | Algorithm | Security | Speed | Use Case | | :-------- | :-------- | :----- | :---------------------------------- | | `SHA256` | High | Fast | General purpose, JWTs, most APIs | | `SHA384` | Very High | Medium | Financial systems, high compliance | | `SHA512` | Maximum | Slower | Maximum security requirements | | `SHA1` | ⚠️ Broken | Fast | **Legacy only** - avoid in new code | **Examples:** ```ts // SHA-256 (recommended for general use) const sign256 = createSign('SHA256'); // SHA-512 (maximum security) const sign512 = createSign('SHA512'); // SHA-384 (balanced) const sign384 = createSign('SHA384'); ``` *** ### createVerify(algorithm) Creates and returns a `Verify` object that uses the given algorithm. **Must use the same algorithm that was used for signing.** **Parameters:** | Name | Type | Description | | :---------- | :------- | :------------------------------------------------------------------ | | `algorithm` | `string` | The hash algorithm. Must match the algorithm used in `createSign()` | **Returns:** `Verify` instance **Examples:** ```ts const verify = createVerify('SHA256'); // Must match signer's algorithm ``` *** ## Real-World Examples ### Example 1: JWT Token Implementation Complete JWT creation and verification: ```ts import { createSign, createVerify, generateKeyPairSync } from 'react-native-quick-crypto'; // Generate keys (do once, save securely) const { publicKey, privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048, publicExponent: 0x10001, }); function createJWT(payload: object): string { // Create header const header = { alg: 'RS256', typ: 'JWT' }; // Encode header and payload const encodedHeader = Buffer.from(JSON.stringify(header)) .toString('base64url'); const encodedPayload = Buffer.from(JSON.stringify(payload)) .toString('base64url'); const dataToSign = `${encodedHeader}.${encodedPayload}`; // Sign const sign = createSign('SHA256'); sign.update(dataToSign); const signature = sign.sign(privateKey, 'base64url'); // Combine return `${dataToSign}.${signature}`; } function verifyJWT(token: string): { valid: boolean; payload?: any } { const parts = token.split('.'); if (parts.length !== 3) { return { valid: false }; } const [encodedHeader, encodedPayload, signature] = parts; const dataToVerify = `${encodedHeader}.${encodedPayload}`; // Verify signature const verify = createVerify('SHA256'); verify.update(dataToVerify); const isValid = verify.verify(publicKey, signature, 'base64url'); if (!isValid) { return { valid: false }; } // Decode payload const payload = JSON.parse( Buffer.from(encodedPayload, 'base64url').toString() ); // Check expiration if (payload.exp && payload.exp < Date.now() / 1000) { return { valid: false }; } return { valid: true, payload }; } // Usage const jwt = createJWT({ sub: 'user123', iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 3600 // 1 hour }); console.log('JWT:', jwt); const result = verifyJWT(jwt); console.log('Valid:', result.valid); console.log('Payload:', result.payload); ``` ### Example 2: API Request Signing (AWS-Style) Sign HTTP requests to prevent tampering and replay attacks: ```ts interface SignedRequest { method: string; url: string; timestamp: number; body: string; signature: string; } function signRequest( method: string, url: string, body: object, privateKey: any ): SignedRequest { const timestamp = Date.now(); // Create canonical string (order matters!) const canonical = [ method.toUpperCase(), url, timestamp.toString(), JSON.stringify(body) ].join('\n'); // Sign const sign = createSign('SHA256'); sign.update(canonical); const signature = sign.sign(privateKey, 'base64'); return { method, url, timestamp, body: JSON.stringify(body), signature }; } function verifyRequest( request: SignedRequest, publicKey: any, maxAge: number = 300000 // 5 minutes ): boolean { // Check timestamp (prevent replay attacks) if (Date.now() - request.timestamp > maxAge) { console.log('Request expired'); return false; } // Reconstruct canonical string const canonical = [ request.method.toUpperCase(), request.url, request.timestamp.toString(), request.body ].join('\n'); // Verify const verify = createVerify('SHA256'); verify.update(canonical); return verify.verify(publicKey, request.signature, 'base64'); } // Usage const signedReq = signRequest( 'POST', '/api/transfer', { amount: 100, to: 'account456' }, privateKey ); // Send to server with headers fetch(signedReq.url, { method: signedReq.method, headers: { 'X-Timestamp': signedReq.timestamp.toString(), 'X-Signature': signedReq.signature, 'Content-Type': 'application/json' }, body: signedReq.body }); // Server-side verification const isValid = verifyRequest(signedReq, publicKey); ``` ### Example 3: Code/App Update Signing Sign mobile app updates to ensure authenticity: ```ts import { createSign, createVerify, createHash } from 'react-native-quick-crypto'; interface SignedUpdate { version: string; fileHash: string; fileSize: number; timestamp: number; signature: string; } async function signAppUpdate( bundlePath: string, version: string, privateKey: any ): Promise { // Read file const bundleData = await RNFS.readFile(bundlePath, 'base64'); const bundleBuffer = Buffer.from(bundleData, 'base64'); // Calculate file hash const hash = createHash('sha256'); hash.update(bundleBuffer); const fileHash = hash.digest('hex'); const timestamp = Date.now(); const fileSize = bundleBuffer.length; // Create manifest const manifest = JSON.stringify({ version, fileHash, fileSize, timestamp }); // Sign manifest const sign = createSign('SHA256'); sign.update(manifest); const signature = sign.sign(privateKey, 'base64'); return { version, fileHash, fileSize, timestamp, signature }; } async function verifyAndApplyUpdate( bundlePath: string, updateInfo: SignedUpdate, trustedPublicKey: any ): Promise { // Reconstruct manifest const manifest = JSON.stringify({ version: updateInfo.version, fileHash: updateInfo.fileHash, fileSize: updateInfo.fileSize, timestamp: updateInfo.timestamp }); // Verify signature const verify = createVerify('SHA256'); verify.update(manifest); const signatureValid = verify.verify( trustedPublicKey, updateInfo.signature, 'base64' ); if (!signatureValid) { console.error('Update signature invalid!'); return false; } // Verify file hash const bundleData = await RNFS.readFile(bundlePath, 'base64'); const bundleBuffer = Buffer.from(bundleData, 'base64'); const hash = createHash('sha256'); hash.update(bundleBuffer); const actualHash = hash.digest('hex'); if (actualHash !== updateInfo.fileHash) { console.error('File corrupted or tampered!'); return false; } // Verify file size if (bundleBuffer.length !== updateInfo.fileSize) { console.error('File size mismatch!'); return false; } console.log('Update verified successfully!'); // Safe to apply update... return true; } ``` ### Example 4: Multi-Party Document Signing Multiple parties sign the same document: ```ts interface Signature { signer: string; signedAt: number; signature: string; } class SignedDocument { private content: string; private signatures: Signature[] = []; constructor(content: string) { this.content = content; } addSignature(signerName: string, privateKey: any): void { const timestamp = Date.now(); // Include all previous signatures in new signature const dataToSign = JSON.stringify({ content: this.content, signer: signerName, signedAt: timestamp, previousSignatures: this.signatures }); const sign = createSign('SHA256'); sign.update(dataToSign); const signature = sign.sign(privateKey, 'base64'); this.signatures.push({ signer: signerName, signedAt: timestamp, signature }); } verifySignature( index: number, publicKey: any ): boolean { if (index >= this.signatures.length) { return false; } const sig = this.signatures[index]; const previousSigs = this.signatures.slice(0, index); const dataToVerify = JSON.stringify({ content: this.content, signer: sig.signer, signedAt: sig.signedAt, previousSignatures: previousSigs }); const verify = createVerify('SHA256'); verify.update(dataToVerify); return verify.verify(publicKey, sig.signature, 'base64'); } verifyAll(publicKeys: Map): boolean { for (let i = 0; i < this.signatures.length; i++) { const sig = this.signatures[i]; const publicKey = publicKeys.get(sig.signer); if (!publicKey || !this.verifySignature(i, publicKey)) { console.log(`Signature ${i} (${sig.signer}) failed verification`); return false; } } return true; } getSignatures(): Signature[] { return [...this.signatures]; } } // Usage const document = new SignedDocument('Contract: Transfer $1M...'); // Alice signs document.addSignature('Alice', alicePrivateKey); // Bob signs document.addSignature('Bob', bobPrivateKey); // Charlie signs document.addSignature('Charlie', charliePrivateKey); // Verify all signatures const publicKeys = new Map([ ['Alice', alicePublicKey], ['Bob', bobPublicKey], ['Charlie', charliePublicKey] ]); const allValid = document.verifyAll(publicKeys); console.log('All signatures valid:', allValid); ``` *** ## Supported Algorithms ### Hash Algorithms The algorithm string passed to `createSign()` and `createVerify()` determines the hash function used. Both standard names and legacy OpenSSL `RSA-*` prefixed names are supported (e.g. `RSA-SHA256` is treated as `SHA256`). Algorithm names are case-insensitive. | Algorithm | Output Size | Security Level | Recommended Use | | :-------- | :---------- | :------------- | :-------------------------------- | | `SHA256` | 256 bits | High | **General purpose - recommended** | | `SHA384` | 384 bits | Very High | High-security applications | | `SHA512` | 512 bits | Maximum | Maximum security requirements | | `SHA1` | 160 bits | ⚠️ **Broken** | **Legacy only - avoid!** | ### Key Types & Signature Schemes Different key types support different signature schemes: #### RSA Keys * **RSASSA-PKCS1-v1\_5**: Default RSA signature scheme * **RSA-PSS**: Probabilistic Signature Scheme (more secure) ```ts // PKCS#1 v1.5 (default) sign.sign(rsaPrivateKey); // RSA-PSS (recommended) sign.sign({ key: rsaPrivateKey, padding: constants.RSA_PKCS1_PSS_PADDING }); ``` #### ECDSA Keys (Elliptic Curve) * Supported curves: Any OpenSSL-supported curve name (`prime256v1`, `secp384r1`, `secp521r1`, `secp256k1`, etc.) * Signature encodings: DER (default) or IEEE-P1363 ```ts // DER encoding (default) sign.sign(ecPrivateKey); // IEEE-P1363 (used in blockchain) sign.sign({ key: ecPrivateKey, dsaEncoding: 'ieee-p1363' }); ``` #### Ed25519 Keys * Modern, fast, simple * No hash function parameter needed (uses internal hash) * Fixed 64-byte signatures ```ts // Ed25519 uses different API const signature = edSign(null, data, ed25519PrivateKey); const isValid = edVerify(null, data, ed25519PublicKey, signature); ``` *** ## Security Considerations 1. **Never expose private keys** - Store in device Keychain/KeyStore, not AsyncStorage 2. **Use strong algorithms** - SHA-256 minimum, prefer SHA-384/512 for high security 3. **Avoid SHA-1** - It's cryptographically broken 4. **Verify algorithm match** - Signing and verification must use same algorithm 5. **Include timestamps** - Prevent replay attacks ### Best Practices **1. Algorithm Selection:** ```ts // ✅ Good - SHA-256 or better const sign = createSign('SHA256'); // ❌ Bad - SHA-1 is broken const sign = createSign('SHA1'); ``` **2. Key Management:** ```ts // ✅ Good - Use Keychain/KeyStore await Keychain.setGenericPassword( 'privateKey', privateKeyPEM, { service: 'com.myapp.signing' } ); // ❌ Bad - Plain storage await AsyncStorage.setItem('privateKey', privateKeyPEM); ``` **3. Prevent Replay Attacks:** ```ts // ✅ Good - Include timestamp and nonce const dataToSign = JSON.stringify({ data: actualData, timestamp: Date.now(), nonce: randomBytes(16).toString('hex') }); // ❌ Bad - No timestamp const dataToSign = JSON.stringify(actualData); ``` **4. Use Authenticated Contexts:** ```ts // ✅ Good - Include context in signature const canonical = `${method}\n${url}\n${timestamp}\n${body}`; // ❌ Bad - Signature could be reused for different requests const canonical = body; ``` *** ## Common Errors ### Error: `error:04800074:PEM routines::bad password read` **Cause:** Your private key is encrypted, but you didn't provide the passphrase. **Solution:** ```ts // ❌ Wrong: Calling sign.sign() with encrypted key but no passphrase // sign.sign(encryptedPrivateKey); // Throws error! // ✅ Correct const sign = createSign('SHA256'); sign.update('data'); sign.sign({ key: encryptedPrivateKey, passphrase: 'your-password' }, 'hex'); ``` *** ### Verification always returns `false` **Possible causes:** 1. **Different algorithms:** ```ts // ❌ Wrong: Using different algorithms for signing and verification // const sign = createSign('SHA256'); // const verify = createVerify('SHA512'); // Different! Will fail! // ✅ Correct const sign = createSign('SHA256'); const verify = createVerify('SHA256'); // Same algorithm ``` 2. **Data mismatch:** ```ts // ❌ Wrong: Data mismatch - extra space in verification // sign.update('Hello World'); // verify.update('Hello World'); // Extra space! Will fail! // ✅ Correct: Use exact same data const { publicKey, privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 }); const data = 'Hello World'; const sign = createSign('SHA256'); sign.update(data); const signature = sign.sign(privateKey); const verify = createVerify('SHA256'); verify.update(data); // Exact same string verify.verify(publicKey, signature); // Success ``` 3. **Encoding issues:** ```ts // ❌ Wrong: Encoding mismatch - stringified vs object // sign.update(JSON.stringify(data)); // Stringified // verify.update(data); // Not stringified! Will fail! // ✅ Correct: Use same encoding const { publicKey, privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 }); const data = { foo: 'bar' }; const payload = JSON.stringify(data); const sign = createSign('SHA256'); sign.update(payload); const signature = sign.sign(privateKey); const verify = createVerify('SHA256'); verify.update(payload); // Same encoding verify.verify(publicKey, signature); // Success ``` 4. **Wrong key pair:** ```ts // ❌ Wrong: Using mismatched key pair // sign.sign(privateKeyA); // verify.verify(publicKeyB, sig); // Different pair! Will fail! // ✅ Correct: Use matching key pair const { publicKey, privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048 }); const sign = createSign('SHA256'); sign.update('test'); const signature = sign.sign(privateKey); const verify = createVerify('SHA256'); verify.update('test'); verify.verify(publicKey, signature); // Matching pair ``` *** ### Error: `sign.sign is not a function` after calling it once **Cause:** `Sign` objects are single-use. After calling `sign()`, the object is unusable. **Solution:** ```ts // ❌ Wrong: Reusing Sign object after calling sign() // const sign = createSign('SHA256'); // const sig1 = sign.sign(key1); // const sig2 = sign.sign(key2); // Error! Object is single-use // ✅ Correct: Create new Sign object for each signature const { privateKey: key1 } = generateKeyPairSync('rsa', { modulusLength: 2048 }); const { privateKey: key2 } = generateKeyPairSync('rsa', { modulusLength: 2048 }); const sign1 = createSign('SHA256'); sign1.update('test'); const sig1 = sign1.sign(key1); const sign2 = createSign('SHA256'); sign2.update('test'); const sig2 = sign2.sign(key2); ``` *** ## Performance Notes **Signature generation performance** (RSA-2048, SHA-256, typical mobile device): * Sign operation: \~5ms (200 signatures/second) * Verify operation: \~0.5ms (2000 verifications/second) **Recommendations:** 1. **Verification is 10× faster than signing** - batch verify when possible 2. **Use ECDSA for better performance** - 10-100× faster than RSA 3. **Consider Ed25519 for maximum speed** - fastest option available 4. **Run signing on background thread** for large files to avoid UI freezes ```ts // Example: Background signing async function signInBackground(data: string, key: any): Promise { return new Promise((resolve) => { setTimeout(() => { const sign = createSign('SHA256'); sign.update(data); resolve(sign.sign(key, 'hex')); }, 0); }); } ``` # Subtle (WebCrypto) (/docs/api/subtle) The `SubtleCrypto` interface allows you to perform cryptographic operations using the standardized W3C Web Cryptography API. ## Table of Contents * [Theory](#theory) * [Module Methods](#module-methods) * [Supported Algorithms](#supported-algorithms) * [Real-World Examples](#real-world-examples) ## Theory The WebCrypto API differs from the Node.js API in two main ways: 1. **Asynchronous Promises**: All operations (`encrypt`, `verify`, etc.) return Promises. This reflects the intense nature of crypto operations and prevents UI blocking. 2. **Unextractable Keys**: A `CryptoKey` object is a handle to a key. If marked `extractable: false`, the raw key bytes can never be accessed by JavaScript code, protecting it from XSS attacks. *** ## Module Methods ### encrypt(algorithm, key, data) Encrypts data. **Parameters:** **Supported algorithms:** `AES-CTR`, `AES-CBC`, `AES-GCM`, `AES-OCB`, `ChaCha20-Poly1305`, `RSA-OAEP` ### decrypt(algorithm, key, data) Decrypts data. Supports the same algorithms as `encrypt`. ### sign(algorithm, key, data) Generates a digital signature. **Supported algorithms:** `ECDSA`, `Ed25519`, `Ed448`, `HMAC`, `KMAC128`, `KMAC256`, `ML-DSA-44`, `ML-DSA-65`, `ML-DSA-87`, `RSA-PSS`, `RSASSA-PKCS1-v1_5` ### verify(algorithm, key, signature, data) Verifies a digital signature. Returns `true` or `false`. Supports the same algorithms as `sign`. ### digest(algorithm, data) Generates a hash digest. **Supported algorithms:** `SHA-1`, `SHA-256`, `SHA-384`, `SHA-512`, `SHA3-256`, `SHA3-384`, `SHA3-512`, `cSHAKE128`, `cSHAKE256` `cSHAKE128` and `cSHAKE256` provide SHAKE XOF (Extendable Output Function) support. The `length` parameter (in bytes) is required to specify the output length. ```ts // SHA3-256 const hash = await subtle.digest('SHA3-256', data); // cSHAKE256 with custom output length const xof = await subtle.digest( { name: 'cSHAKE256', length: 64 }, data ); ``` ### generateKey(algorithm, extractable, keyUsages) Generates a new key or key pair. **Example (RSA-OAEP):** ```ts const keyPair = await subtle.generateKey( { name: "RSA-OAEP", modulusLength: 4096, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" }, true, ["encrypt", "decrypt"] ); ``` ### importKey(format, keyData, algorithm, extractable, keyUsages) Imports a key. Supported formats: `raw`, `raw-secret`, `raw-public`, `raw-seed`, `pkcs8`, `spki`, `jwk`. ### exportKey(format, key) Exports a key (if extractable). Supported formats match `importKey`. ### deriveBits(algorithm, baseKey, length) Derives raw bits from a key using a key derivation algorithm. **Parameters:** **Supported algorithms:** `PBKDF2`, `HKDF`, `Argon2d`, `Argon2i`, `Argon2id`, `ECDH`, `X25519`, `X448` ```ts // PBKDF2 const passwordKey = await subtle.importKey( 'raw', new TextEncoder().encode('password'), 'PBKDF2', false, ['deriveBits'] ); const bits = await subtle.deriveBits( { name: 'PBKDF2', salt: crypto.getRandomValues(new Uint8Array(16)), iterations: 600000, hash: 'SHA-256' }, passwordKey, 256 ); ``` ```ts // HKDF const ikmKey = await subtle.importKey( 'raw', masterSecret, 'HKDF', false, ['deriveBits'] ); const derived = await subtle.deriveBits( { name: 'HKDF', hash: 'SHA-256', salt: new Uint8Array(32), info: new TextEncoder().encode('session-key') }, ikmKey, 256 ); ``` ```ts // X25519 ECDH const aliceKey = await subtle.generateKey( { name: 'X25519' }, true, ['deriveBits'] ); const sharedBits = await subtle.deriveBits( { name: 'X25519', public: bobPublicKey }, aliceKey.privateKey, 256 ); ``` ### deriveKey(algorithm, baseKey, derivedKeyAlgorithm, extractable, keyUsages) Derives a new `CryptoKey` from a base key. Same derivation algorithms as `deriveBits`, but produces a usable key directly. ```ts // Derive an AES key from a password using PBKDF2 const aesKey = await subtle.deriveKey( { name: 'PBKDF2', salt: crypto.getRandomValues(new Uint8Array(16)), iterations: 600000, hash: 'SHA-256' }, passwordKey, { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ); ``` ### wrapKey(format, key, wrappingKey, wrapAlgo) Exports a key and encrypts (wraps) it for safe transport. **Parameters:** **Wrapping algorithms:** `AES-KW`, `AES-CBC`, `AES-CTR`, `AES-GCM`, `AES-OCB`, `ChaCha20-Poly1305`, `RSA-OAEP` ```ts // Wrap an AES key with AES-KW const wrappingKey = await subtle.generateKey( { name: 'AES-KW', length: 256 }, true, ['wrapKey', 'unwrapKey'] ); const keyToWrap = await subtle.generateKey( { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ); const wrapped = await subtle.wrapKey( 'raw', keyToWrap, wrappingKey, { name: 'AES-KW' } ); ``` ### unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgo, unwrappedKeyAlgo, extractable, keyUsages) Decrypts (unwraps) a wrapped key and imports it. ```ts const unwrapped = await subtle.unwrapKey( 'raw', wrapped, wrappingKey, { name: 'AES-KW' }, { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ); ``` ### getPublicKey(key, keyUsages) Extracts the public key from a private `CryptoKey`. ```ts const keyPair = await subtle.generateKey( { name: 'ECDSA', namedCurve: 'P-256' }, true, ['sign', 'verify'] ); const publicOnly = await subtle.getPublicKey( keyPair.privateKey, ['verify'] ); ``` ### SubtleCrypto.supports(operation, algorithm\[, lengthOrAdditionalAlgorithm]) Static method that checks if a given operation/algorithm combination is supported. ```ts const canSign = SubtleCrypto.supports('sign', 'Ed25519'); const canEncrypt = SubtleCrypto.supports('encrypt', 'AES-GCM', 256); ``` ### Encapsulation (ML-KEM) Post-quantum key encapsulation methods for ML-KEM. See the [PQC page](/docs/api/pqc) for full details. * `encapsulateBits(algorithm, key)` — Encapsulates and returns `{ sharedSecret, ciphertext }` * `encapsulateKey(algorithm, key, sharedKeyAlgo, extractable, usages)` — Encapsulates and returns a derived `CryptoKey` * `decapsulateBits(algorithm, key, ciphertext)` — Decapsulates to raw bits * `decapsulateKey(algorithm, key, ciphertext, sharedKeyAlgo, extractable, usages)` — Decapsulates to a `CryptoKey` *** ## Supported Algorithms ### Encryption/Decryption | Algorithm | Type | Notes | | :------------------ | :--------- | :----------------------------------------- | | `AES-CBC` | Symmetric | Block cipher, requires padding | | `AES-CTR` | Symmetric | Stream-like, counter mode | | `AES-GCM` | Symmetric | **Recommended** — authenticated encryption | | `AES-OCB` | Symmetric | Authenticated, faster than GCM | | `ChaCha20-Poly1305` | Symmetric | Authenticated, fast on mobile | | `RSA-OAEP` | Asymmetric | For small payloads or key wrapping | ### Signing/Verification | Algorithm | Type | Notes | | :-------------------- | :----------- | :----------------------------- | | `ECDSA` | Asymmetric | P-256, P-384, P-521, secp256k1 | | `Ed25519` | Asymmetric | Fast, deterministic | | `Ed448` | Asymmetric | Higher security Ed curve | | `HMAC` | Symmetric | Message authentication | | `KMAC128` / `KMAC256` | Symmetric | Keccak-based MAC | | `ML-DSA-44/65/87` | Post-Quantum | FIPS 204 lattice signatures | | `RSA-PSS` | Asymmetric | Probabilistic RSA signatures | | `RSASSA-PKCS1-v1_5` | Asymmetric | Legacy RSA signatures | ### Key Derivation | Algorithm | Input | Notes | | :------------- | :----------- | :--------------------------------- | | `PBKDF2` | Password | Iterations-based, widely supported | | `HKDF` | Key material | Extract-and-Expand (RFC 5869) | | `Argon2d/i/id` | Password | Memory-hard, PHC winner | | `ECDH` | Key pair | P-256, P-384, P-521, secp256k1 | | `X25519` | Key pair | Modern ECDH | | `X448` | Key pair | Higher security ECDH | ### Key Wrapping | Algorithm | Notes | | :-------------------------------- | :----------------------------------- | | `AES-KW` | RFC 3394, purpose-built key wrapping | | `AES-GCM` | Authenticated wrapping | | `AES-CBC` / `AES-CTR` / `AES-OCB` | Alternative wrapping | | `ChaCha20-Poly1305` | Authenticated wrapping | | `RSA-OAEP` | Asymmetric wrapping | *** ## Real-World Examples ### End-to-End Encryption Symmetric encryption with a random key. ```ts async function secureMessage(msg: string) { // Generate Key const key = await subtle.generateKey( { name: "AES-GCM", length: 256 }, true, ["encrypt", "decrypt"] ); // Encrypt const iv = QuickCrypto.getRandomValues(new Uint8Array(12)); const encoded = new TextEncoder().encode(msg); const ciphertext = await subtle.encrypt( { name: "AES-GCM", iv: iv }, key, encoded ); return { key, iv, ciphertext }; } ``` ### JWT Verification Importing a public key from a JWK to verify a token. ```ts async function verifyToken(token: string, jwk: any) { const pubKey = await subtle.importKey( "jwk", jwk, { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"] ); const [header, payload, sigBase64] = token.split('.'); const data = new TextEncoder().encode(`${header}.${payload}`); const signature = Buffer.from(sigBase64, 'base64'); return await subtle.verify( { name: "ECDSA", hash: "SHA-256" }, pubKey, signature, data ); } ``` ### Password-Based Encryption (PBKDF2 + AES-GCM) ```ts async function encryptWithPassword(plaintext: string, password: string) { const salt = crypto.getRandomValues(new Uint8Array(16)); const iv = crypto.getRandomValues(new Uint8Array(12)); const passwordKey = await subtle.importKey( 'raw', new TextEncoder().encode(password), 'PBKDF2', false, ['deriveKey'] ); const aesKey = await subtle.deriveKey( { name: 'PBKDF2', salt, iterations: 600000, hash: 'SHA-256' }, passwordKey, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt'] ); const ciphertext = await subtle.encrypt( { name: 'AES-GCM', iv }, aesKey, new TextEncoder().encode(plaintext) ); return { ciphertext, salt, iv }; } ``` # Utilities (/docs/api/utilities) Utility functions for common crypto operations that don't fit neatly into a single class. ## Table of Contents * [One-Shot Hashing](#one-shot-hashing) * [Timing-Safe Comparison](#timing-safe-comparison) * [Prime Numbers](#prime-numbers) * [Introspection](#introspection) * [Constants](#constants) ## One-Shot Hashing ### hash(algorithm, data\[, outputEncoding]) Computes a hash digest in a single call without creating a `Hash` object. More convenient and slightly faster for small inputs. **Parameters:** **Returns:** `string | Buffer` ```ts // Get hex digest const hex = hash('sha256', 'hello world', 'hex'); // Get Buffer const buf = hash('sha256', 'hello world'); // Hash binary data const digest = hash('sha512', Buffer.from([1, 2, 3]), 'base64'); ``` For repeated hashing or streaming data, use `createHash()` instead. The one-shot `hash()` is best for hashing a single value. *** ## Timing-Safe Comparison ### timingSafeEqual(a, b) Compares two buffers in constant time, preventing timing attacks. Both buffers must have the same byte length. **Parameters:** **Returns:** `boolean` Always use `timingSafeEqual` when comparing MACs, signatures, hashes, or tokens. Regular `===` comparison leaks timing information that attackers can exploit. ```ts function verifyWebhook(payload: string, receivedSig: string, secret: string): boolean { const expected = createHmac('sha256', secret) .update(payload) .digest(); const received = Buffer.from(receivedSig, 'hex'); if (expected.length !== received.length) return false; return timingSafeEqual(expected, received); } ``` *** ## Prime Numbers ### generatePrime(size\[, options\[, callback]]) Generates a cryptographic prime number. **Parameters:** ```ts // Async generatePrime(256, (err, prime) => { console.log(prime.toString('hex')); }); // Sync const prime = generatePrimeSync(256); // As BigInt const bigPrime = generatePrimeSync(256, { bigint: true }); // Safe prime (slower) const safePrime = generatePrimeSync(256, { safe: true }); ``` ### checkPrime(candidate\[, options], callback) Tests whether a number is prime using Miller-Rabin probabilistic primality testing. **Parameters:** ```ts const prime = generatePrimeSync(256); // Async checkPrime(prime, (err, result) => { console.log(result); // true }); // Sync const isPrime = checkPrimeSync(prime); console.log(isPrime); // true // Test a specific number checkPrimeSync(7n); // true checkPrimeSync(4n); // false ``` *** ## Introspection ### getCiphers() Returns an array of supported cipher algorithm names. ```ts const ciphers = getCiphers(); // ['aes-128-cbc', 'aes-128-gcm', 'aes-256-gcm', 'chacha20-poly1305', ...] ``` ### getHashes() Returns an array of supported hash algorithm names. ```ts const hashes = getHashes(); // ['sha1', 'sha256', 'sha512', 'sha3-256', 'blake2b512', ...] ``` ### getCurves() Returns an array of supported elliptic curve names. ```ts const curves = getCurves(); // ['prime256v1', 'secp384r1', 'secp521r1', 'secp256k1', ...] ``` ### getCipherInfo(nameOrNid\[, options]) Returns details about a cipher algorithm. **Parameters:** **Returns:** `Object | undefined` ```ts const info = getCipherInfo('aes-256-gcm'); // { // name: 'aes-256-gcm', // nid: 901, // blockSize: 1, // ivLength: 12, // keyLength: 32, // mode: 'gcm' // } ``` *** ## Constants The `crypto.constants` object provides numeric constants used with other crypto functions (padding modes, DH check flags, etc.). ```ts // RSA padding modes constants.RSA_PKCS1_PADDING; constants.RSA_PKCS1_OAEP_PADDING; constants.RSA_PKCS1_PSS_PADDING; constants.RSA_NO_PADDING; // RSA-PSS salt lengths constants.RSA_PSS_SALTLEN_DIGEST; constants.RSA_PSS_SALTLEN_MAX_SIGN; constants.RSA_PSS_SALTLEN_AUTO; ``` # X509 Certificates (/docs/api/x509) The `X509Certificate` class provides a complete implementation for working with X.509 certificates — the standard format used in TLS/SSL, code signing, and PKI systems. Parse certificates, extract properties, validate hostnames, and verify signatures. **Certificate pinning** in mobile apps, **mTLS client certificate** validation, **certificate chain verification**, **hostname matching** for custom TLS implementations, and **extracting public keys** from certificates. ## Table of Contents * [Theory](#theory) * [Class: X509Certificate](#class-x509certificate) * [Properties](#properties) * [Methods](#methods) * [Real-World Examples](#real-world-examples) ## Theory X.509 is the standard format for public key certificates. A certificate binds an identity (subject) to a public key, signed by a Certificate Authority (CA). Key concepts: 1. **Subject / Issuer**: Distinguished Names identifying the certificate holder and signer. 2. **Validity Period**: Time window during which the certificate is valid. 3. **Subject Alternative Name (SAN)**: Additional identities (DNS names, IPs, emails) the certificate is valid for. 4. **Fingerprint**: A hash of the certificate used for identification (not security). 5. **CA flag**: Whether the certificate can sign other certificates. *** ## Class: X509Certificate ### Constructor ```ts const cert = new X509Certificate(pemString); ``` **Parameters:** Accepts both PEM-encoded strings (beginning with `-----BEGIN CERTIFICATE-----`) and DER-encoded binary data. *** ## Properties All properties are lazily computed and cached on first access. | Property | Type | Description | | :---------------------- | :---------- | :--------------------------------------------------------- | | `subject` | `string` | Distinguished name of the certificate subject | | `issuer` | `string` | Distinguished name of the issuing CA | | `subjectAltName` | `string` | Subject Alternative Name extension | | `infoAccess` | `string` | Authority Information Access extension | | `validFrom` | `string` | "Not Before" date as a string | | `validTo` | `string` | "Not After" date as a string | | `validFromDate` | `Date` | "Not Before" as a JavaScript Date object | | `validToDate` | `Date` | "Not After" as a JavaScript Date object | | `serialNumber` | `string` | Certificate serial number (uppercase hex) | | `signatureAlgorithm` | `string` | Signature algorithm name (e.g., `sha256WithRSAEncryption`) | | `signatureAlgorithmOid` | `string` | Signature algorithm OID | | `fingerprint` | `string` | SHA-1 fingerprint (colon-separated hex) | | `fingerprint256` | `string` | SHA-256 fingerprint (colon-separated hex) | | `fingerprint512` | `string` | SHA-512 fingerprint (colon-separated hex) | | `extKeyUsage` | `string[]` | Extended key usage OIDs (also available as `keyUsage`) | | `ca` | `boolean` | Whether this is a CA certificate | | `raw` | `Buffer` | Raw DER-encoded certificate bytes | | `publicKey` | `KeyObject` | The certificate's public key as a KeyObject | | `issuerCertificate` | `undefined` | Always `undefined` (no TLS context in React Native) | ```ts const cert = new X509Certificate(pemString); console.log(cert.subject); // C=US\nST=California\nO=Example\nCN=example.com console.log(cert.fingerprint256); // AB:CD:EF:12:34:... console.log(cert.ca); // true console.log(cert.publicKey.type); // 'public' ``` *** ## Methods ### x509.checkHost(name\[, options]) Checks whether the certificate matches the given hostname. **Returns:** `string | undefined` — The matched hostname, or `undefined` if no match. ```ts const cert = new X509Certificate(pemString); cert.checkHost('example.com'); // 'example.com' cert.checkHost('wrong.com'); // undefined // Disable wildcard matching cert.checkHost('sub.example.com', { wildcards: false }); ``` #### CheckOptions | Option | Type | Default | Description | | :---------------------- | :--------------------------------- | :---------- | :---------------------------------- | | `subject` | `'default' \| 'always' \| 'never'` | `'default'` | When to check the subject CN | | `wildcards` | `boolean` | `true` | Allow wildcard certificate matching | | `partialWildcards` | `boolean` | `true` | Allow partial wildcard matching | | `multiLabelWildcards` | `boolean` | `false` | Allow multi-label wildcard matching | | `singleLabelSubdomains` | `boolean` | `false` | Match single-label subdomains | ### x509.checkEmail(email\[, options]) Checks whether the certificate matches the given email address. **Returns:** `string | undefined` — The matched email, or `undefined` if no match. ```ts cert.checkEmail('user@example.com'); // 'user@example.com' or undefined ``` ### x509.checkIP(ip) Checks whether the certificate matches the given IP address. **Returns:** `string | undefined` — The matched IP, or `undefined` if no match. ```ts cert.checkIP('127.0.0.1'); // '127.0.0.1' cert.checkIP('192.168.1.1'); // undefined ``` ### x509.checkIssued(otherCert) Checks whether this certificate was issued by `otherCert`. **Returns:** `boolean` ```ts // Self-signed certificate cert.checkIssued(cert); // true // Chain validation rootCert.checkIssued(intermediateCert); // true or false ``` ### x509.checkPrivateKey(privateKey) Checks whether the given private key matches this certificate's public key. **Returns:** `boolean` ```ts const privKey = createPrivateKey(privateKeyPem); cert.checkPrivateKey(privKey); // true ``` ### x509.verify(publicKey) Verifies that the certificate was signed with the given public key. **Returns:** `boolean` ```ts // For self-signed certificates cert.verify(cert.publicKey); // true ``` ### x509.toString() Returns the PEM-encoded certificate string. **Returns:** `string` ### x509.toJSON() Returns the PEM-encoded certificate string (same as `toString()`). **Returns:** `string` ### x509.toLegacyObject() Returns a plain object with legacy certificate fields. **Returns:** `object` *** ## Real-World Examples ### Certificate Pinning ```ts const PINNED_FINGERPRINT = 'AB:CD:EF:...'; function validateServerCert(pemCert: string): boolean { const cert = new X509Certificate(pemCert); // Check fingerprint if (cert.fingerprint256 !== PINNED_FINGERPRINT) { return false; } // Check validity const now = new Date(); if (now < cert.validFromDate || now > cert.validToDate) { return false; } return true; } ``` ### Hostname Verification ```ts function verifyHostname(pemCert: string, hostname: string): boolean { const cert = new X509Certificate(pemCert); return cert.checkHost(hostname) !== undefined; } ``` ### Extract Public Key from Certificate ```ts const cert = new X509Certificate(pemCert); const publicKey = cert.publicKey; // Use the public key for encryption or verification console.log(publicKey.type); // 'public' console.log(publicKey.asymmetricKeyType); // 'rsa' ``` ### Validate Certificate Chain ```ts function validateChain(leafPem: string, issuerPem: string): boolean { const leaf = new X509Certificate(leafPem); const issuerCert = new X509Certificate(issuerPem); // Check the leaf was issued by the issuer if (!issuerCert.checkIssued(leaf)) { return false; } // Verify the leaf's signature with issuer's public key if (!leaf.verify(issuerCert.publicKey)) { return false; } return true; } ``` # Community & Team (/docs/introduction/community) **RNQC** is a community-driven project maintained by **Margelo** and an amazing group of open-source contributors. ## Contributors We are grateful to the community for contributing to this project. ## License RNQC is licensed under the **MIT License**. # Comparison (/docs/introduction/comparison) RNQC is designed to be the faster, more complete alternative to existing solutions. | Feature | RNQC | react-native-crypto | react-native-fast-crypto | | :---------------- | :--------------------: | :-----------------: | :----------------------: | | **Performance** | **Nitro** (Native C++) | 🐢 Bridge (JS-shim) | ⚡️ JSI (Partial) | | **Node.js API** | **1:1 Compatible** | Compatible | Custom API | | **Sync Methods** | Fully Supported | Async Only | Supported | | **Thread Safety** | **Off-Main-Thread** | Blocks JS Thread | Off-Main-Thread | | **Maintenance** | **Active (Margelo)** | Abandoned | Stale | *** ## Visual Comparison ### Standard Bridge vs. RNQC Nitro *** ## Theory: Why is it faster? The performance gap comes down to **Memory Access** and **Execution Model**. ### 1. Zero-Copy Buffers Standard React Native modules (like `react-native-crypto`) communicate via the Bridge. To hash a 1MB file: 1. **JS**: Converts Buffer to Base64 string (Costly). 2. **Bridge**: Serializes msg to JSON. Used double memory. 3. **Native**: Deserializes JSON, decodes Base64 to bytes. 4. **Native**: Hashes bytes. **RNQC (Nitro)**: 1. **JS**: Holds a reference to an `ArrayBuffer`. 2. **C++**: Reads that memory address directly. **Hash.** 3. **Done.** ### 2. C++ Thread Pool JSI allows us to define "Hybrid Objects" that can spawn their own C++ threads. We use a dedicated thread pool for heavy algorithms (like `scrypt` or `rsa keygen`), ensuring the main JS thread (and your UI) is never blocked, while avoiding the overhead of spawning new OS threads for every call. *** # Benchmarks Proof is in the numbers. We benchmarked RNQC against standard JS implementations (`browserify`) and other native libraries. **Benchmark Philosophy**: This is not meant to disparage the other libraries. On the contrary, they perform amazingly well when used in a server-side Node environment or browser. This library exists because React Native does not have that environment nor the Node Crypto API implementation at hand. So the benchmark suite is there to show you the speedup vs. the alternative of using a pure JS library on React Native. **Environment**: iPhone 15 Pro, iOS 17. ## 1. Hashing (BLAKE3) Blake3 Benchmark BLAKE3 is optimized for speed. RNQC (via Nitro) processes large inputs **90x faster** than JS implementations. | Operation | RNQC | @noble/hashes | Speedup | | :------------- | :---------------- | :------------ | :--------- | | **32b input** | **105,397 ops/s** | 13,175 ops/s | **8x** 🚀 | | **64KB input** | **1,307 ops/s** | 14 ops/s | **93x** 🚀 | | **Streaming** | **39,208 ops/s** | 938 ops/s | **41x** 🚀 | ## 2. Encryption (AES-GCM / Salsa20) Cipher Benchmark Native encryption prevents frame drops. `AES-256-GCM` is over **100x faster** for large buffers. | Operation | RNQC | JS / Browserify | Speedup | | :-------------------- | :------------ | :---------------------- | :---------- | | **XSalsa20 (64KB)** | **852 ops/s** | 7.39 ops/s | **115x** 🚀 | | **AES-256-GCM (1MB)** | **177 ops/s** | 0.18 ops/s (browserify) | **962x** 🚀 | | **AES-256-GCM (1MB)** | **177 ops/s** | 1.32 ops/s (@noble) | **134x** 🚀 | ## 3. Key Derivation (PBKDF2) PBKDF2 Benchmark Password hashing is computationally expensive. Running this on the JS thread freezes the UI. | Operation | RNQC | @noble/hashes | Speedup | | :----------------- | :--------------- | :------------ | :--------- | | **PBKDF2 (async)** | **52,948 ops/s** | 1,447 ops/s | **36x** 🚀 | | **PBKDF2 (sync)** | **76,601 ops/s** | 1,459 ops/s | **52x** 🚀 | ## 4. Signatures (Ed25519) Ed25519 Benchmark Essential for crypto wallets. Verify signatures instantly without lag. | Operation | RNQC | @noble/curves | Speedup | | :---------------------- | :--------------- | :------------ | :---------- | | **Sign/Verify (async)** | **9,917 ops/s** | 60 ops/s | **164x** 🚀 | | **Sign/Verify (sync)** | **17,108 ops/s** | 60 ops/s | **285x** 🚀 | ## 5. HKDF HKDF Benchmark Standard key derivation is **over 100x faster**. | Operation | RNQC | @noble/hashes | Speedup | | :--------------- | :--------------- | :------------ | :---------- | | **HKDF (async)** | **65,792 ops/s** | 582 ops/s | **112x** 🚀 | | **HKDF (sync)** | **70,918 ops/s** | 572 ops/s | **123x** 🚀 | ## 6. Scrypt (Memory Hard) Scrypt is a memory-hard key derivation function, making it incredibly slow in pure JavaScript. Native implementation provides massive gains. | Operation | RNQC | @noble/hashes | Speedup | | :----------------- | :-------------- | :------------ | :---------- | | **scrypt (async)** | **1,791 ops/s** | 2.98 ops/s | **601x** 🚀 | | **scrypt (sync)** | **2,160 ops/s** | 3.10 ops/s | **696x** 🚀 | # Complete Setup Process (/docs/introduction/complete-setup) This guide covers the full installation process, including how to configure RNQC as a drop-in replacement for the Node.js crypto module. ## Installation Install the package and the required `react-native-nitro-modules` core. ### Install Dependencies ```bash bun add react-native-quick-crypto react-native-nitro-modules ``` ### Install Pods (iOS) Navigate to the iOS directory and install CocoaPods. ```bash cd ios && pod install ``` For Expo projects, use the Expo CLI to ensure compatibility. ### Install Dependencies ```bash npx expo install react-native-quick-crypto ``` ### Add Config Plugin Add the library to your `plugins` list in `app.json` (or `app.config.js`). ```json title="app.json" { "expo": { "plugins": [ "react-native-quick-crypto" ] } } ``` ### Prebuild Generate the native android and ios directories. ```bash npx expo prebuild ``` This library requires native code. You **must** run `prebuild` to generate the native directories. It will not work in Expo Go. ## Global Polyfill (Recommended) If you want `crypto` and `Buffer` to be available globally (as they are in Node.js), import the `install` function as early as possible in your application's entry point (e.g., `index.js`). ```ts install(); ``` ## Configuration Use `metro.config.js` to redirect imports at the bundler level. This is generally more robust. ### Open Config Open your `metro.config.js` file. ### Add Resolver Add the `resolveRequest` handler to redirect `crypto` imports. ```js title="metro.config.js" const { getDefaultConfig } = require('@react-native/metro-config'); const config = getDefaultConfig(__dirname); config.resolver.resolveRequest = (context, moduleName, platform) => { if (moduleName === 'crypto') { // when importing crypto, resolve to react-native-quick-crypto return context.resolveRequest( context, 'react-native-quick-crypto', platform, ); } // otherwise chain to the standard Metro resolver. return context.resolveRequest(context, moduleName, platform); }; module.exports = config; ``` Alternatively, use `babel-plugin-module-resolver` to alias imports during transpilation. ### Install Plugin ```bash yarn add --dev babel-plugin-module-resolver ``` ### Update Babel Config Add the plugin to your `babel.config.js`. ```diff title="babel.config.js" module.exports = { presets: ['module:metro-react-native-babel-preset'], plugins: [ + [ + 'module-resolver', + { + alias: { + 'crypto': 'react-native-quick-crypto', + 'stream': 'readable-stream', + 'buffer': 'react-native-quick-crypto', + }, + }, + ], ... ], }; ``` `react-native-quick-crypto` re-exports `Buffer` from `@craftzdog/react-native-buffer`, so you can use either as the buffer alias. Using `react-native-quick-crypto` ensures a single Buffer instance across your app. ### Reset Cache Restart your bundler to apply changes. ```bash yarn start --reset-cache ``` ## XSalsa20 Support (Optional) If you need to use the `xsalsa20` cipher algorithm, you must enable libsodium support by setting an environment variable **before building your native code**: ```bash ``` This can be done: * **iOS**: Export the variable `export SODIUM_ENABLED=1` before running `pod install`. * **Android**: Add `sodiumEnabled=true` to your project's `gradle.properties` file. Without this flag, attempting to use `xsalsa20` will throw a runtime error: `"libsodium must be enabled to use this cipher"` # Implementation Coverage (/docs/introduction/coverage) This page tracks the implementation status of `react-native-quick-crypto` against the standard Node.js `crypto` API and the W3C WebCrypto API.
Implemented : Full support
Partial : Not fully implemented
Missing : Not implemented
# LLM-Friendly Docs (/docs/introduction/llms) RNQC publishes two machine-readable endpoints following the [`llms.txt` standard](https://llmstxt.org): | Endpoint | Description | | ---------------------------------- | ------------------------------------------------------------ | | [`/llms.txt`](/llms.txt) | Index of all documentation pages with one-line descriptions | | [`/llms-full.txt`](/llms-full.txt) | Full text of every page, concatenated into a single document | Full URLs: * * ## Usage Point any LLM tool that supports `llms.txt` at your site root and it will discover these endpoints automatically. For example, with [Context7](https://context7.com) or similar services, the library can be referenced by name and the documentation is fetched on demand. ### Claude Code (CLAUDE.md) Add the full-text endpoint to your `CLAUDE.md`: * ### Cursor / Windsurf Add the site URL as a documentation source in your editor's AI settings. The `/llms.txt` index tells the tool which pages are available, and `/llms-full.txt` provides the full content in one request. # Quick Start (/docs/introduction/quick-start) ## Introduction **React Native Quick Crypto** (also known as **RNQC**) is the fastest, next-generation cryptography library for React Native. It is designed to be a drop-in replacement for all other React Native crypto libraries and is inspired by the Node.js `crypto` module, powered by a high-performance C++ JSI binding that executes directly on the native thread. } href="/docs/introduction/comparison" title="High Performance" description="Up to 100x faster than standard native bridges using JSI." /> } href="/docs/introduction/comparison" title="Node.js Compatible" description="A complete implementation of the standard Node.js crypto API." /> } href="/docs/introduction/what-is-rnqc" title="Secure by Design" description="Operations run on a dedicated thread pool to keep the UI smooth." /> } href="/docs/guides/nitro-integration" title="Nitro Powered" description="Built on Nitro Modules for zero-overhead native communication." /> Want to understand the architecture? Read our in-depth guide: [What is RNQC?](/docs/introduction/what-is-rnqc) ## Terminology Before we proceed, a few key concepts: * **JSI (JavaScript Interface)**: A direct C++ interface to the JS runtime, bypassing the slow React Native Bridge. * **Native Thread**: The thread where C++ code executes. We offload heavy crypto work here to prevent UI freezes. * **Polyfill**: We provide a seamless global `crypto` object, just like in a browser or Node.js environment. ### Architecture At a Glance RNQC is unique because it uses a **Hybrid Object** model. It lives on both the Javascript thread and the Native thread simultaneously. *** ## Automatic Installation Follow these steps to integrate Quick Crypto into your React Native project. ### Install the Package Add the dependency using your preferred package manager. ```bash npm install react-native-quick-crypto react-native-nitro-modules react-native-quick-base64 ``` ```bash yarn add react-native-quick-crypto react-native-nitro-modules react-native-quick-base64 ``` ```bash pnpm add react-native-quick-crypto react-native-nitro-modules react-native-quick-base64 ``` ```bash bun add react-native-quick-crypto react-native-nitro-modules react-native-quick-base64 ``` ### iOS Setup (Cocoapods) If you are developing for iOS, navigate to your `ios` directory and install the pods. ```bash cd ios && pod install ``` ### Rebuild the App Since this library includes native C++ code, you must rebuild your native binary. ```bash npx react-native run-android ``` ```bash npx react-native run-ios ``` Don't forget: You **cannot** use this library with Expo Go. You must use a Development Build or straight React Native. ## Create your first cryptographic operation Once installed, usage is straightforward. The API mirrors Node.js exactly. ```tsx title="App.tsx" // Polyfill global.crypto for full compatibility QuickCrypto.install(); export default function App() { useEffect(() => { // Standard Node.js API (SHA-256) const hash = QuickCrypto.createHash('sha256') .update('Hello World') .digest('hex'); console.log('SHA-256:', hash); // High-Performance Random Bytes (Native JSI) const randomBuffer = QuickCrypto.randomBytes(16); console.log('Random:', randomBuffer.toString('hex')); // Next-Gen Algorithms (BLAKE3) const b3Hash = QuickCrypto.blake3('Fastest Hash', { dkLen: 32 }); console.log('BLAKE3:', Buffer.from(b3Hash).toString('hex')); }, []); return null; } ``` React Native does not provide a native `crypto` implementation or a global `Buffer` environment. Calling `QuickCrypto.install()` injects our high-performance C++ bindings into `global.crypto` and `global.Buffer`. This is essential because many popular libraries (like `ethers`, `jsonwebtoken`, or `viem`) rely on these Node.js standard APIs to function correctly. ## For Expo Users Quick Crypto includes a **Config Plugin** for Expo. Add the plugin to your `app.json` or `app.config.js`: ```json title="app.json" { "expo": { "plugins": [ ["react-native-quick-crypto", { "sodiumEnabled": true }] // Optional configuration ] } } ``` Then rebuild your development client: ```bash npx expo prebuild npx expo run:ios ``` *** ## FAQ
Does this work with Expo Go?
No. Expo Go does not support custom native modules. You must use a **Custom Development Client** (CNG) or `npx expo prebuild`.
Is it faster than `react-native-crypto`?
Yes, significantly. We use direct C++ JSI bindings instead of the React Native Bridge, which eliminates serialization overhead.
*** ## Learn More New here? Don't worry, we welcome your questions. If you find anything confusing or have suggestions, please give your feedback on our **GitHub Discussions**. [Join the Discussion](https://github.com/margelo/react-native-quick-crypto/discussions) # Versions & Releases (/docs/introduction/releases) Stay up to date with the latest changes in RNQC. ## Compatibility Matrix | Version | RN Architecture | Modules | | ------- | ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | | `1.x` | **New Architecture** [->](https://github.com/reactwg/react-native-new-architecture/blob/main/docs/enable-apps.md) | **Nitro Modules** [->](https://github.com/mrousavy/nitro) | | `0.x` | Old (Bridge) & New (Interop) | Bridge & JSI | > \[!NOTE] > **Version 1.x** is a major refactor (Nitro Modules). It requires **React Native 0.75+**. > If you need to support earlier versions, please continue using `0.x`. # What is RNQC? (/docs/introduction/what-is-rnqc) **RNQC** (also known as React Native Quick Crypto) is a cryptography library for React Native compatible with Node's `crypto` API, differing from other libraries by being a complete re-implementation rather than just a fast wrapper. Developed by [Margelo](https://margelo.com), it is built entirely on C++ JSI bindings to provide direct, synchronous access to native cryptographic primitives. ## Why was it created? React Native has historically lacked a robust, high-performance cryptographic standard. Developers largely relied on slower JS-only polyfills or incomplete wrappers. **RNQC was created because complex apps need a FULL, INDEPENDENT cryptographic standard.** We wanted a complete, standalone cryptographic library for React Native where you can simply call `install()` to polyfill the environment, ensuring all your cryptographic operations run smoothly and **hundreds of times faster** than standard JS solutions. This library was built at **Margelo**, an elite app development agency, specifically to power high-performance crypto apps and wallets. *** ## Philosophies ### 1. Seamless Integration We believe improved performance shouldn't require code rewrites. RNQC implements the **exact** Node.js API surface. If you know Node `crypto`, you know RNQC. There is zero learning curve. ### 2. Performance First (Nitro Modules) We bypass the "Bridge". All operations communicate directly with C++ via **Nitro Modules**, a modern architecture built on top of JSI (JavaScript Interface). Heavy tasks (like `pbkdf2` or key generation) are offloaded to a dedicated native thread pool to ensure your UI **never freezes**, even during intensive encryption. ### 3. Secure Defaults We don't roll our own crypto. All primitives map directly to the platform-native, battle-tested **OpenSSL** library: * **Android**: OpenSSL (bundled or system) * **iOS**: OpenSSL (via CocoaPods) ## Architecture RNQC bridges the gap between the JavaScript world and native C++ performance using **Nitro Modules**. ### Nitro Modules Architecture Unlike the legacy Bridge, which relies on asynchronous JSON message passing (slow, serializable-only), **Nitro Modules** provides a modern architecture built on JSI (JavaScript Interface), allowing C++ code to expose "Hybrid Objects" directly to the JavaScript runtime. 1. **Zero Serialization**: Data (like `ArrayBuffer`) is shared by reference. No cloning large buffers. 2. **Synchronous Execution**: Methods like `randomBytes` or `update` run instantly, just like native JS functions. 3. **Thread Safety**: Heavy operations automatically offload to a C++ thread pool to prevent UI jank. *** ## When to use RNQC? | Scenario | Use RNQC? | Why? | | :------------------------ | :----------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Web3 / Blockchain** | | Essential for wallet generation, signing transactions, and hashing (Keccak/SHA) securely and quickly. | | **Auth (JWT/Jose)** | | Drastically speeds up token verification and signing compared to JS-only implementations. | | **End-to-End Encryption** | | Using standard algorithms (AES-GCM, RSA) on the native thread prevents UI jank. | | **Simple Random Strings** | | If you only need random numbers, consider [`react-native-get-random-values`](https://github.com/LinusU/react-native-get-random-values) for a lighter alternative. Use RNQC if you need other crypto operations too. | | **UI-Only Apps** | | If your app has zero security or hashing requirements, you probably don't need this. |