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

secretbox.mddocs/

Secret-Key Encryption (SecretBox)

Secret-key encryption functions provide fast symmetric encryption with authentication using shared secrets. The "secretbox" functions combine stream ciphers with polynomial authentication for both confidentiality and integrity.

Supported Algorithms

  • XSalsa20Poly1305: Original NaCl-compatible algorithm (default)
  • XChaCha20Poly1305: Extended nonce variant for better security margins

Generic SecretBox (XSalsa20Poly1305)

The default secretbox implementation using XSalsa20 stream cipher with Poly1305 authentication.

Key Generation

/**
 * Generate a random key for secret-key encryption
 * @returns Uint8Array - 32-byte encryption key
 */
function crypto_secretbox_keygen(): Uint8Array;

Encryption

/**
 * Encrypt and authenticate data using secret-key cryptography
 * @param message - Plaintext data to encrypt
 * @param nonce - 24-byte nonce (must be unique per key)
 * @param key - 32-byte encryption key
 * @returns Uint8Array - Encrypted data with authentication tag
 * @throws Error on invalid parameters
 */
function crypto_secretbox_easy(
  message: Uint8Array,
  nonce: Uint8Array,
  key: Uint8Array
): Uint8Array;

Decryption

/**
 * Decrypt and verify data using secret-key cryptography
 * @param ciphertext - Encrypted data with authentication tag
 * @param nonce - 24-byte nonce used for encryption
 * @param key - 32-byte decryption key
 * @returns Uint8Array - Decrypted plaintext
 * @throws Error if authentication fails or wrong key
 */
function crypto_secretbox_open_easy(
  ciphertext: Uint8Array,
  nonce: Uint8Array,
  key: Uint8Array
): Uint8Array;

Detached Operations

/**
 * Encrypt with separate authentication tag
 * @param message - Plaintext to encrypt
 * @param nonce - 24-byte nonce
 * @param key - 32-byte encryption key
 * @returns Object with cipher and mac properties
 */
function crypto_secretbox_detached(
  message: Uint8Array,
  nonce: Uint8Array,
  key: Uint8Array
): { cipher: Uint8Array; mac: Uint8Array };

/**
 * Decrypt with separate authentication tag
 * @param ciphertext - Encrypted data (without tag)
 * @param mac - 16-byte authentication tag
 * @param nonce - 24-byte nonce
 * @param key - 32-byte decryption key
 * @returns Uint8Array - Decrypted plaintext
 * @throws Error if authentication fails
 */
function crypto_secretbox_open_detached(
  ciphertext: Uint8Array,
  mac: Uint8Array,
  nonce: Uint8Array,
  key: Uint8Array
): Uint8Array;

Constants

const crypto_secretbox_KEYBYTES: number;        // 32 (key length)
const crypto_secretbox_NONCEBYTES: number;      // 24 (nonce length)
const crypto_secretbox_MACBYTES: number;        // 16 (authentication tag length)
const crypto_secretbox_MESSAGEBYTES_MAX: number; // Maximum message size

XChaCha20Poly1305 Variant

The XChaCha20Poly1305 variant provides the same API but with extended nonce space for better security.

Key Generation

/**
 * Generate key for XChaCha20Poly1305 (compatible with generic)
 * @returns Uint8Array - 32-byte encryption key
 */
// Uses same crypto_secretbox_keygen() function

Encryption and Decryption

/**
 * Encrypt using XChaCha20-Poly1305
 * @param message - Plaintext to encrypt
 * @param nonce - 24-byte nonce (can be random with XChaCha20)
 * @param key - 32-byte encryption key
 * @returns Uint8Array - Encrypted data with tag
 */
// Note: XChaCha20 functions have same signatures as generic secretbox
// The library automatically selects the appropriate algorithm

XChaCha20 Constants

const crypto_secretbox_xchacha20poly1305_KEYBYTES: number;        // 32
const crypto_secretbox_xchacha20poly1305_NONCEBYTES: number;      // 24
const crypto_secretbox_xchacha20poly1305_MACBYTES: number;        // 16
const crypto_secretbox_xchacha20poly1305_MESSAGEBYTES_MAX: number; // Large value

XSalsa20 Legacy Constants

