CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-libsodium-wrappers-sumo

The Sodium cryptographic library compiled to pure JavaScript (wrappers, sumo variant)

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

streaming.mddocs/

Streaming Operations

Streaming functions provide encryption and stream generation for large data sets, real-time communication, and custom protocols. These functions process data in chunks rather than requiring the entire message in memory.

Secret Stream (XChaCha20-Poly1305)

Secret stream provides streaming authenticated encryption with rekeying support, perfect for encrypting large files or real-time data streams.

Key Generation

/**
 * Generate a random key for secret stream operations
 * @returns Uint8Array - 32-byte stream key
 */
function crypto_secretstream_xchacha20poly1305_keygen(): Uint8Array;

Stream Initialization

Push (Encryption) Stream

/**
 * Initialize stream for encryption (push)
 * @param key - 32-byte stream key
 * @returns Object with state and header
 */
function crypto_secretstream_xchacha20poly1305_init_push(key: Uint8Array): {
  state: Uint8Array;   // State object for streaming
  header: Uint8Array;  // 24-byte header to send with stream
};

Pull (Decryption) Stream

/**
 * Initialize stream for decryption (pull)
 * @param header - 24-byte header from encryption stream
 * @param key - 32-byte stream key
 * @returns Uint8Array - State object for streaming
 */
function crypto_secretstream_xchacha20poly1305_init_pull(
  header: Uint8Array,
  key: Uint8Array
): Uint8Array;

Stream Operations

Push (Encrypt) Data

/**
 * Encrypt data chunk in stream
 * @param state_address - State from init_push
 * @param message_chunk - Data chunk to encrypt
 * @param ad - Additional authenticated data (optional)
 * @param tag - Stream tag (MESSAGE, PUSH, REKEY, or FINAL)
 * @returns Uint8Array - Encrypted chunk with authentication
 */
function crypto_secretstream_xchacha20poly1305_push(
  state_address: any,
  message_chunk: Uint8Array,
  ad: Uint8Array | null,
  tag: number
): Uint8Array;

Pull (Decrypt) Data

/**
 * Decrypt data chunk from stream
 * @param state_address - State from init_pull
 * @param cipher - Encrypted chunk
 * @param ad - Additional authenticated data (optional)
 * @returns Object with message_chunk and tag
 */
function crypto_secretstream_xchacha20poly1305_pull(
  state_address: any,
  cipher: Uint8Array,
  ad: Uint8Array | null
): {
  message_chunk: Uint8Array;
  tag: number;
};

Rekeying

/**
 * Rekey the stream for forward secrecy
 * @param state_address - Stream state to rekey
 */
function crypto_secretstream_xchacha20poly1305_rekey(state_address: any): void;

Stream Tags

const crypto_secretstream_xchacha20poly1305_TAG_MESSAGE: number; // 0 - Normal message
const crypto_secretstream_xchacha20poly1305_TAG_PUSH: number;    // 1 - End of chunk
const crypto_secretstream_xchacha20poly1305_TAG_REKEY: number;   // 2 - Rekey after this
const crypto_secretstream_xchacha20poly1305_TAG_FINAL: number;   // 3 - End of stream

Secret Stream Constants

const crypto_secretstream_xchacha20poly1305_KEYBYTES: number;       // 32
const crypto_secretstream_xchacha20poly1305_HEADERBYTES: number;    // 24
const crypto_secretstream_xchacha20poly1305_ABYTES: number;         // 17
const crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX: number; // Large value

Stream Ciphers

Stream cipher functions generate pseudorandom streams for XOR-based encryption or random number generation.

ChaCha20

Key Generation

/**
 * Generate key for ChaCha20 stream cipher
 * @returns Uint8Array - 32-byte stream key
 */
function crypto_stream_chacha20_keygen(): Uint8Array;

Stream Generation

/**
 * Generate ChaCha20 keystream
 * @param outLength - Length of stream to generate
 * @param key - 32-byte stream key
 * @param nonce - 8-byte nonce
 * @returns Uint8Array - Generated keystream
 */
function crypto_stream_chacha20(
  outLength: number,
  key: Uint8Array,
  nonce: Uint8Array
): Uint8Array;

XOR Operations

/**
 * Encrypt/decrypt data using ChaCha20 stream XOR
 * @param input_message - Data to encrypt/decrypt
 * @param nonce - 8-byte nonce
 * @param key - 32-byte stream key
 * @returns Uint8Array - XOR result
 */
function crypto_stream_chacha20_xor(
  input_message: Uint8Array,
  nonce: Uint8Array,
  key: Uint8Array
): Uint8Array;

