0
# Secret Streams
1
2
Streaming authenticated encryption using XChaCha20-Poly1305 for encrypting sequences of messages with automatic key rotation and message ordering.
3
4
## Capabilities
5
6
### Key Generation
7
8
Generate keys for secret stream operations.
9
10
```javascript { .api }
11
/**
12
* Generate random key for secret stream encryption
13
* @param k - Output buffer for key (must be KEYBYTES long)
14
*/
15
function crypto_secretstream_xchacha20poly1305_keygen(k: Buffer): void;
16
```
17
18
**Usage Example:**
19
20
```javascript
21
const sodium = require('sodium-native');
22
23
// Generate key for secret stream
24
const key = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES);
25
sodium.crypto_secretstream_xchacha20poly1305_keygen(key);
26
```
27
28
### Push Stream Initialization
29
30
Initialize a secret stream for encryption (push mode).
31
32
```javascript { .api }
33
/**
34
* Initialize secret stream for encryption
35
* @param state - Output buffer for stream state (must be STATEBYTES long)
36
* @param header - Output buffer for stream header (must be HEADERBYTES long)
37
* @param k - Key buffer (must be KEYBYTES long)
38
* @throws Error if initialization fails or buffer sizes incorrect
39
*/
40
function crypto_secretstream_xchacha20poly1305_init_push(
41
state: Buffer,
42
header: Buffer,
43
k: Buffer
44
): void;
45
```
46
47
### Pull Stream Initialization
48
49
Initialize a secret stream for decryption (pull mode).
50
51
```javascript { .api }
52
/**
53
* Initialize secret stream for decryption
54
* @param state - Output buffer for stream state (must be STATEBYTES long)
55
* @param header - Stream header from push initialization (must be HEADERBYTES long)
56
* @param k - Key buffer (must be KEYBYTES long)
57
* @throws Error if initialization fails or buffer sizes incorrect
58
*/
59
function crypto_secretstream_xchacha20poly1305_init_pull(
60
state: Buffer,
61
header: Buffer,
62
k: Buffer
63
): void;
64
```
65
66
**Usage Example:**
67
68
```javascript
69
const sodium = require('sodium-native');
70
71
// Initialize encryption stream
72
const pushState = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_STATEBYTES);
73
const header = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES);
74
75
sodium.crypto_secretstream_xchacha20poly1305_init_push(pushState, header, key);
76
77
// Initialize decryption stream
78
const pullState = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_STATEBYTES);
79
sodium.crypto_secretstream_xchacha20poly1305_init_pull(pullState, header, key);
80
```
81
82
### Push (Encryption)
83
84
Encrypt and authenticate a message chunk in the stream.
85
86
```javascript { .api }
87
/**
88
* Encrypt message chunk in secret stream
89
* @param state - Stream state buffer from init_push
90
* @param c - Output buffer for ciphertext (must be m.length + ABYTES)
91
* @param m - Message buffer to encrypt
92
* @param ad - Optional additional data buffer (can be null)
93
* @param tag - Message tag indicating message type/position
94
* @returns Number of bytes written to ciphertext buffer
95
* @throws Error if encryption fails
96
*/
97
function crypto_secretstream_xchacha20poly1305_push(
98
state: Buffer,
99
c: Buffer,
100
m: Buffer,
101
ad: Buffer | null,
102
tag: number
103
): number;
104
```
105
106
### Pull (Decryption)
107
108
Decrypt and verify a message chunk from the stream.
109
110
```javascript { .api }
111
/**
112
* Decrypt and verify message chunk from secret stream
113
* @param state - Stream state buffer from init_pull
114
* @param m - Output buffer for plaintext (must be c.length - ABYTES)
115
* @param tag - Output buffer for message tag (must be TAGBYTES long)
116
* @param c - Ciphertext buffer to decrypt
117
* @param ad - Optional additional data buffer (can be null)
118
* @returns Number of bytes written to plaintext buffer
119
* @throws Error if decryption or verification fails
120
*/
121
function crypto_secretstream_xchacha20poly1305_pull(
122
state: Buffer,
123
m: Buffer,
124
tag: Buffer,
125
c: Buffer,
126
ad: Buffer | null
127
): number;
128
```
129
130
**Usage Example:**
131
132
```javascript
133
const sodium = require('sodium-native');
134
135
// Encrypt message chunks
136
const message1 = Buffer.from('First message');
137
const message2 = Buffer.from('Second message');
138
const message3 = Buffer.from('Final message');
139
140
// Encrypt first message
141
const ciphertext1 = Buffer.alloc(message1.length + sodium.crypto_secretstream_xchacha20poly1305_ABYTES);
142
const len1 = sodium.crypto_secretstream_xchacha20poly1305_push(
143
pushState,
144
ciphertext1,
145
message1,
146
null,
147
sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE
148
);
149
150
// Encrypt second message
151
const ciphertext2 = Buffer.alloc(message2.length + sodium.crypto_secretstream_xchacha20poly1305_ABYTES);
152
const len2 = sodium.crypto_secretstream_xchacha20poly1305_push(
153
pushState,
154
ciphertext2,
155
message2,
156
null,
157
sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE
158
);
159
160
// Encrypt final message
161
const ciphertext3 = Buffer.alloc(message3.length + sodium.crypto_secretstream_xchacha20poly1305_ABYTES);
162
const len3 = sodium.crypto_secretstream_xchacha20poly1305_push(
163
pushState,
164
ciphertext3,
165
message3,
166
null,
167
sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
168
);
169
170
// Decrypt message chunks
171
const plaintext1 = Buffer.alloc(ciphertext1.length - sodium.crypto_secretstream_xchacha20poly1305_ABYTES);
172
const tag1 = Buffer.alloc(1);
173
sodium.crypto_secretstream_xchacha20poly1305_pull(pullState, plaintext1, tag1, ciphertext1, null);
174
175
console.log('Decrypted:', plaintext1.toString());
176
console.log('Tag:', tag1[0]); // Should be TAG_MESSAGE
177
```
178
179
### Key Rotation
180
181
Rotate the stream key to provide forward secrecy.
182
183
```javascript { .api }
184
/**
185
* Rotate stream key for forward secrecy
186
* @param state - Stream state buffer to rekey
187
*/
188
function crypto_secretstream_xchacha20poly1305_rekey(state: Buffer): void;
189
```
190
191
**Usage Example:**
192
193
```javascript
194
const sodium = require('sodium-native');
195
196
// Rotate key after processing sensitive data
197
sodium.crypto_secretstream_xchacha20poly1305_rekey(pushState);
198
199
// Messages encrypted after rekey cannot be decrypted with old state
200
const sensitiveMessage = Buffer.from('Top secret data');
201
const encryptedSensitive = Buffer.alloc(sensitiveMessage.length + sodium.crypto_secretstream_xchacha20poly1305_ABYTES);
202
203
sodium.crypto_secretstream_xchacha20poly1305_push(
204
pushState,
205
encryptedSensitive,
206
sensitiveMessage,
207
null,
208
sodium.crypto_secretstream_xchacha20poly1305_TAG_REKEY
209
);
210
```
211
212
## Constants
213
214
```javascript { .api }
215
// State buffer size in bytes
216
const crypto_secretstream_xchacha20poly1305_STATEBYTES: number;
217
218
// Authentication tag size in bytes
219
const crypto_secretstream_xchacha20poly1305_ABYTES: number;
220
221
// Stream header size in bytes
222
const crypto_secretstream_xchacha20poly1305_HEADERBYTES: number;
223
224
// Key size in bytes
225
const crypto_secretstream_xchacha20poly1305_KEYBYTES: number;
226
227
// Message tag size in bytes
228
const crypto_secretstream_xchacha20poly1305_TAGBYTES: number;
229
230
// Maximum message size in bytes
231
const crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX: number;
232
233
// Message tags for different message types
234
const crypto_secretstream_xchacha20poly1305_TAG_MESSAGE: number;
235
const crypto_secretstream_xchacha20poly1305_TAG_PUSH: number;
236
const crypto_secretstream_xchacha20poly1305_TAG_REKEY: number;
237
const crypto_secretstream_xchacha20poly1305_TAG_FINAL: number;
238
```
239
240
## Message Tags
241
242
Secret streams use tags to indicate message types and stream state:
243
244
- **TAG_MESSAGE**: Regular message in the stream
245
- **TAG_PUSH**: End of a push sequence (commit point)
246
- **TAG_REKEY**: Key rotation occurred (forward secrecy)
247
- **TAG_FINAL**: Final message in the stream
248
249
## Security Considerations
250
251
- **Forward Secrecy**: Use TAG_REKEY and `rekey()` function to prevent decryption of future messages if state is compromised.
252
- **Message Ordering**: Secret streams enforce message ordering and prevent replay attacks.
253
- **Stream Integrity**: Each message is authenticated within the context of the entire stream.
254
- **Key Management**: Store and transmit the stream header securely alongside the first ciphertext.
255
256
## Common Patterns
257
258
### Secure File Streaming
259
260
```javascript
261
const sodium = require('sodium-native');
262
const fs = require('fs');
263
264
class SecureFileStream {
265
constructor(key) {
266
this.key = key || Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_KEYBYTES);
267
if (!key) {
268
sodium.crypto_secretstream_xchacha20poly1305_keygen(this.key);
269
}
270
}
271
272
encryptFile(inputFile, outputFile) {
273
const state = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_STATEBYTES);
274
const header = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES);
275
276
sodium.crypto_secretstream_xchacha20poly1305_init_push(state, header, this.key);
277
278
const input = fs.createReadStream(inputFile, { highWaterMark: 4096 });
279
const output = fs.createWriteStream(outputFile);
280
281
// Write header first
282
output.write(header);
283
284
let isFirstChunk = true;
285
let isLastChunk = false;
286
287
input.on('data', (chunk) => {
288
const ciphertext = Buffer.alloc(chunk.length + sodium.crypto_secretstream_xchacha20poly1305_ABYTES);
289
290
// Determine tag based on position
291
let tag = sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
292
if (isFirstChunk) {
293
tag = sodium.crypto_secretstream_xchacha20poly1305_TAG_PUSH;
294
isFirstChunk = false;
295
}
296
297
const len = sodium.crypto_secretstream_xchacha20poly1305_push(
298
state, ciphertext, chunk, null, tag
299
);
300
301
output.write(ciphertext.subarray(0, len));
302
});
303
304
input.on('end', () => {
305
// Write final empty message with TAG_FINAL
306
const finalCiphertext = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_ABYTES);
307
const len = sodium.crypto_secretstream_xchacha20poly1305_push(
308
state, finalCiphertext, Buffer.alloc(0), null,
309
sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
310
);
311
312
output.write(finalCiphertext.subarray(0, len));
313
output.end();
314
});
315
}
316
317
decryptFile(inputFile, outputFile) {
318
const encryptedData = fs.readFileSync(inputFile);
319
const header = encryptedData.subarray(0, sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES);
320
321
const state = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_STATEBYTES);
322
sodium.crypto_secretstream_xchacha20poly1305_init_pull(state, header, this.key);
323
324
const output = fs.createWriteStream(outputFile);
325
let offset = sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES;
326
327
while (offset < encryptedData.length) {
328
// Read chunk length (simplified - real implementation needs proper framing)
329
const chunkSize = Math.min(4096 + sodium.crypto_secretstream_xchacha20poly1305_ABYTES,
330
encryptedData.length - offset);
331
332
const ciphertext = encryptedData.subarray(offset, offset + chunkSize);
333
const plaintext = Buffer.alloc(ciphertext.length - sodium.crypto_secretstream_xchacha20poly1305_ABYTES);
334
const tag = Buffer.alloc(1);
335
336
const len = sodium.crypto_secretstream_xchacha20poly1305_pull(
337
state, plaintext, tag, ciphertext, null
338
);
339
340
if (len > 0) {
341
output.write(plaintext.subarray(0, len));
342
}
343
344
// Check for final tag
345
if (tag[0] === sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL) {
346
break;
347
}
348
349
offset += chunkSize;
350
}
351
352
output.end();
353
}
354
}
355
```
356
357
### Network Protocol with Forward Secrecy
358
359
```javascript
360
const sodium = require('sodium-native');
361
362
class SecureProtocol {
363
constructor() {
364
this.sendState = null;
365
this.receiveState = null;
366
this.messageCount = 0;
367
}
368
369
initializeSession(key, isInitiator = true) {
370
const header = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES);
371
372
if (isInitiator) {
373
this.sendState = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_STATEBYTES);
374
sodium.crypto_secretstream_xchacha20poly1305_init_push(this.sendState, header, key);
375
return header; // Send to peer
376
} else {
377
this.receiveState = Buffer.alloc(sodium.crypto_secretstream_xchacha20poly1305_STATEBYTES);
378
sodium.crypto_secretstream_xchacha20poly1305_init_pull(this.receiveState, header, key);
379
}
380
}
381
382
sendMessage(message, isImportant = false) {
383
if (!this.sendState) throw new Error('Send state not initialized');
384
385
const ciphertext = Buffer.alloc(message.length + sodium.crypto_secretstream_xchacha20poly1305_ABYTES);
386
387
// Use different tags for message importance
388
const tag = isImportant ?
389
sodium.crypto_secretstream_xchacha20poly1305_TAG_PUSH :
390
sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
391
392
const len = sodium.crypto_secretstream_xchacha20poly1305_push(
393
this.sendState, ciphertext, message, null, tag
394
);
395
396
this.messageCount++;
397
398
// Rotate key every 1000 messages for forward secrecy
399
if (this.messageCount % 1000 === 0) {
400
sodium.crypto_secretstream_xchacha20poly1305_rekey(this.sendState);
401
}
402
403
return ciphertext.subarray(0, len);
404
}
405
406
receiveMessage(ciphertext) {
407
if (!this.receiveState) throw new Error('Receive state not initialized');
408
409
const plaintext = Buffer.alloc(ciphertext.length - sodium.crypto_secretstream_xchacha20poly1305_ABYTES);
410
const tag = Buffer.alloc(1);
411
412
const len = sodium.crypto_secretstream_xchacha20poly1305_pull(
413
this.receiveState, plaintext, tag, ciphertext, null
414
);
415
416
// Handle different message tags
417
switch (tag[0]) {
418
case sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE:
419
return { message: plaintext.subarray(0, len), type: 'regular' };
420
case sodium.crypto_secretstream_xchacha20poly1305_TAG_PUSH:
421
return { message: plaintext.subarray(0, len), type: 'important' };
422
case sodium.crypto_secretstream_xchacha20poly1305_TAG_REKEY:
423
sodium.crypto_secretstream_xchacha20poly1305_rekey(this.receiveState);
424
return { message: plaintext.subarray(0, len), type: 'rekey' };
425
case sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL:
426
return { message: plaintext.subarray(0, len), type: 'final' };
427
default:
428
throw new Error('Unknown message tag');
429
}
430
}
431
}
432
```