0
# Secret-Key Encryption (SecretBox)
1
2
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.
3
4
## Supported Algorithms
5
6
- **XSalsa20Poly1305**: Original NaCl-compatible algorithm (default)
7
- **XChaCha20Poly1305**: Extended nonce variant for better security margins
8
9
## Generic SecretBox (XSalsa20Poly1305)
10
11
The default secretbox implementation using XSalsa20 stream cipher with Poly1305 authentication.
12
13
### Key Generation
14
15
```javascript { .api }
16
/**
17
* Generate a random key for secret-key encryption
18
* @returns Uint8Array - 32-byte encryption key
19
*/
20
function crypto_secretbox_keygen(): Uint8Array;
21
```
22
23
### Encryption
24
25
```javascript { .api }
26
/**
27
* Encrypt and authenticate data using secret-key cryptography
28
* @param message - Plaintext data to encrypt
29
* @param nonce - 24-byte nonce (must be unique per key)
30
* @param key - 32-byte encryption key
31
* @returns Uint8Array - Encrypted data with authentication tag
32
* @throws Error on invalid parameters
33
*/
34
function crypto_secretbox_easy(
35
message: Uint8Array,
36
nonce: Uint8Array,
37
key: Uint8Array
38
): Uint8Array;
39
```
40
41
### Decryption
42
43
```javascript { .api }
44
/**
45
* Decrypt and verify data using secret-key cryptography
46
* @param ciphertext - Encrypted data with authentication tag
47
* @param nonce - 24-byte nonce used for encryption
48
* @param key - 32-byte decryption key
49
* @returns Uint8Array - Decrypted plaintext
50
* @throws Error if authentication fails or wrong key
51
*/
52
function crypto_secretbox_open_easy(
53
ciphertext: Uint8Array,
54
nonce: Uint8Array,
55
key: Uint8Array
56
): Uint8Array;
57
```
58
59
### Detached Operations
60
61
```javascript { .api }
62
/**
63
* Encrypt with separate authentication tag
64
* @param message - Plaintext to encrypt
65
* @param nonce - 24-byte nonce
66
* @param key - 32-byte encryption key
67
* @returns Object with cipher and mac properties
68
*/
69
function crypto_secretbox_detached(
70
message: Uint8Array,
71
nonce: Uint8Array,
72
key: Uint8Array
73
): { cipher: Uint8Array; mac: Uint8Array };
74
75
/**
76
* Decrypt with separate authentication tag
77
* @param ciphertext - Encrypted data (without tag)
78
* @param mac - 16-byte authentication tag
79
* @param nonce - 24-byte nonce
80
* @param key - 32-byte decryption key
81
* @returns Uint8Array - Decrypted plaintext
82
* @throws Error if authentication fails
83
*/
84
function crypto_secretbox_open_detached(
85
ciphertext: Uint8Array,
86
mac: Uint8Array,
87
nonce: Uint8Array,
88
key: Uint8Array
89
): Uint8Array;
90
```
91
92
### Constants
93
94
```javascript { .api }
95
const crypto_secretbox_KEYBYTES: number; // 32 (key length)
96
const crypto_secretbox_NONCEBYTES: number; // 24 (nonce length)
97
const crypto_secretbox_MACBYTES: number; // 16 (authentication tag length)
98
const crypto_secretbox_MESSAGEBYTES_MAX: number; // Maximum message size
99
```
100
101
## XChaCha20Poly1305 Variant
102
103
The XChaCha20Poly1305 variant provides the same API but with extended nonce space for better security.
104
105
### Key Generation
106
107
```javascript { .api }
108
/**
109
* Generate key for XChaCha20Poly1305 (compatible with generic)
110
* @returns Uint8Array - 32-byte encryption key
111
*/
112
// Uses same crypto_secretbox_keygen() function
113
```
114
115
### Encryption and Decryption
116
117
```javascript { .api }
118
/**
119
* Encrypt using XChaCha20-Poly1305
120
* @param message - Plaintext to encrypt
121
* @param nonce - 24-byte nonce (can be random with XChaCha20)
122
* @param key - 32-byte encryption key
123
* @returns Uint8Array - Encrypted data with tag
124
*/
125
// Note: XChaCha20 functions have same signatures as generic secretbox
126
// The library automatically selects the appropriate algorithm
127
```
128
129
### XChaCha20 Constants
130
131
```javascript { .api }
132
const crypto_secretbox_xchacha20poly1305_KEYBYTES: number; // 32
133
const crypto_secretbox_xchacha20poly1305_NONCEBYTES: number; // 24
134
const crypto_secretbox_xchacha20poly1305_MACBYTES: number; // 16
135
const crypto_secretbox_xchacha20poly1305_MESSAGEBYTES_MAX: number; // Large value
136
```
137
138
### XSalsa20 Legacy Constants
139
140
```javascript { .api }
141
const crypto_secretbox_xsalsa20poly1305_KEYBYTES: number; // 32
142
const crypto_secretbox_xsalsa20poly1305_NONCEBYTES: number; // 24
143
const crypto_secretbox_xsalsa20poly1305_MACBYTES: number; // 16
144
const crypto_secretbox_xsalsa20poly1305_MESSAGEBYTES_MAX: number; // Large value
145
```
146
147
## Usage Examples
148
149
### Basic Secret-Key Encryption
150
151
```javascript
152
import _sodium from 'libsodium-wrappers-sumo';
153
await _sodium.ready;
154
const sodium = _sodium;
155
156
// Generate encryption key
157
const key = sodium.crypto_secretbox_keygen();
158
159
// Message to encrypt
160
const message = sodium.from_string('This is a secret message');
161
162
// Generate nonce (must be unique per key)
163
const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
164
165
// Encrypt
166
const ciphertext = sodium.crypto_secretbox_easy(message, nonce, key);
167
168
// Decrypt
169
const plaintext = sodium.crypto_secretbox_open_easy(ciphertext, nonce, key);
170
171
console.log(sodium.to_string(plaintext)); // "This is a secret message"
172
console.log('Ciphertext length:', ciphertext.length); // Original length + 16 (MAC)
173
```
174
175
### Detached Authentication for Separate Storage
176
177
```javascript
178
const key = sodium.crypto_secretbox_keygen();
179
const message = sodium.from_string('Message with separate MAC');
180
const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
181
182
// Encrypt with separate MAC
183
const { cipher, mac } = sodium.crypto_secretbox_detached(message, nonce, key);
184
185
// Store ciphertext and MAC separately
186
console.log('Cipher:', sodium.to_hex(cipher));
187
console.log('MAC:', sodium.to_hex(mac));
188
console.log('Nonce:', sodium.to_hex(nonce));
189
190
// Decrypt with separate MAC
191
const plaintext = sodium.crypto_secretbox_open_detached(cipher, mac, nonce, key);
192
console.log('Decrypted:', sodium.to_string(plaintext));
193
```
194
195
### File Encryption System
196
197
```javascript
198
class FileEncryption {
199
constructor() {
200
this.key = sodium.crypto_secretbox_keygen();
201
}
202
203
encrypt(data) {
204
const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
205
const ciphertext = sodium.crypto_secretbox_easy(data, nonce, this.key);
206
207
// Prepend nonce to ciphertext for storage
208
const encrypted = new Uint8Array(nonce.length + ciphertext.length);
209
encrypted.set(nonce);
210
encrypted.set(ciphertext, nonce.length);
211
212
return encrypted;
213
}
214
215
decrypt(encrypted) {
216
// Extract nonce and ciphertext
217
const nonce = encrypted.subarray(0, sodium.crypto_secretbox_NONCEBYTES);
218
const ciphertext = encrypted.subarray(sodium.crypto_secretbox_NONCEBYTES);
219
220
return sodium.crypto_secretbox_open_easy(ciphertext, nonce, this.key);
221
}
222
223
// Export key for storage (should be encrypted with password)
224
exportKey() {
225
return sodium.to_base64(this.key);
226
}
227
228
// Import key from storage
229
importKey(base64Key) {
230
this.key = sodium.from_base64(base64Key);
231
}
232
}
233
234
// Usage
235
const fileEnc = new FileEncryption();
236
const fileData = sodium.from_string('File contents to encrypt');
237
238
const encrypted = fileEnc.encrypt(fileData);
239
const decrypted = fileEnc.decrypt(encrypted);
240
241
console.log('Original equals decrypted:',
242
sodium.memcmp(fileData, decrypted)); // true
243
```
244
245
### Streaming Encryption for Large Files
246
247
```javascript
248
class StreamingEncryption {
249
constructor(key) {
250
this.key = key;
251
this.chunkSize = 4096; // 4KB chunks
252
}
253
254
encryptStream(data) {
255
const chunks = [];
256
const totalChunks = Math.ceil(data.length / this.chunkSize);
257
258
for (let i = 0; i < totalChunks; i++) {
259
const start = i * this.chunkSize;
260
const end = Math.min(start + this.chunkSize, data.length);
261
const chunk = data.subarray(start, end);
262
263
// Use chunk index as part of nonce (be careful in production!)
264
const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
265
const encrypted = sodium.crypto_secretbox_easy(chunk, nonce, this.key);
266
267
chunks.push({
268
index: i,
269
nonce: nonce,
270
data: encrypted
271
});
272
}
273
274
return chunks;
275
}
276
277
decryptStream(chunks) {
278
// Sort chunks by index
279
chunks.sort((a, b) => a.index - b.index);
280
281
const decryptedChunks = [];
282
for (const chunk of chunks) {
283
const decrypted = sodium.crypto_secretbox_open_easy(
284
chunk.data, chunk.nonce, this.key
285
);
286
decryptedChunks.push(decrypted);
287
}
288
289
// Concatenate all chunks
290
const totalLength = decryptedChunks.reduce((sum, chunk) => sum + chunk.length, 0);
291
const result = new Uint8Array(totalLength);
292
let offset = 0;
293
294
for (const chunk of decryptedChunks) {
295
result.set(chunk, offset);
296
offset += chunk.length;
297
}
298
299
return result;
300
}
301
}
302
303
// Usage with large file
304
const key = sodium.crypto_secretbox_keygen();
305
const streaming = new StreamingEncryption(key);
306
307
const largeData = new Uint8Array(100000); // 100KB
308
sodium.randombytes_buf_into(largeData);
309
310
console.time('Stream Encrypt');
311
const encryptedChunks = streaming.encryptStream(largeData);
312
console.timeEnd('Stream Encrypt');
313
314
console.time('Stream Decrypt');
315
const decryptedData = streaming.decryptStream(encryptedChunks);
316
console.timeEnd('Stream Decrypt');
317
318
console.log('Streaming successful:',
319
sodium.memcmp(largeData, decryptedData)); // true
320
```
321
322
### Password-Based Encryption
323
324
```javascript
325
// Combine with key derivation for password-based encryption
326
async function encryptWithPassword(message, password) {
327
// Derive key from password using Argon2id
328
const salt = sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES);
329
const key = sodium.crypto_pwhash(
330
sodium.crypto_secretbox_KEYBYTES,
331
sodium.from_string(password),
332
salt,
333
sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE,
334
sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE,
335
sodium.crypto_pwhash_ALG_ARGON2ID13
336
);
337
338
// Encrypt with derived key
339
const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
340
const ciphertext = sodium.crypto_secretbox_easy(message, nonce, key);
341
342
// Return salt, nonce, and ciphertext
343
return {
344
salt: sodium.to_base64(salt),
345
nonce: sodium.to_base64(nonce),
346
ciphertext: sodium.to_base64(ciphertext)
347
};
348
}
349
350
async function decryptWithPassword(encryptedData, password) {
351
const salt = sodium.from_base64(encryptedData.salt);
352
const nonce = sodium.from_base64(encryptedData.nonce);
353
const ciphertext = sodium.from_base64(encryptedData.ciphertext);
354
355
// Derive same key from password
356
const key = sodium.crypto_pwhash(
357
sodium.crypto_secretbox_KEYBYTES,
358
sodium.from_string(password),
359
salt,
360
sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE,
361
sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE,
362
sodium.crypto_pwhash_ALG_ARGON2ID13
363
);
364
365
// Decrypt
366
return sodium.crypto_secretbox_open_easy(ciphertext, nonce, key);
367
}
368
369
// Usage
370
const message = sodium.from_string('Password-protected message');
371
const encrypted = await encryptWithPassword(message, 'mypassword123');
372
const decrypted = await decryptWithPassword(encrypted, 'mypassword123');
373
374
console.log(sodium.to_string(decrypted)); // "Password-protected message"
375
```
376
377
### Performance Benchmarking
378
379
```javascript
380
function benchmarkSecretBox() {
381
const key = sodium.crypto_secretbox_keygen();
382
const message = new Uint8Array(1024 * 1024); // 1MB
383
sodium.randombytes_buf_into(message);
384
385
console.time('SecretBox Encrypt 1MB');
386
for (let i = 0; i < 10; i++) {
387
const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
388
const ciphertext = sodium.crypto_secretbox_easy(message, nonce, key);
389
}
390
console.timeEnd('SecretBox Encrypt 1MB');
391
392
// Test decryption
393
const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
394
const ciphertext = sodium.crypto_secretbox_easy(message, nonce, key);
395
396
console.time('SecretBox Decrypt 1MB');
397
for (let i = 0; i < 10; i++) {
398
const plaintext = sodium.crypto_secretbox_open_easy(ciphertext, nonce, key);
399
}
400
console.timeEnd('SecretBox Decrypt 1MB');
401
}
402
403
benchmarkSecretBox();
404
```
405
406
## Security Considerations
407
408
- **Nonce Uniqueness**: Never reuse nonces with the same key - this breaks security completely
409
- **Key Management**: Keep encryption keys secret and use crypto_secretbox_keygen() for generation
410
- **Nonce Generation**: Use randombytes_buf() for nonce generation
411
- **Authentication**: All secretbox functions provide authenticated encryption
412
- **Key Rotation**: Consider periodic key rotation for long-term use
413
- **Memory Security**: Use memzero() to clear sensitive data from memory
414
415
## Algorithm Selection Guide
416
417
- **Generic secretbox**: Good default choice, NaCl-compatible
418
- **XChaCha20 variant**: Better security margins, safer for high-volume applications
419
- **Detached mode**: When you need to store/transmit MAC separately
420
- **Password-based**: Combine with crypto_pwhash for user password encryption
421
422
SecretBox provides excellent performance and security for symmetric encryption needs.