/**
 * ChaCha20 XOR with initial counter
 * @param input_message - Data to encrypt/decrypt
 * @param nonce - 8-byte nonce
 * @param nonce_increment - Initial counter value
 * @param key - 32-byte stream key
 * @returns Uint8Array - XOR result
 */
function crypto_stream_chacha20_xor_ic(
  input_message: Uint8Array,
  nonce: Uint8Array,
  nonce_increment: number,
  key: Uint8Array
): Uint8Array;

ChaCha20 IETF

XOR Operations

/**
 * ChaCha20 IETF variant XOR (12-byte nonce)
 * @param input_message - Data to encrypt/decrypt
 * @param nonce - 12-byte nonce
 * @param key - 32-byte stream key
 * @returns Uint8Array - XOR result
 */
function crypto_stream_chacha20_ietf_xor(
  input_message: Uint8Array,
  nonce: Uint8Array,
  key: Uint8Array
): Uint8Array;

/**
 * ChaCha20 IETF XOR with initial counter
 * @param input_message - Data to encrypt/decrypt
 * @param nonce - 12-byte nonce
 * @param nonce_increment - Initial counter value
 * @param key - 32-byte stream key
 * @returns Uint8Array - XOR result
 */
function crypto_stream_chacha20_ietf_xor_ic(
  input_message: Uint8Array,
  nonce: Uint8Array,
  nonce_increment: number,
  key: Uint8Array
): Uint8Array;

XChaCha20

Extended nonce variant of ChaCha20 for better security.

Key Generation

/**
 * Generate key for XChaCha20 stream cipher
 * @returns Uint8Array - 32-byte stream key
 */
function crypto_stream_xchacha20_keygen(): Uint8Array;

XOR Operations

/**
 * XChaCha20 XOR encryption/decryption
 * @param input_message - Data to encrypt/decrypt
 * @param nonce - 24-byte nonce
 * @param key - 32-byte stream key
 * @returns Uint8Array - XOR result
 */
function crypto_stream_xchacha20_xor(
  input_message: Uint8Array,
  nonce: Uint8Array,
  key: Uint8Array
): Uint8Array;

/**
 * XChaCha20 XOR with initial counter
 * @param input_message - Data to encrypt/decrypt
 * @param nonce - 24-byte nonce
 * @param nonce_increment - Initial counter value
 * @param key - 32-byte stream key
 * @returns Uint8Array - XOR result
 */
function crypto_stream_xchacha20_xor_ic(
  input_message: Uint8Array,
  nonce: Uint8Array,
  nonce_increment: number,
  key: Uint8Array
): Uint8Array;

Generic Stream

Default stream cipher functions.

/**
 * Generate key for generic stream cipher
 * @returns Uint8Array - Stream key
 */
function crypto_stream_keygen(): Uint8Array;

Stream Cipher Constants

// ChaCha20
const crypto_stream_chacha20_KEYBYTES: number;          // 32
const crypto_stream_chacha20_NONCEBYTES: number;        // 8
const crypto_stream_chacha20_MESSAGEBYTES_MAX: number;  // Large value

// ChaCha20 IETF
const crypto_stream_chacha20_ietf_KEYBYTES: number;          // 32
const crypto_stream_chacha20_ietf_NONCEBYTES: number;        // 12
const crypto_stream_chacha20_ietf_MESSAGEBYTES_MAX: number;  // Large value

// XChaCha20
const crypto_stream_xchacha20_KEYBYTES: number;          // 32
const crypto_stream_xchacha20_NONCEBYTES: number;        // 24
const crypto_stream_xchacha20_MESSAGEBYTES_MAX: number;  // Large value

// Generic stream
const crypto_stream_KEYBYTES: number;          // 32
const crypto_stream_NONCEBYTES: number;        // 24
const crypto_stream_MESSAGEBYTES_MAX: number;  // Large value

Usage Examples

Basic Secret Stream

import _sodium from 'libsodium-wrappers-sumo';
await _sodium.ready;
const sodium = _sodium;

// Generate stream key
const key = sodium.crypto_secretstream_xchacha20poly1305_keygen();

// Initialize encryption stream
const { state: pushState, header } = sodium.crypto_secretstream_xchacha20poly1305_init_push(key);

// Initialize decryption stream
const pullState = sodium.crypto_secretstream_xchacha20poly1305_init_pull(header, key);

// Encrypt data chunks
const chunk1 = sodium.from_string('First chunk of data');
const chunk2 = sodium.from_string('Second chunk of data');
const chunk3 = sodium.from_string('Final chunk of data');