const crypto_secretbox_xsalsa20poly1305_KEYBYTES: number;        // 32
const crypto_secretbox_xsalsa20poly1305_NONCEBYTES: number;      // 24
const crypto_secretbox_xsalsa20poly1305_MACBYTES: number;        // 16
const crypto_secretbox_xsalsa20poly1305_MESSAGEBYTES_MAX: number; // Large value

Usage Examples

Basic Secret-Key Encryption

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

// Generate encryption key
const key = sodium.crypto_secretbox_keygen();

// Message to encrypt
const message = sodium.from_string('This is a secret message');

// Generate nonce (must be unique per key)
const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);

// Encrypt
const ciphertext = sodium.crypto_secretbox_easy(message, nonce, key);

// Decrypt
const plaintext = sodium.crypto_secretbox_open_easy(ciphertext, nonce, key);

console.log(sodium.to_string(plaintext)); // "This is a secret message"
console.log('Ciphertext length:', ciphertext.length); // Original length + 16 (MAC)

Detached Authentication for Separate Storage

const key = sodium.crypto_secretbox_keygen();
const message = sodium.from_string('Message with separate MAC');
const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);

// Encrypt with separate MAC
const { cipher, mac } = sodium.crypto_secretbox_detached(message, nonce, key);

// Store ciphertext and MAC separately
console.log('Cipher:', sodium.to_hex(cipher));
console.log('MAC:', sodium.to_hex(mac));
console.log('Nonce:', sodium.to_hex(nonce));

// Decrypt with separate MAC
const plaintext = sodium.crypto_secretbox_open_detached(cipher, mac, nonce, key);
console.log('Decrypted:', sodium.to_string(plaintext));

File Encryption System

class FileEncryption {
  constructor() {
    this.key = sodium.crypto_secretbox_keygen();
  }
  
  encrypt(data) {
    const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
    const ciphertext = sodium.crypto_secretbox_easy(data, nonce, this.key);
    
    // Prepend nonce to ciphertext for storage
    const encrypted = new Uint8Array(nonce.length + ciphertext.length);
    encrypted.set(nonce);
    encrypted.set(ciphertext, nonce.length);
    
    return encrypted;
  }
  
  decrypt(encrypted) {
    // Extract nonce and ciphertext
    const nonce = encrypted.subarray(0, sodium.crypto_secretbox_NONCEBYTES);
    const ciphertext = encrypted.subarray(sodium.crypto_secretbox_NONCEBYTES);
    
    return sodium.crypto_secretbox_open_easy(ciphertext, nonce, this.key);
  }
  
  // Export key for storage (should be encrypted with password)
  exportKey() {
    return sodium.to_base64(this.key);
  }
  
  // Import key from storage
  importKey(base64Key) {
    this.key = sodium.from_base64(base64Key);
  }
}

// Usage
const fileEnc = new FileEncryption();
const fileData = sodium.from_string('File contents to encrypt');

const encrypted = fileEnc.encrypt(fileData);
const decrypted = fileEnc.decrypt(encrypted);

console.log('Original equals decrypted:', 
  sodium.memcmp(fileData, decrypted)); // true

Streaming Encryption for Large Files

class StreamingEncryption {
  constructor(key) {
    this.key = key;
    this.chunkSize = 4096; // 4KB chunks
  }
  
  encryptStream(data) {
    const chunks = [];
    const totalChunks = Math.ceil(data.length / this.chunkSize);
    
    for (let i = 0; i < totalChunks; i++) {
      const start = i * this.chunkSize;
      const end = Math.min(start + this.chunkSize, data.length);
      const chunk = data.subarray(start, end);
      
      // Use chunk index as part of nonce (be careful in production!)
      const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
      const encrypted = sodium.crypto_secretbox_easy(chunk, nonce, this.key);
      
      chunks.push({
        index: i,
        nonce: nonce,
        data: encrypted
      });
    }
    
    return chunks;
  }
  
