Low level bindings for libsodium cryptographic library
—
Streaming authenticated encryption using XChaCha20-Poly1305 for encrypting sequences of messages with automatic key rotation and message ordering.
Generate keys for secret stream operations.
/**
* Generate random key for secret stream encryption
* @param k - Output buffer for key (must be KEYBYTES long)
*/
function crypto_secretstream_xchacha20poly1305_keygen(k: Buffer): void;Usage Example:
const sodium = require('sodium-native');
// Generate key for secret stream
const key = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES);
sodium.crypto_secretstream_xchacha20poly1305_keygen(key);Initialize a secret stream for encryption (push mode).
/**
* Initialize secret stream for encryption
* @param state - Output buffer for stream state (must be STATEBYTES long)
* @param header - Output buffer for stream header (must be HEADERBYTES long)
* @param k - Key buffer (must be KEYBYTES long)
* @throws Error if initialization fails or buffer sizes incorrect
*/
function crypto_secretstream_xchacha20poly1305_init_push(
state: Buffer,
header: Buffer,
k: Buffer
): void;Initialize a secret stream for decryption (pull mode).
/**
* Initialize secret stream for decryption
* @param state - Output buffer for stream state (must be STATEBYTES long)
* @param header - Stream header from push initialization (must be HEADERBYTES long)
* @param k - Key buffer (must be KEYBYTES long)
* @throws Error if initialization fails or buffer sizes incorrect
*/
function crypto_secretstream_xchacha20poly1305_init_pull(
state: Buffer,
header: Buffer,
k: Buffer
): void;Usage Example:
const sodium = require('sodium-native');
// Initialize encryption stream
const pushState = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_STATEBYTES);
const header = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES);
sodium.crypto_secretstream_xchacha20poly1305_init_push(pushState, header, key);
// Initialize decryption stream
const pullState = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_STATEBYTES);
sodium.crypto_secretstream_xchacha20poly1305_init_pull(pullState, header, key);Encrypt and authenticate a message chunk in the stream.
/**
* Encrypt message chunk in secret stream
* @param state - Stream state buffer from init_push
* @param c - Output buffer for ciphertext (must be m.length + ABYTES)
* @param m - Message buffer to encrypt
* @param ad - Optional additional data buffer (can be null)
* @param tag - Message tag indicating message type/position
* @returns Number of bytes written to ciphertext buffer
* @throws Error if encryption fails
*/
function crypto_secretstream_xchacha20poly1305_push(
state: Buffer,
c: Buffer,
m: Buffer,
ad: Buffer | null,
tag: number
): number;Decrypt and verify a message chunk from the stream.
/**
* Decrypt and verify message chunk from secret stream
* @param state - Stream state buffer from init_pull
* @param m - Output buffer for plaintext (must be c.length - ABYTES)
* @param tag - Output buffer for message tag (must be TAGBYTES long)
* @param c - Ciphertext buffer to decrypt
* @param ad - Optional additional data buffer (can be null)
* @returns Number of bytes written to plaintext buffer
* @throws Error if decryption or verification fails
*/
function crypto_secretstream_xchacha20poly1305_pull(
state: Buffer,
m: Buffer,
tag: Buffer,
c: Buffer,
ad: Buffer | null
): number;Usage Example:
const sodium = require('sodium-native');
// Encrypt message chunks
const message1 = Buffer.from('First message');
const message2 = Buffer.from('Second message');
const message3 = Buffer.from('Final message');
// Encrypt first message
const ciphertext1 = Buffer.alloc(message1.length + sodium.crypto_secretstream_xchacha20poly1305_ABYTES);
const len1 = sodium.crypto_secretstream_xchacha20poly1305_push(
pushState,
ciphertext1,
message1,
null,
sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE
);
// Encrypt second message
const ciphertext2 = Buffer.alloc(message2.length + sodium.crypto_secretstream_xchacha20poly1305_ABYTES);
const len2 = sodium.crypto_secretstream_xchacha20poly1305_push(
pushState,
ciphertext2,
message2,
null,
sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE
);
// Encrypt final message
const ciphertext3 = Buffer.alloc(message3.length + sodium.crypto_secretstream_xchacha20poly1305_ABYTES);
const len3 = sodium.crypto_secretstream_xchacha20poly1305_push(
pushState,
ciphertext3,
message3,
null,
sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
);
// Decrypt message chunks
const plaintext1 = Buffer.alloc(ciphertext1.length - sodium.crypto_secretstream_xchacha20poly1305_ABYTES);
const tag1 = Buffer.alloc(1);
sodium.crypto_secretstream_xchacha20poly1305_pull(pullState, plaintext1, tag1, ciphertext1, null);
console.log('Decrypted:', plaintext1.toString());
console.log('Tag:', tag1[0]); // Should be TAG_MESSAGERotate the stream key to provide forward secrecy.
/**
* Rotate stream key for forward secrecy
* @param state - Stream state buffer to rekey
*/
function crypto_secretstream_xchacha20poly1305_rekey(state: Buffer): void;Usage Example:
const sodium = require('sodium-native');
// Rotate key after processing sensitive data
sodium.crypto_secretstream_xchacha20poly1305_rekey(pushState);
// Messages encrypted after rekey cannot be decrypted with old state
const sensitiveMessage = Buffer.from('Top secret data');
const encryptedSensitive = Buffer.alloc(sensitiveMessage.length + sodium.crypto_secretstream_xchacha20poly1305_ABYTES);
sodium.crypto_secretstream_xchacha20poly1305_push(
pushState,
encryptedSensitive,
sensitiveMessage,
null,
sodium.crypto_secretstream_xchacha20poly1305_TAG_REKEY
);// State buffer size in bytes
const crypto_secretstream_xchacha20poly1305_STATEBYTES: number;
// Authentication tag size in bytes
const crypto_secretstream_xchacha20poly1305_ABYTES: number;
// Stream header size in bytes
const crypto_secretstream_xchacha20poly1305_HEADERBYTES: number;
// Key size in bytes
const crypto_secretstream_xchacha20poly1305_KEYBYTES: number;
// Message tag size in bytes
const crypto_secretstream_xchacha20poly1305_TAGBYTES: number;
// Maximum message size in bytes
const crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX: number;
// Message tags for different message types
const crypto_secretstream_xchacha20poly1305_TAG_MESSAGE: number;
const crypto_secretstream_xchacha20poly1305_TAG_PUSH: number;
const crypto_secretstream_xchacha20poly1305_TAG_REKEY: number;
const crypto_secretstream_xchacha20poly1305_TAG_FINAL: number;Secret streams use tags to indicate message types and stream state:
rekey() function to prevent decryption of future messages if state is compromised.const sodium = require('sodium-native');
const fs = require('fs');
class SecureFileStream {
constructor(key) {
this.key = key || Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES);
if (!key) {
sodium.crypto_secretstream_xchacha20poly1305_keygen(this.key);
}
}
encryptFile(inputFile, outputFile) {
const state = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_STATEBYTES);
const header = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES);
sodium.crypto_secretstream_xchacha20poly1305_init_push(state, header, this.key);
const input = fs.createReadStream(inputFile, { highWaterMark: 4096 });
const output = fs.createWriteStream(outputFile);
// Write header first
output.write(header);
let isFirstChunk = true;
let isLastChunk = false;
input.on('data', (chunk) => {
const ciphertext = Buffer.alloc(chunk.length + sodium.crypto_secretstream_xchacha20poly1305_ABYTES);
// Determine tag based on position
let tag = sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
if (isFirstChunk) {
tag = sodium.crypto_secretstream_xchacha20poly1305_TAG_PUSH;
isFirstChunk = false;
}
const len = sodium.crypto_secretstream_xchacha20poly1305_push(
state, ciphertext, chunk, null, tag
);
output.write(ciphertext.subarray(0, len));
});
input.on('end', () => {
// Write final empty message with TAG_FINAL
const finalCiphertext = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_ABYTES);
const len = sodium.crypto_secretstream_xchacha20poly1305_push(
state, finalCiphertext, Buffer.alloc(0), null,
sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
);
output.write(finalCiphertext.subarray(0, len));
output.end();
});
}
decryptFile(inputFile, outputFile) {
const encryptedData = fs.readFileSync(inputFile);
const header = encryptedData.subarray(0, sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES);
const state = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_STATEBYTES);
sodium.crypto_secretstream_xchacha20poly1305_init_pull(state, header, this.key);
const output = fs.createWriteStream(outputFile);
let offset = sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES;
while (offset < encryptedData.length) {
// Read chunk length (simplified - real implementation needs proper framing)
const chunkSize = Math.min(4096 + sodium.crypto_secretstream_xchacha20poly1305_ABYTES,
encryptedData.length - offset);
const ciphertext = encryptedData.subarray(offset, offset + chunkSize);
const plaintext = Buffer.alloc(ciphertext.length - sodium.crypto_secretstream_xchacha20poly1305_ABYTES);
const tag = Buffer.alloc(1);
const len = sodium.crypto_secretstream_xchacha20poly1305_pull(
state, plaintext, tag, ciphertext, null
);
if (len > 0) {
output.write(plaintext.subarray(0, len));
}
// Check for final tag
if (tag[0] === sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL) {
break;
}
offset += chunkSize;
}
output.end();
}
}const sodium = require('sodium-native');
class SecureProtocol {
constructor() {
this.sendState = null;
this.receiveState = null;
this.messageCount = 0;
}
initializeSession(key, isInitiator = true) {
const header = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES);
if (isInitiator) {
this.sendState = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_STATEBYTES);
sodium.crypto_secretstream_xchacha20poly1305_init_push(this.sendState, header, key);
return header; // Send to peer
} else {
this.receiveState = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_STATEBYTES);
sodium.crypto_secretstream_xchacha20poly1305_init_pull(this.receiveState, header, key);
}
}
sendMessage(message, isImportant = false) {
if (!this.sendState) throw new Error('Send state not initialized');
const ciphertext = Buffer.alloc(message.length + sodium.crypto_secretstream_xchacha20poly1305_ABYTES);
// Use different tags for message importance
const tag = isImportant ?
sodium.crypto_secretstream_xchacha20poly1305_TAG_PUSH :
sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
const len = sodium.crypto_secretstream_xchacha20poly1305_push(
this.sendState, ciphertext, message, null, tag
);
this.messageCount++;
// Rotate key every 1000 messages for forward secrecy
if (this.messageCount % 1000 === 0) {
sodium.crypto_secretstream_xchacha20poly1305_rekey(this.sendState);
}
return ciphertext.subarray(0, len);
}
receiveMessage(ciphertext) {
if (!this.receiveState) throw new Error('Receive state not initialized');
const plaintext = Buffer.alloc(ciphertext.length - sodium.crypto_secretstream_xchacha20poly1305_ABYTES);
const tag = Buffer.alloc(1);
const len = sodium.crypto_secretstream_xchacha20poly1305_pull(
this.receiveState, plaintext, tag, ciphertext, null
);
// Handle different message tags
switch (tag[0]) {
case sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE:
return { message: plaintext.subarray(0, len), type: 'regular' };
case sodium.crypto_secretstream_xchacha20poly1305_TAG_PUSH:
return { message: plaintext.subarray(0, len), type: 'important' };
case sodium.crypto_secretstream_xchacha20poly1305_TAG_REKEY:
sodium.crypto_secretstream_xchacha20poly1305_rekey(this.receiveState);
return { message: plaintext.subarray(0, len), type: 'rekey' };
case sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL:
return { message: plaintext.subarray(0, len), type: 'final' };
default:
throw new Error('Unknown message tag');
}
}
}Install with Tessl CLI
npx tessl i tessl/npm-sodium-native