const encrypted1 = sodium.crypto_secretstream_xchacha20poly1305_push(
  pushState, chunk1, null, sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE
);

const encrypted2 = sodium.crypto_secretstream_xchacha20poly1305_push(
  pushState, chunk2, null, sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE
);

const encrypted3 = sodium.crypto_secretstream_xchacha20poly1305_push(
  pushState, chunk3, null, sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
);

// Decrypt data chunks
const { message_chunk: decrypted1, tag: tag1 } = sodium.crypto_secretstream_xchacha20poly1305_pull(
  pullState, encrypted1, null
);

const { message_chunk: decrypted2, tag: tag2 } = sodium.crypto_secretstream_xchacha20poly1305_pull(
  pullState, encrypted2, null
);

const { message_chunk: decrypted3, tag: tag3 } = sodium.crypto_secretstream_xchacha20poly1305_pull(
  pullState, encrypted3, null
);

console.log('Decrypted chunk 1:', sodium.to_string(decrypted1));
console.log('Decrypted chunk 2:', sodium.to_string(decrypted2));
console.log('Decrypted chunk 3:', sodium.to_string(decrypted3));
console.log('Final tag:', tag3 === sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL); // true

File Streaming Encryption

class StreamingFileEncryption {
  constructor() {
    this.key = sodium.crypto_secretstream_xchacha20poly1305_keygen();
  }
  
  // Encrypt file in chunks
  encryptFile(fileData, chunkSize = 4096) {
    const { state, header } = sodium.crypto_secretstream_xchacha20poly1305_init_push(this.key);
    const encryptedChunks = [];
    
    // Store header first
    encryptedChunks.push({
      type: 'header',
      data: header
    });
    
    // Process file in chunks
    for (let i = 0; i < fileData.length; i += chunkSize) {
      const chunk = fileData.subarray(i, i + chunkSize);
      const isLast = i + chunkSize >= fileData.length;
      
      const tag = isLast 
        ? sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
        : sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
      
      const encrypted = sodium.crypto_secretstream_xchacha20poly1305_push(
        state, chunk, null, tag
      );
      
      encryptedChunks.push({
        type: 'chunk',
        data: encrypted,
        tag: tag
      });
    }
    
    return encryptedChunks;
  }
  
  // Decrypt file from chunks
  decryptFile(encryptedChunks) {
    if (encryptedChunks[0].type !== 'header') {
      throw new Error('First chunk must be header');
    }
    
    const header = encryptedChunks[0].data;
    const state = sodium.crypto_secretstream_xchacha20poly1305_init_pull(header, this.key);
    
    const decryptedChunks = [];
    
    for (let i = 1; i < encryptedChunks.length; i++) {
      const { message_chunk, tag } = sodium.crypto_secretstream_xchacha20poly1305_pull(
        state, encryptedChunks[i].data, null
      );
      
      decryptedChunks.push(message_chunk);
      
      // Check if this is the final chunk
      if (tag === sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL) {
        break;
      }
    }
    
    // Concatenate all chunks
    const totalLength = decryptedChunks.reduce((sum, chunk) => sum + chunk.length, 0);
    const result = new Uint8Array(totalLength);
    let offset = 0;
    
    for (const chunk of decryptedChunks) {
      result.set(chunk, offset);
      offset += chunk.length;
    }
    
    return result;
  }
}

// Usage
const fileEnc = new StreamingFileEncryption();
const largeFile = new Uint8Array(100000); // 100KB file
sodium.randombytes_buf_into(largeFile);

console.time('Encrypt large file');
const encryptedChunks = fileEnc.encryptFile(largeFile, 8192); // 8KB chunks
console.timeEnd('Encrypt large file');

console.log('Encrypted chunks:', encryptedChunks.length);

console.time('Decrypt large file');
const decryptedFile = fileEnc.decryptFile(encryptedChunks);
console.timeEnd('Decrypt large file');

console.log('Files match:', sodium.memcmp(largeFile, decryptedFile)); // true

Real-Time Streaming with Rekeying

class SecureDataStream {
  constructor(isEncryptor = true) {
    this.key = sodium.crypto_secretstream_xchacha20poly1305_keygen();
    this.isEncryptor = isEncryptor;
    this.messageCount = 0;
    this.rekeyInterval = 100; // Rekey every 100 messages
    
    if (isEncryptor) {
      const { state, header } = sodium.crypto_secretstream_xchacha20poly1305_init_push(this.key);
      this.state = state;
      this.header = header;
    } else {
      this.state = null; // Will be set when header received
    }
  }
  