  decryptStream(chunks) {
    // Sort chunks by index
    chunks.sort((a, b) => a.index - b.index);
    
    const decryptedChunks = [];
    for (const chunk of chunks) {
      const decrypted = sodium.crypto_secretbox_open_easy(
        chunk.data, chunk.nonce, this.key
      );
      decryptedChunks.push(decrypted);
    }
    
    // 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 with large file
const key = sodium.crypto_secretbox_keygen();
const streaming = new StreamingEncryption(key);

const largeData = new Uint8Array(100000); // 100KB
sodium.randombytes_buf_into(largeData);

console.time('Stream Encrypt');
const encryptedChunks = streaming.encryptStream(largeData);
console.timeEnd('Stream Encrypt');

console.time('Stream Decrypt');
const decryptedData = streaming.decryptStream(encryptedChunks);
console.timeEnd('Stream Decrypt');

console.log('Streaming successful:', 
  sodium.memcmp(largeData, decryptedData)); // true

Password-Based Encryption

// Combine with key derivation for password-based encryption
async function encryptWithPassword(message, password) {
  // Derive key from password using Argon2id
  const salt = sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES);
  const key = sodium.crypto_pwhash(
    sodium.crypto_secretbox_KEYBYTES,
    sodium.from_string(password),
    salt,
    sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE,
    sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE,
    sodium.crypto_pwhash_ALG_ARGON2ID13
  );
  
  // Encrypt with derived key
  const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
  const ciphertext = sodium.crypto_secretbox_easy(message, nonce, key);
  
  // Return salt, nonce, and ciphertext
  return {
    salt: sodium.to_base64(salt),
    nonce: sodium.to_base64(nonce),
    ciphertext: sodium.to_base64(ciphertext)
  };
}

async function decryptWithPassword(encryptedData, password) {
  const salt = sodium.from_base64(encryptedData.salt);
  const nonce = sodium.from_base64(encryptedData.nonce);
  const ciphertext = sodium.from_base64(encryptedData.ciphertext);
  
  // Derive same key from password
  const key = sodium.crypto_pwhash(
    sodium.crypto_secretbox_KEYBYTES,
    sodium.from_string(password),
    salt,
    sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE,
    sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE,
    sodium.crypto_pwhash_ALG_ARGON2ID13
  );
  
  // Decrypt
  return sodium.crypto_secretbox_open_easy(ciphertext, nonce, key);
}

// Usage
const message = sodium.from_string('Password-protected message');
const encrypted = await encryptWithPassword(message, 'mypassword123');
const decrypted = await decryptWithPassword(encrypted, 'mypassword123');

console.log(sodium.to_string(decrypted)); // "Password-protected message"

Performance Benchmarking

function benchmarkSecretBox() {
  const key = sodium.crypto_secretbox_keygen();
  const message = new Uint8Array(1024 * 1024); // 1MB
  sodium.randombytes_buf_into(message);
  
  console.time('SecretBox Encrypt 1MB');
  for (let i = 0; i < 10; i++) {
    const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
    const ciphertext = sodium.crypto_secretbox_easy(message, nonce, key);
  }
  console.timeEnd('SecretBox Encrypt 1MB');
  
  // Test decryption
  const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
  const ciphertext = sodium.crypto_secretbox_easy(message, nonce, key);
  
  console.time('SecretBox Decrypt 1MB');
  for (let i = 0; i < 10; i++) {
    const plaintext = sodium.crypto_secretbox_open_easy(ciphertext, nonce, key);
  }
  console.timeEnd('SecretBox Decrypt 1MB');
}

benchmarkSecretBox();

Security Considerations

  • Nonce Uniqueness: Never reuse nonces with the same key - this breaks security completely
  • Key Management: Keep encryption keys secret and use crypto_secretbox_keygen() for generation
  • Nonce Generation: Use randombytes_buf() for nonce generation
  • Authentication: All secretbox functions provide authenticated encryption
  • Key Rotation: Consider periodic key rotation for long-term use
  • Memory Security: Use memzero() to clear sensitive data from memory

Algorithm Selection Guide

  • Generic secretbox: Good default choice, NaCl-compatible
  • XChaCha20 variant: Better security margins, safer for high-volume applications
  • Detached mode: When you need to store/transmit MAC separately
  • Password-based: Combine with crypto_pwhash for user password encryption

SecretBox provides excellent performance and security for symmetric encryption needs.

docs

aead.md

auth.md

box.md

hash.md

index.md

key-derivation.md

secretbox.md

sign.md

streaming.md

utilities.md

tile.json