0
# Authenticated Encryption with Additional Data (AEAD)
1
2
Modern authenticated encryption schemes using ChaCha20-Poly1305 and XChaCha20-Poly1305 for secure encryption with additional data authentication.
3
4
## Capabilities
5
6
### XChaCha20-Poly1305-IETF AEAD
7
8
Extended nonce authenticated encryption suitable for high-volume applications.
9
10
```javascript { .api }
11
/**
12
* Generate random key for XChaCha20-Poly1305-IETF AEAD
13
* @param k - Output buffer for key (must be KEYBYTES long)
14
*/
15
function crypto_aead_xchacha20poly1305_ietf_keygen(k: Buffer): void;
16
17
/**
18
* Encrypt message with XChaCha20-Poly1305-IETF AEAD
19
* @param c - Output buffer for ciphertext (must be m.length + ABYTES)
20
* @param m - Message buffer to encrypt
21
* @param ad - Additional data buffer to authenticate (can be null)
22
* @param nsec - Must always be null (reserved parameter)
23
* @param npub - Nonce/public number buffer (must be NPUBBYTES long)
24
* @param k - Key buffer (must be KEYBYTES long)
25
* @returns Number of bytes written to ciphertext buffer
26
* @throws Error if encryption fails or parameters invalid
27
*/
28
function crypto_aead_xchacha20poly1305_ietf_encrypt(
29
c: Buffer,
30
m: Buffer,
31
ad: Buffer | null,
32
nsec: null,
33
npub: Buffer,
34
k: Buffer
35
): number;
36
37
/**
38
* Decrypt and verify with XChaCha20-Poly1305-IETF AEAD
39
* @param m - Output buffer for plaintext (must be c.length - ABYTES)
40
* @param nsec - Must always be null (reserved parameter)
41
* @param c - Ciphertext buffer to decrypt
42
* @param ad - Additional data buffer that was authenticated (can be null)
43
* @param npub - Nonce/public number buffer (must be NPUBBYTES long)
44
* @param k - Key buffer (must be KEYBYTES long)
45
* @returns Number of bytes written to plaintext buffer
46
* @throws Error if decryption fails or authentication invalid
47
*/
48
function crypto_aead_xchacha20poly1305_ietf_decrypt(
49
m: Buffer,
50
nsec: null,
51
c: Buffer,
52
ad: Buffer | null,
53
npub: Buffer,
54
k: Buffer
55
): number;
56
```
57
58
**Usage Example:**
59
60
```javascript
61
const sodium = require('sodium-native');
62
63
// Generate key and nonce
64
const key = Buffer.alloc(sodium.crypto_aead_xchacha20poly1305_ietf_KEYBYTES);
65
const nonce = Buffer.alloc(sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
66
sodium.crypto_aead_xchacha20poly1305_ietf_keygen(key);
67
sodium.randombytes_buf(nonce);
68
69
// Encrypt with additional data
70
const message = Buffer.from('Secret message');
71
const additionalData = Buffer.from('header info');
72
const ciphertext = Buffer.alloc(message.length + sodium.crypto_aead_xchacha20poly1305_ietf_ABYTES);
73
74
const cipherLen = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
75
ciphertext, message, additionalData, null, nonce, key
76
);
77
78
// Decrypt and verify
79
const plaintext = Buffer.alloc(ciphertext.length - sodium.crypto_aead_xchacha20poly1305_ietf_ABYTES);
80
const plainLen = sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
81
plaintext, null, ciphertext, additionalData, nonce, key
82
);
83
84
console.log('Decrypted:', plaintext.subarray(0, plainLen).toString());
85
```
86
87
### XChaCha20-Poly1305-IETF Detached Mode
88
89
Encrypt with authentication tag stored separately from ciphertext.
90
91
```javascript { .api }
92
/**
93
* Encrypt with XChaCha20-Poly1305-IETF AEAD (detached mode)
94
* @param c - Output buffer for ciphertext (must be same length as message)
95
* @param mac - Output buffer for authentication tag (must be ABYTES long)
96
* @param m - Message buffer to encrypt
97
* @param ad - Additional data buffer to authenticate (can be null)
98
* @param nsec - Must always be null (reserved parameter)
99
* @param npub - Nonce/public number buffer (must be NPUBBYTES long)
100
* @param k - Key buffer (must be KEYBYTES long)
101
* @returns Number of bytes written to ciphertext buffer
102
* @throws Error if encryption fails
103
*/
104
function crypto_aead_xchacha20poly1305_ietf_encrypt_detached(
105
c: Buffer,
106
mac: Buffer,
107
m: Buffer,
108
ad: Buffer | null,
109
nsec: null,
110
npub: Buffer,
111
k: Buffer
112
): number;
113
114
/**
115
* Decrypt with XChaCha20-Poly1305-IETF AEAD (detached mode)
116
* @param m - Output buffer for plaintext (must be same length as ciphertext)
117
* @param nsec - Must always be null (reserved parameter)
118
* @param c - Ciphertext buffer to decrypt
119
* @param mac - Authentication tag buffer (must be ABYTES long)
120
* @param ad - Additional data buffer that was authenticated (can be null)
121
* @param npub - Nonce/public number buffer (must be NPUBBYTES long)
122
* @param k - Key buffer (must be KEYBYTES long)
123
* @throws Error if decryption fails or authentication invalid
124
*/
125
function crypto_aead_xchacha20poly1305_ietf_decrypt_detached(
126
m: Buffer,
127
nsec: null,
128
c: Buffer,
129
mac: Buffer,
130
ad: Buffer | null,
131
npub: Buffer,
132
k: Buffer
133
): void;
134
```
135
136
### ChaCha20-Poly1305-IETF AEAD
137
138
Standard IETF ChaCha20-Poly1305 authenticated encryption.
139
140
```javascript { .api }
141
/**
142
* Generate random key for ChaCha20-Poly1305-IETF AEAD
143
* @param k - Output buffer for key (must be KEYBYTES long)
144
*/
145
function crypto_aead_chacha20poly1305_ietf_keygen(k: Buffer): void;
146
147
/**
148
* Encrypt message with ChaCha20-Poly1305-IETF AEAD
149
* @param c - Output buffer for ciphertext (must be m.length + ABYTES)
150
* @param m - Message buffer to encrypt
151
* @param ad - Additional data buffer to authenticate (can be null)
152
* @param nsec - Must always be null (reserved parameter)
153
* @param npub - Nonce/public number buffer (must be NPUBBYTES long)
154
* @param k - Key buffer (must be KEYBYTES long)
155
* @returns Number of bytes written to ciphertext buffer
156
* @throws Error if encryption fails or parameters invalid
157
*/
158
function crypto_aead_chacha20poly1305_ietf_encrypt(
159
c: Buffer,
160
m: Buffer,
161
ad: Buffer | null,
162
nsec: null,
163
npub: Buffer,
164
k: Buffer
165
): number;
166
167
/**
168
* Decrypt and verify with ChaCha20-Poly1305-IETF AEAD
169
* @param m - Output buffer for plaintext (must be c.length - ABYTES)
170
* @param nsec - Must always be null (reserved parameter)
171
* @param c - Ciphertext buffer to decrypt
172
* @param ad - Additional data buffer that was authenticated (can be null)
173
* @param npub - Nonce/public number buffer (must be NPUBBYTES long)
174
* @param k - Key buffer (must be KEYBYTES long)
175
* @returns Number of bytes written to plaintext buffer
176
* @throws Error if decryption fails or authentication invalid
177
*/
178
function crypto_aead_chacha20poly1305_ietf_decrypt(
179
m: Buffer,
180
nsec: null,
181
c: Buffer,
182
ad: Buffer | null,
183
npub: Buffer,
184
k: Buffer
185
): number;
186
```
187
188
### ChaCha20-Poly1305-IETF Detached Mode
189
190
```javascript { .api }
191
/**
192
* Encrypt with ChaCha20-Poly1305-IETF AEAD (detached mode)
193
* @param c - Output buffer for ciphertext (must be same length as message)
194
* @param mac - Output buffer for authentication tag (must be ABYTES long)
195
* @param m - Message buffer to encrypt
196
* @param ad - Additional data buffer to authenticate (can be null)
197
* @param nsec - Must always be null (reserved parameter)
198
* @param npub - Nonce/public number buffer (must be NPUBBYTES long)
199
* @param k - Key buffer (must be KEYBYTES long)
200
* @returns Number of bytes written to ciphertext buffer
201
* @throws Error if encryption fails
202
*/
203
function crypto_aead_chacha20poly1305_ietf_encrypt_detached(
204
c: Buffer,
205
mac: Buffer,
206
m: Buffer,
207
ad: Buffer | null,
208
nsec: null,
209
npub: Buffer,
210
k: Buffer
211
): number;
212
213
/**
214
* Decrypt with ChaCha20-Poly1305-IETF AEAD (detached mode)
215
* @param m - Output buffer for plaintext (must be same length as ciphertext)
216
* @param nsec - Must always be null (reserved parameter)
217
* @param c - Ciphertext buffer to decrypt
218
* @param mac - Authentication tag buffer (must be ABYTES long)
219
* @param ad - Additional data buffer that was authenticated (can be null)
220
* @param npub - Nonce/public number buffer (must be NPUBBYTES long)
221
* @param k - Key buffer (must be KEYBYTES long)
222
* @throws Error if decryption fails or authentication invalid
223
*/
224
function crypto_aead_chacha20poly1305_ietf_decrypt_detached(
225
m: Buffer,
226
nsec: null,
227
c: Buffer,
228
mac: Buffer,
229
ad: Buffer | null,
230
npub: Buffer,
231
k: Buffer
232
): void;
233
```
234
235
**Usage Example:**
236
237
```javascript
238
const sodium = require('sodium-native');
239
240
// Standard ChaCha20-Poly1305-IETF
241
const key = Buffer.alloc(sodium.crypto_aead_chacha20poly1305_ietf_KEYBYTES);
242
const nonce = Buffer.alloc(sodium.crypto_aead_chacha20poly1305_ietf_NPUBBYTES);
243
sodium.crypto_aead_chacha20poly1305_ietf_keygen(key);
244
sodium.randombytes_buf(nonce);
245
246
const message = Buffer.from('Important data');
247
const header = Buffer.from('v1.0|json|gzip');
248
249
// Encrypt with header as additional data
250
const ciphertext = Buffer.alloc(message.length + sodium.crypto_aead_chacha20poly1305_ietf_ABYTES);
251
const cipherLen = sodium.crypto_aead_chacha20poly1305_ietf_encrypt(
252
ciphertext, message, header, null, nonce, key
253
);
254
255
// Decrypt and authenticate header
256
const plaintext = Buffer.alloc(ciphertext.length - sodium.crypto_aead_chacha20poly1305_ietf_ABYTES);
257
const plainLen = sodium.crypto_aead_chacha20poly1305_ietf_decrypt(
258
plaintext, null, ciphertext, header, nonce, key
259
);
260
```
261
262
## Constants
263
264
```javascript { .api }
265
// XChaCha20-Poly1305-IETF constants
266
const crypto_aead_xchacha20poly1305_ietf_ABYTES: number;
267
const crypto_aead_xchacha20poly1305_ietf_KEYBYTES: number;
268
const crypto_aead_xchacha20poly1305_ietf_NPUBBYTES: number;
269
const crypto_aead_xchacha20poly1305_ietf_NSECBYTES: number;
270
const crypto_aead_xchacha20poly1305_ietf_MESSAGEBYTES_MAX: number;
271
272
// ChaCha20-Poly1305-IETF constants
273
const crypto_aead_chacha20poly1305_ietf_ABYTES: number;
274
const crypto_aead_chacha20poly1305_ietf_KEYBYTES: number;
275
const crypto_aead_chacha20poly1305_ietf_NPUBBYTES: number;
276
const crypto_aead_chacha20poly1305_ietf_NSECBYTES: number;
277
const crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX: number;
278
```
279
280
## Security Considerations
281
282
- **Nonce Management**: Never reuse nonces with the same key. XChaCha20 allows larger nonces for easier management.
283
- **Additional Data**: Additional data is authenticated but not encrypted. Use for headers, metadata, etc.
284
- **Key Reuse**: Keys can be safely reused with different nonces, but consider key rotation for long-term use.
285
- **Message Limits**: Respect maximum message size limits for each variant.
286
287
## Common Patterns
288
289
### API Request/Response Encryption
290
291
```javascript
292
const sodium = require('sodium-native');
293
294
class APIEncryption {
295
constructor() {
296
this.key = Buffer.alloc(sodium.crypto_aead_xchacha20poly1305_ietf_KEYBYTES);
297
sodium.crypto_aead_xchacha20poly1305_ietf_keygen(this.key);
298
}
299
300
encryptRequest(method, url, body) {
301
const nonce = Buffer.alloc(sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
302
sodium.randombytes_buf(nonce);
303
304
// Use HTTP method and URL as additional data
305
const additionalData = Buffer.from(`${method} ${url}`);
306
const messageBuffer = Buffer.from(JSON.stringify(body));
307
308
const ciphertext = Buffer.alloc(
309
messageBuffer.length + sodium.crypto_aead_xchacha20poly1305_ietf_ABYTES
310
);
311
312
const cipherLen = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
313
ciphertext,
314
messageBuffer,
315
additionalData,
316
null,
317
nonce,
318
this.key
319
);
320
321
return {
322
nonce: nonce.toString('base64'),
323
ciphertext: ciphertext.subarray(0, cipherLen).toString('base64'),
324
method,
325
url
326
};
327
}
328
329
decryptRequest(encryptedRequest) {
330
const nonce = Buffer.from(encryptedRequest.nonce, 'base64');
331
const ciphertext = Buffer.from(encryptedRequest.ciphertext, 'base64');
332
const additionalData = Buffer.from(`${encryptedRequest.method} ${encryptedRequest.url}`);
333
334
const plaintext = Buffer.alloc(
335
ciphertext.length - sodium.crypto_aead_xchacha20poly1305_ietf_ABYTES
336
);
337
338
const plainLen = sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
339
plaintext,
340
null,
341
ciphertext,
342
additionalData,
343
nonce,
344
this.key
345
);
346
347
return JSON.parse(plaintext.subarray(0, plainLen).toString());
348
}
349
}
350
```
351
352
### Database Field Encryption
353
354
```javascript
355
const sodium = require('sodium-native');
356
357
class FieldEncryption {
358
constructor(masterKey) {
359
this.masterKey = Buffer.from(masterKey);
360
}
361
362
// Derive field-specific key from master key
363
deriveFieldKey(tableName, fieldName) {
364
const context = Buffer.from(`${tableName}.${fieldName}`);
365
const fieldKey = Buffer.alloc(sodium.crypto_aead_xchacha20poly1305_ietf_KEYBYTES);
366
367
// Simple key derivation (use proper KDF in production)
368
const combined = Buffer.concat([this.masterKey, context]);
369
sodium.crypto_generichash(fieldKey, combined);
370
371
return fieldKey;
372
}
373
374
encryptField(tableName, fieldName, value, recordId) {
375
const fieldKey = this.deriveFieldKey(tableName, fieldName);
376
const nonce = Buffer.alloc(sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
377
sodium.randombytes_buf(nonce);
378
379
// Use record ID as additional data for integrity
380
const additionalData = Buffer.from(recordId.toString());
381
const plaintext = Buffer.from(value);
382
383
const ciphertext = Buffer.alloc(
384
plaintext.length + sodium.crypto_aead_xchacha20poly1305_ietf_ABYTES
385
);
386
387
const cipherLen = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
388
ciphertext,
389
plaintext,
390
additionalData,
391
null,
392
nonce,
393
fieldKey
394
);
395
396
// Return as base64 with nonce prepended
397
const encrypted = Buffer.concat([nonce, ciphertext.subarray(0, cipherLen)]);
398
return encrypted.toString('base64');
399
}
400
401
decryptField(tableName, fieldName, encryptedValue, recordId) {
402
const fieldKey = this.deriveFieldKey(tableName, fieldName);
403
const encrypted = Buffer.from(encryptedValue, 'base64');
404
405
const nonce = encrypted.subarray(0, sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
406
const ciphertext = encrypted.subarray(sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
407
const additionalData = Buffer.from(recordId.toString());
408
409
const plaintext = Buffer.alloc(
410
ciphertext.length - sodium.crypto_aead_xchacha20poly1305_ietf_ABYTES
411
);
412
413
const plainLen = sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
414
plaintext,
415
null,
416
ciphertext,
417
additionalData,
418
nonce,
419
fieldKey
420
);
421
422
return plaintext.subarray(0, plainLen).toString();
423
}
424
}
425
426
// Usage
427
const encryption = new FieldEncryption('master-key-32-bytes-long-exactly!');
428
429
// Encrypt sensitive field
430
const encryptedSSN = encryption.encryptField('users', 'ssn', '123-45-6789', 12345);
431
432
// Decrypt when needed
433
const originalSSN = encryption.decryptField('users', 'ssn', encryptedSSN, 12345);
434
```
435
436
### File Encryption with Compression
437
438
```javascript
439
const sodium = require('sodium-native');
440
const zlib = require('zlib');
441
442
class SecureFileStorage {
443
constructor() {
444
this.key = Buffer.alloc(sodium.crypto_aead_xchacha20poly1305_ietf_KEYBYTES);
445
sodium.crypto_aead_xchacha20poly1305_ietf_keygen(this.key);
446
}
447
448
async encryptFile(filename, fileData) {
449
// Compress data first
450
const compressed = zlib.gzipSync(fileData);
451
452
const nonce = Buffer.alloc(sodium.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
453
sodium.randombytes_buf(nonce);
454
455
// Use filename as additional data
456
const additionalData = Buffer.from(filename);
457
458
const ciphertext = Buffer.alloc(
459
compressed.length + sodium.crypto_aead_xchacha20poly1305_ietf_ABYTES
460
);
461
462
const cipherLen = sodium.crypto_aead_xchacha20poly1305_ietf_encrypt(
463
ciphertext,
464
compressed,
465
additionalData,
466
null,
467
nonce,
468
this.key
469
);
470
471
// Return file package with metadata
472
return {
473
filename,
474
nonce: nonce.toString('hex'),
475
ciphertext: ciphertext.subarray(0, cipherLen).toString('hex'),
476
compressed: true,
477
originalSize: fileData.length,
478
encryptedSize: cipherLen
479
};
480
}
481
482
async decryptFile(encryptedFile) {
483
const nonce = Buffer.from(encryptedFile.nonce, 'hex');
484
const ciphertext = Buffer.from(encryptedFile.ciphertext, 'hex');
485
const additionalData = Buffer.from(encryptedFile.filename);
486
487
const compressed = Buffer.alloc(
488
ciphertext.length - sodium.crypto_aead_xchacha20poly1305_ietf_ABYTES
489
);
490
491
const compressedLen = sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
492
compressed,
493
null,
494
ciphertext,
495
additionalData,
496
nonce,
497
this.key
498
);
499
500
// Decompress if compressed
501
if (encryptedFile.compressed) {
502
return zlib.gunzipSync(compressed.subarray(0, compressedLen));
503
}
504
505
return compressed.subarray(0, compressedLen);
506
}
507
}
508
```