CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-sodium-native

Low level bindings for libsodium cryptographic library

Pending
Overview
Eval results
Files

kx.mddocs/

Key Exchange

Diffie-Hellman key exchange using X25519 elliptic curve cryptography for establishing shared secrets between parties.

Capabilities

Key Pair Generation

Generate X25519 key pairs for key exchange operations.

/**
 * Generate random X25519 key pair for key exchange
 * @param pk - Output buffer for public key (must be PUBLICKEYBYTES long)
 * @param sk - Output buffer for secret key (must be SECRETKEYBYTES long)
 * @throws Error if buffer sizes are incorrect or generation fails
 */
function crypto_kx_keypair(pk: Buffer, sk: Buffer): void;

/**
 * Generate X25519 key pair from seed
 * @param pk - Output buffer for public key (must be PUBLICKEYBYTES long)
 * @param sk - Output buffer for secret key (must be SECRETKEYBYTES long)
 * @param seed - Seed buffer (must be SEEDBYTES long)
 * @throws Error if buffer sizes are incorrect or generation fails
 */
function crypto_kx_seed_keypair(pk: Buffer, sk: Buffer, seed: Buffer): void;

Usage Example:

const sodium = require('sodium-native');

// Generate key pairs for Alice and Bob
const alicePk = Buffer.alloc(sodium.crypto_kx_PUBLICKEYBYTES);
const aliceSk = Buffer.alloc(sodium.crypto_kx_SECRETKEYBYTES);
sodium.crypto_kx_keypair(alicePk, aliceSk);

const bobPk = Buffer.alloc(sodium.crypto_kx_PUBLICKEYBYTES);
const bobSk = Buffer.alloc(sodium.crypto_kx_SECRETKEYBYTES);
sodium.crypto_kx_keypair(bobPk, bobSk);

Client Session Key Derivation

Derive session keys from the client's perspective.

/**
 * Derive client session keys for secure communication
 * @param rx - Output buffer for receiving key (can be null if not needed)
 * @param tx - Output buffer for transmitting key (can be null if not needed)
 * @param clientPk - Client's public key (must be PUBLICKEYBYTES long)
 * @param clientSk - Client's secret key (must be SECRETKEYBYTES long)
 * @param serverPk - Server's public key (must be PUBLICKEYBYTES long)
 * @throws Error if both rx and tx are null, or if key derivation fails
 */
function crypto_kx_client_session_keys(
  rx: Buffer | null,
  tx: Buffer | null,
  clientPk: Buffer,
  clientSk: Buffer,
  serverPk: Buffer
): void;

Server Session Key Derivation

Derive session keys from the server's perspective.

/**
 * Derive server session keys for secure communication
 * @param rx - Output buffer for receiving key (can be null if not needed)
 * @param tx - Output buffer for transmitting key (can be null if not needed)
 * @param serverPk - Server's public key (must be PUBLICKEYBYTES long)
 * @param serverSk - Server's secret key (must be SECRETKEYBYTES long)
 * @param clientPk - Client's public key (must be PUBLICKEYBYTES long)
 * @throws Error if both rx and tx are null, or if key derivation fails
 */
function crypto_kx_server_session_keys(
  rx: Buffer | null,
  tx: Buffer | null,
  serverPk: Buffer,
  serverSk: Buffer,
  clientPk: Buffer
): void;

Usage Example:

const sodium = require('sodium-native');

// After key pair generation...

// Client derives session keys
const clientRx = Buffer.alloc(sodium.crypto_kx_SESSIONKEYBYTES);
const clientTx = Buffer.alloc(sodium.crypto_kx_SESSIONKEYBYTES);

sodium.crypto_kx_client_session_keys(
  clientRx,    // Key for receiving data from server
  clientTx,    // Key for transmitting data to server
  alicePk,     // Client's public key
  aliceSk,     // Client's secret key
  bobPk        // Server's public key
);

// Server derives session keys
const serverRx = Buffer.alloc(sodium.crypto_kx_SESSIONKEYBYTES);
const serverTx = Buffer.alloc(sodium.crypto_kx_SESSIONKEYBYTES);

sodium.crypto_kx_server_session_keys(
  serverRx,    // Key for receiving data from client
  serverTx,    // Key for transmitting data to client
  bobPk,       // Server's public key
  bobSk,       // Server's secret key
  alicePk      // Client's public key
);

// Now clientTx === serverRx and serverTx === clientRx
console.log('Keys match:', clientTx.equals(serverRx) && serverTx.equals(clientRx));

