Low level bindings for libsodium cryptographic library
—
Diffie-Hellman key exchange using X25519 elliptic curve cryptography for establishing shared secrets between parties.
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);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;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));// 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;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());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 };
}
}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