  // Initialize decryptor with header
  initDecryptor(header) {
    if (this.isEncryptor) {
      throw new Error('Cannot init decryptor on encryptor');
    }
    this.state = sodium.crypto_secretstream_xchacha20poly1305_init_pull(header, this.key);
  }
  
  // Process outgoing message
  encrypt(message, metadata = null) {
    if (!this.isEncryptor || !this.state) {
      throw new Error('Invalid encryptor state');
    }
    
    this.messageCount++;
    let tag = sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
    
    // Rekey periodically for forward secrecy
    if (this.messageCount % this.rekeyInterval === 0) {
      tag = sodium.crypto_secretstream_xchacha20poly1305_TAG_REKEY;
    }
    
    const encrypted = sodium.crypto_secretstream_xchacha20poly1305_push(
      this.state, message, metadata, tag
    );
    
    // Perform rekeying after REKEY tag
    if (tag === sodium.crypto_secretstream_xchacha20poly1305_TAG_REKEY) {
      sodium.crypto_secretstream_xchacha20poly1305_rekey(this.state);
      console.log('Stream rekeyed at message', this.messageCount);
    }
    
    return encrypted;
  }
  
  // Process incoming message
  decrypt(encrypted, expectedMetadata = null) {
    if (this.isEncryptor || !this.state) {
      throw new Error('Invalid decryptor state');
    }
    
    const { message_chunk, tag } = sodium.crypto_secretstream_xchacha20poly1305_pull(
      this.state, encrypted, expectedMetadata
    );
    
    // Handle rekeying
    if (tag === sodium.crypto_secretstream_xchacha20poly1305_TAG_REKEY) {
      sodium.crypto_secretstream_xchacha20poly1305_rekey(this.state);
      console.log('Stream rekeyed by peer');
    }
    
    return { message: message_chunk, tag };
  }
  
  // Finalize stream
  finalize(finalMessage = null) {
    if (!this.isEncryptor) {
      throw new Error('Only encryptor can finalize');
    }
    
    const message = finalMessage || new Uint8Array(0);
    return sodium.crypto_secretstream_xchacha20poly1305_push(
      this.state, message, null, sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
    );
  }
  
  getHeader() {
    return this.header;
  }
}

// Simulate real-time communication with rekeying
const sender = new SecureDataStream(true);
const receiver = new SecureDataStream(false);

// Initialize receiver with sender's header
receiver.initDecryptor(sender.getHeader());

// Send many messages to trigger rekeying
for (let i = 1; i <= 150; i++) {
  const message = sodium.from_string(`Message ${i}`);
  const metadata = sodium.from_string(`meta${i}`);
  
  // Encrypt
  const encrypted = sender.encrypt(message, metadata);
  
  // Decrypt
  const { message: decrypted, tag } = receiver.decrypt(encrypted, metadata);
  
  if (i % 50 === 0) { // Log every 50th message
    console.log(`Message ${i}:`, sodium.to_string(decrypted));
  }
}

// Finalize stream
const finalEncrypted = sender.finalize(sodium.from_string('Stream ended'));
const { message: finalDecrypted, tag: finalTag } = receiver.decrypt(finalEncrypted);

console.log('Final message:', sodium.to_string(finalDecrypted));
console.log('Stream finalized:', finalTag === sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL);

Stream Cipher Usage

// Basic stream cipher usage
const streamKey = sodium.crypto_stream_xchacha20_keygen();
const nonce = sodium.randombytes_buf(sodium.crypto_stream_xchacha20_NONCEBYTES);

const plaintext = sodium.from_string('Stream cipher encryption');

// Encrypt using XOR
const ciphertext = sodium.crypto_stream_xchacha20_xor(plaintext, nonce, streamKey);
console.log('Encrypted:', sodium.to_hex(ciphertext));

// Decrypt using XOR (same operation)
const decrypted = sodium.crypto_stream_xchacha20_xor(ciphertext, nonce, streamKey);
console.log('Decrypted:', sodium.to_string(decrypted)); // "Stream cipher encryption"

// Generate raw keystream
const keystream = sodium.crypto_stream_chacha20(100, streamKey.subarray(0, 32), nonce.subarray(0, 8));
console.log('Keystream sample:', sodium.to_hex(keystream.subarray(0, 16)));

Custom Protocol with Stream Chunks

class ChunkedProtocol {
  constructor(chunkSize = 1024) {
    this.chunkSize = chunkSize;
    this.key = sodium.crypto_secretstream_xchacha20poly1305_keygen();
  }
  