Constants

// Public key size in bytes
const crypto_kx_PUBLICKEYBYTES: number;

// Secret key size in bytes
const crypto_kx_SECRETKEYBYTES: number;

// Seed size for deterministic key generation
const crypto_kx_SEEDBYTES: number;

// Session key size in bytes
const crypto_kx_SESSIONKEYBYTES: number;

Security Considerations

  • Key Reuse: X25519 key pairs can be reused for multiple key exchanges with different parties.
  • Forward Secrecy: For forward secrecy, generate ephemeral key pairs for each session.
  • Key Validation: Always validate public keys received from other parties.
  • Session Keys: Use derived session keys immediately and securely delete them after use.

Common Patterns

Secure Communication Protocol

const sodium = require('sodium-native');

class SecureChannel {
  constructor(role = 'client') {
    this.role = role;
    
    // Generate long-term identity key pair
    this.identityPk = Buffer.alloc(sodium.crypto_kx_PUBLICKEYBYTES);
    this.identitySk = Buffer.alloc(sodium.crypto_kx_SECRETKEYBYTES);
    sodium.crypto_kx_keypair(this.identityPk, this.identitySk);
    
    this.sessionKeys = null;
  }
  
  // Generate ephemeral key pair for this session
  generateEphemeralKeys() {
    this.ephemeralPk = Buffer.alloc(sodium.crypto_kx_PUBLICKEYBYTES);
    this.ephemeralSk = Buffer.alloc(sodium.crypto_kx_SECRETKEYBYTES);
    sodium.crypto_kx_keypair(this.ephemeralPk, this.ephemeralSk);
    
    return this.ephemeralPk;
  }
  
  // Establish secure session with peer
  establishSession(peerPublicKey) {
    if (!this.ephemeralPk || !this.ephemeralSk) {
      throw new Error('Must generate ephemeral keys first');
    }
    
    const rx = Buffer.alloc(sodium.crypto_kx_SESSIONKEYBYTES);
    const tx = Buffer.alloc(sodium.crypto_kx_SESSIONKEYBYTES);
    
    if (this.role === 'client') {
      sodium.crypto_kx_client_session_keys(
        rx, tx,
        this.ephemeralPk,
        this.ephemeralSk,
        peerPublicKey
      );
    } else {
      sodium.crypto_kx_server_session_keys(
        rx, tx,
        this.ephemeralPk,
        this.ephemeralSk,
        peerPublicKey
      );
    }
    
    this.sessionKeys = { rx, tx };
    
    // Clean up ephemeral secret key
    sodium.sodium_memzero(this.ephemeralSk);
    
    return this.sessionKeys;
  }
  
  // Encrypt message using session key
  encrypt(message) {
    if (!this.sessionKeys) {
      throw new Error('Session not established');
    }
    
    const nonce = Buffer.alloc(sodium.crypto_secretbox_NONCEBYTES);
    sodium.randombytes_buf(nonce);
    
    const ciphertext = Buffer.alloc(message.length + sodium.crypto_secretbox_MACBYTES);
    sodium.crypto_secretbox_easy(ciphertext, message, nonce, this.sessionKeys.tx);
    
    return { ciphertext, nonce };
  }
  
  // Decrypt message using session key
  decrypt(ciphertext, nonce) {
    if (!this.sessionKeys) {
      throw new Error('Session not established');
    }
    
    const plaintext = Buffer.alloc(ciphertext.length - sodium.crypto_secretbox_MACBYTES);
    
    if (sodium.crypto_secretbox_open_easy(plaintext, ciphertext, nonce, this.sessionKeys.rx)) {
      return plaintext;
    }
    
    return null;
  }
}

// Usage
const client = new SecureChannel('client');
const server = new SecureChannel('server');

// Key exchange
const clientEphemeralPk = client.generateEphemeralKeys();
const serverEphemeralPk = server.generateEphemeralKeys();

client.establishSession(serverEphemeralPk);
server.establishSession(clientEphemeralPk);

// Secure communication
const message = Buffer.from('Hello from client!');
const encrypted = client.encrypt(message);
const decrypted = server.decrypt(encrypted.ciphertext, encrypted.nonce);

console.log('Decrypted:', decrypted.toString());

Multi-party Key Exchange

const sodium = require('sodium-native');

