The Sodium cryptographic library compiled to pure JavaScript (wrappers, sumo variant)
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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.
The default secretbox implementation using XSalsa20 stream cipher with Poly1305 authentication.
/**
* Generate a random key for secret-key encryption
* @returns Uint8Array - 32-byte encryption key
*/
function crypto_secretbox_keygen(): Uint8Array;/**
* 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;/**
* 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;/**
* 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;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 sizeThe XChaCha20Poly1305 variant provides the same API but with extended nonce space for better security.
/**
* Generate key for XChaCha20Poly1305 (compatible with generic)
* @returns Uint8Array - 32-byte encryption key
*/
// Uses same crypto_secretbox_keygen() function/**
* 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 algorithmconst 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 valueconst 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 valueimport _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)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));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)); // trueclass 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// 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"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();SecretBox provides excellent performance and security for symmetric encryption needs.