  // Encode message into protocol chunks
  encodeMessage(message, messageType = 'data') {
    const { state, header } = sodium.crypto_secretstream_xchacha20poly1305_init_push(this.key);
    
    const chunks = [];
    
    // Add protocol header
    chunks.push({
      type: 'protocol_header',
      data: header
    });
    
    // Add message metadata
    const metadata = {
      messageType,
      totalLength: message.length,
      timestamp: Date.now()
    };
    
    const metadataBytes = sodium.from_string(JSON.stringify(metadata));
    const encryptedMetadata = sodium.crypto_secretstream_xchacha20poly1305_push(
      state, metadataBytes, null, sodium.crypto_secretstream_xchacha20poly1305_TAG_PUSH
    );
    
    chunks.push({
      type: 'metadata',
      data: encryptedMetadata
    });
    
    // Add message chunks
    for (let i = 0; i < message.length; i += this.chunkSize) {
      const chunk = message.subarray(i, i + this.chunkSize);
      const isLast = i + this.chunkSize >= message.length;
      
      const tag = isLast
        ? sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
        : sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
      
      const encrypted = sodium.crypto_secretstream_xchacha20poly1305_push(
        state, chunk, null, tag
      );
      
      chunks.push({
        type: 'data',
        data: encrypted
      });
    }
    
    return chunks;
  }
  
  // Decode protocol chunks back to message
  decodeMessage(chunks) {
    if (chunks[0].type !== 'protocol_header') {
      throw new Error('Invalid protocol: missing header');
    }
    
    const header = chunks[0].data;
    const state = sodium.crypto_secretstream_xchacha20poly1305_init_pull(header, this.key);
    
    // Decrypt metadata
    if (chunks[1].type !== 'metadata') {
      throw new Error('Invalid protocol: missing metadata');
    }
    
    const { message_chunk: metadataBytes } = sodium.crypto_secretstream_xchacha20poly1305_pull(
      state, chunks[1].data, null
    );
    
    const metadata = JSON.parse(sodium.to_string(metadataBytes));
    console.log('Message metadata:', metadata);
    
    // Decrypt data chunks
    const dataChunks = [];
    for (let i = 2; i < chunks.length; i++) {
      if (chunks[i].type === 'data') {
        const { message_chunk, tag } = sodium.crypto_secretstream_xchacha20poly1305_pull(
          state, chunks[i].data, null
        );
        
        dataChunks.push(message_chunk);
        
        if (tag === sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL) {
          break;
        }
      }
    }
    
    // Reconstruct complete message
    const totalLength = dataChunks.reduce((sum, chunk) => sum + chunk.length, 0);
    const result = new Uint8Array(totalLength);
    let offset = 0;
    
    for (const chunk of dataChunks) {
      result.set(chunk, offset);
      offset += chunk.length;
    }
    
    return {
      message: result,
      metadata
    };
  }
}

// Usage
const protocol = new ChunkedProtocol(512); // 512-byte chunks
const message = sodium.from_string('This is a long message that will be split into multiple chunks for transmission over the network. Each chunk is encrypted separately for security.');

// Encode
const encodedChunks = protocol.encodeMessage(message, 'text_message');
console.log('Encoded into', encodedChunks.length, 'chunks');

// Decode
const { message: decodedMessage, metadata } = protocol.decodeMessage(encodedChunks);
console.log('Decoded message:', sodium.to_string(decodedMessage));
console.log('Messages match:', sodium.memcmp(message, decodedMessage)); // true

Security Considerations

  • Stream Reuse: Never reuse stream states or nonces
  • Rekeying: Use periodic rekeying for forward secrecy in long streams
  • Tag Handling: Properly handle stream tags to maintain protocol integrity
  • Memory Management: Clear stream states after use
  • Authentication: Secret streams provide authentication, stream ciphers do not
  • Nonce Management: Use proper nonce generation for stream ciphers

Algorithm Selection Guide

  • Secret Stream: Use for authenticated streaming encryption with rekeying
  • XChaCha20: Best stream cipher choice for new applications
  • ChaCha20 IETF: Standards compliance, careful nonce management
  • ChaCha20: Original variant, 8-byte nonces
  • Stream Rekeying: Essential for long-running streams and forward secrecy

Streaming operations are perfect for large files, real-time communication, and memory-constrained environments.

docs

aead.md

auth.md

box.md

hash.md

index.md

key-derivation.md

secretbox.md

sign.md

streaming.md

utilities.md

tile.json