React Native Quick Crypto
API Reference

ECDH (Elliptic Curve Diffie-Hellman)

Efficient key exchange with elliptic curves

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.

Why ECDH?

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

FeatureECDHTraditional DH
Key Size (128-bit security)256 bits (32 bytes)3072 bits (384 bytes)
Public Key Size32-65 bytes384 bytes
Key Generation~5ms~80ms
Secret Computation~2ms~15ms
Performance10-100× fasterSlower
BandwidthMinimalHigh
Battery ImpactLowerHigher
Mobile SuitabilityExcellentPoor

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.

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:

import { createECDH } from 'react-native-quick-crypto';

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:

NameTypeDescription
otherPublicKeystring | BufferThe other party's public key
inputEncodingstringEncoding 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:

import { createECDH } from 'react-native-quick-crypto';

// 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:

NameTypeDescription
encodingstringOptional encoding: 'hex', 'base64', or 'base64url'

Returns: Buffer or string

Examples:

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:

NameTypeDescription
publicKeystring | BufferThe public key to set
encodingstringEncoding 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:

NameTypeDescription
privateKeystring | BufferThe private key to set
encodingstringEncoding of privateKey if it's a string

Module Methods

createECDH(curveName)

Creates an ECDH instance using the specified elliptic curve.

Parameters:

NameTypeDescription
curveNamestringName of the elliptic curve (see Supported Curves)

Returns: ECDH instance

Examples:

import { createECDH } from 'react-native-quick-crypto';

// 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

Curve NameAlso Known AsKey SizeSecurity LevelUse Case
prime256v1P-256, secp256r1256-bit~128-bitGeneral purpose, TLS, most APIs
secp384r1P-384384-bit~192-bitHigh security applications
secp521r1P-521521-bit~256-bitMaximum security

Koblitz Curves

Curve NameKey SizeSecurity LevelUse Case
secp256k1256-bit~128-bitBitcoin, 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
import { createECDH } from 'react-native-quick-crypto';

// ✅ 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:

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<void> {
    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:

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<string, Buffer>(); // 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:

import {
  createECDH,
  createHash,
  randomBytes,
  createCipheriv,
  createDecipheriv
} from 'react-native-quick-crypto';
import RNFS from 'react-native-fs';

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<void> {
    // 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:

import { createECDH, createHash } from 'react-native-quick-crypto';

function deriveMultipleKeys(
  sharedSecret: Buffer,
  ...purposes: string[]
): Map<string, Buffer> {
  const keys = new Map<string, Buffer>();

  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

Critical Security Rules

  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:

// ✅ 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:

// ✅ 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:

// ✅ 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:

// ✅ 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:

// ❌ 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
// ✅ 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.

// ❌ 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
// 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);
}

On this page