class MultiPartyKeyExchange {
  constructor(participantId) {
    this.participantId = participantId;
    this.publicKey = Buffer.alloc(sodium.crypto_kx_PUBLICKEYBYTES);
    this.secretKey = Buffer.alloc(sodium.crypto_kx_SECRETKEYBYTES);
    sodium.crypto_kx_keypair(this.publicKey, this.secretKey);
    
    this.sharedKeys = new Map();
  }
  
  // Establish pairwise shared keys with other participants
  establishPairwiseKeys(participants) {
    for (const [id, publicKey] of participants) {
      if (id === this.participantId) continue;
      
      const sharedKey = Buffer.alloc(sodium.crypto_kx_SESSIONKEYBYTES);
      
      // Use consistent role assignment based on ID comparison
      if (this.participantId < id) {
        // Act as client
        sodium.crypto_kx_client_session_keys(
          sharedKey, null,
          this.publicKey,
          this.secretKey,
          publicKey
        );
      } else {
        // Act as server
        sodium.crypto_kx_server_session_keys(
          sharedKey, null,
          this.publicKey,
          this.secretKey,
          publicKey
        );
      }
      
      this.sharedKeys.set(id, sharedKey);
    }
  }
  
  // Get shared key with specific participant
  getSharedKey(participantId) {
    return this.sharedKeys.get(participantId);
  }
  
  // Encrypt message for specific participant
  encryptFor(participantId, message) {
    const sharedKey = this.sharedKeys.get(participantId);
    if (!sharedKey) {
      throw new Error(`No shared key with participant ${participantId}`);
    }
    
    const nonce = Buffer.alloc(sodium.crypto_secretbox_NONCEBYTES);
    sodium.randombytes_buf(nonce);
    
    const ciphertext = Buffer.alloc(message.length + sodium.crypto_secretbox_MACBYTES);
    sodium.crypto_secretbox_easy(ciphertext, message, nonce, sharedKey);
    
    return { ciphertext, nonce };
  }
}

Perfect Forward Secrecy Protocol

const sodium = require('sodium-native');

class PFSProtocol {
  constructor() {
    // Long-term identity keys
    this.identityPk = Buffer.alloc(sodium.crypto_kx_PUBLICKEYBYTES);
    this.identitySk = Buffer.alloc(sodium.crypto_kx_SECRETKEYBYTES);
    sodium.crypto_kx_keypair(this.identityPk, this.identitySk);
    
    this.sessions = new Map();
  }
  
  // Create new session with forward secrecy
  createSession(sessionId, peerIdentityPk) {
    // Generate ephemeral keys for this session
    const ephemeralPk = Buffer.alloc(sodium.crypto_kx_PUBLICKEYBYTES);
    const ephemeralSk = Buffer.alloc(sodium.crypto_kx_SECRETKEYBYTES);
    sodium.crypto_kx_keypair(ephemeralPk, ephemeralSk);
    
    const session = {
      ephemeralPk,
      ephemeralSk,
      peerIdentityPk,
      peerEphemeralPk: null,
      sessionKeys: null,
      messageCounter: 0
    };
    
    this.sessions.set(sessionId, session);
    return ephemeralPk;
  }
  
  // Complete session establishment
  completeSession(sessionId, peerEphemeralPk) {
    const session = this.sessions.get(sessionId);
    if (!session) {
      throw new Error('Session not found');
    }
    
    session.peerEphemeralPk = peerEphemeralPk;
    
    // Derive session keys using ephemeral keys
    const rx = Buffer.alloc(sodium.crypto_kx_SESSIONKEYBYTES);
    const tx = Buffer.alloc(sodium.crypto_kx_SESSIONKEYBYTES);
    
    sodium.crypto_kx_client_session_keys(
      rx, tx,
      session.ephemeralPk,
      session.ephemeralSk,
      peerEphemeralPk
    );
    
    session.sessionKeys = { rx, tx };
    
    // Immediately destroy ephemeral secret keys
    sodium.sodium_memzero(session.ephemeralSk);
  }
  
  // Destroy session and all keys
  destroySession(sessionId) {
    const session = this.sessions.get(sessionId);
    if (session) {
      if (session.sessionKeys) {
        sodium.sodium_memzero(session.sessionKeys.rx);
        sodium.sodium_memzero(session.sessionKeys.tx);
      }
      this.sessions.delete(sessionId);
    }
  }
}

Install with Tessl CLI

npx tessl i tessl/npm-sodium-native

docs

aead.md

auth.md

box.md

ed25519.md

hash.md

index.md

kdf.md

kx.md

memory.md

pwhash.md

random.md

secretbox.md

secretstream.md

shorthash.md

sign.md

stream.md

tile.json