0
# Key Derivation and Exchange
1
2
Key derivation functions (KDF) and key exchange (KX) protocols enable the generation of multiple keys from a master key and the establishment of shared secrets between parties. These functions are essential for key management, protocol implementation, and cryptographic key hierarchies.
3
4
## Key Derivation Function (KDF)
5
6
KDF functions derive multiple subkeys from a single master key using BLAKE2b internally.
7
8
### Master Key Generation
9
10
```javascript { .api }
11
/**
12
* Generate a random master key for key derivation
13
* @returns Uint8Array - 32-byte master key
14
*/
15
function crypto_kdf_keygen(): Uint8Array;
16
```
17
18
### Key Derivation
19
20
```javascript { .api }
21
/**
22
* Derive a subkey from master key
23
* @param subkey_len - Length of derived key (1-64 bytes)
24
* @param subkey_id - Unique identifier for this subkey (64-bit integer)
25
* @param ctx - Context string (exactly 8 bytes)
26
* @param key - 32-byte master key
27
* @returns Uint8Array - Derived subkey
28
*/
29
function crypto_kdf_derive_from_key(
30
subkey_len: number,
31
subkey_id: number,
32
ctx: string,
33
key: Uint8Array
34
): Uint8Array;
35
```
36
37
### KDF Constants
38
39
```javascript { .api }
40
const crypto_kdf_KEYBYTES: number; // 32 (master key length)
41
const crypto_kdf_BYTES_MIN: number; // 16 (minimum subkey length)
42
const crypto_kdf_BYTES_MAX: number; // 64 (maximum subkey length)
43
const crypto_kdf_CONTEXTBYTES: number; // 8 (context string length)
44
45
// BLAKE2b KDF specific constants
46
const crypto_kdf_blake2b_KEYBYTES: number; // 32
47
const crypto_kdf_blake2b_BYTES_MIN: number; // 16
48
const crypto_kdf_blake2b_BYTES_MAX: number; // 64
49
const crypto_kdf_blake2b_CONTEXTBYTES: number; // 8
50
```
51
52
## Key Exchange (KX)
53
54
Key exchange functions establish shared secrets between clients and servers using Curve25519.
55
56
### Key Pair Generation
57
58
```javascript { .api }
59
/**
60
* Generate a random Curve25519 key pair for key exchange
61
* @returns Object with publicKey and privateKey properties
62
*/
63
function crypto_kx_keypair(): {
64
publicKey: Uint8Array; // 32 bytes
65
privateKey: Uint8Array; // 32 bytes
66
};
67
68
/**
69
* Generate deterministic key pair from seed
70
* @param seed - 32-byte seed for reproducible key generation
71
* @returns Object with publicKey and privateKey properties
72
*/
73
function crypto_kx_seed_keypair(seed: Uint8Array): {
74
publicKey: Uint8Array;
75
privateKey: Uint8Array;
76
};
77
```
78
79
### Client Session Keys
80
81
```javascript { .api }
82
/**
83
* Compute session keys from client perspective
84
* @param clientPublicKey - Client's public key
85
* @param clientSecretKey - Client's private key
86
* @param serverPublicKey - Server's public key
87
* @returns Object with sharedRx (receive) and sharedTx (transmit) keys
88
*/
89
function crypto_kx_client_session_keys(
90
clientPublicKey: Uint8Array,
91
clientSecretKey: Uint8Array,
92
serverPublicKey: Uint8Array
93
): {
94
sharedRx: Uint8Array; // 32 bytes - for receiving data from server
95
sharedTx: Uint8Array; // 32 bytes - for sending data to server
96
};
97
```
98
99
### Server Session Keys
100
101
```javascript { .api }
102
/**
103
* Compute session keys from server perspective
104
* @param serverPublicKey - Server's public key
105
* @param serverSecretKey - Server's private key
106
* @param clientPublicKey - Client's public key
107
* @returns Object with sharedRx (receive) and sharedTx (transmit) keys
108
*/
109
function crypto_kx_server_session_keys(
110
serverPublicKey: Uint8Array,
111
serverSecretKey: Uint8Array,
112
clientPublicKey: Uint8Array
113
): {
114
sharedRx: Uint8Array; // 32 bytes - for receiving data from client
115
sharedTx: Uint8Array; // 32 bytes - for sending data to client
116
};
117
```
118
119
### KX Constants
120
121
```javascript { .api }
122
const crypto_kx_PUBLICKEYBYTES: number; // 32 (public key length)
123
const crypto_kx_SECRETKEYBYTES: number; // 32 (private key length)
124
const crypto_kx_SEEDBYTES: number; // 32 (seed length)
125
const crypto_kx_SESSIONKEYBYTES: number; // 32 (session key length)
126
```
127
128
## HKDF (HMAC-based Key Derivation)
129
130
Additional key derivation functions based on HMAC for standards compliance.
131
132
### HKDF-SHA256
133
134
```javascript { .api }
135
// HKDF-SHA256 constants
136
const crypto_kdf_hkdf_sha256_KEYBYTES: number; // 32
137
const crypto_kdf_hkdf_sha256_BYTES_MIN: number; // 1
138
const crypto_kdf_hkdf_sha256_BYTES_MAX: number; // 8160
139
```
140
141
### HKDF-SHA512
142
143
```javascript { .api }
144
// HKDF-SHA512 constants
145
const crypto_kdf_hkdf_sha512_KEYBYTES: number; // 32
146
const crypto_kdf_hkdf_sha512_BYTES_MIN: number; // 1
147
const crypto_kdf_hkdf_sha512_BYTES_MAX: number; // 16320
148
```
149
150
## Usage Examples
151
152
### Basic Key Derivation
153
154
```javascript
155
import _sodium from 'libsodium-wrappers-sumo';
156
await _sodium.ready;
157
const sodium = _sodium;
158
159
// Generate master key
160
const masterKey = sodium.crypto_kdf_keygen();
161
console.log('Master key:', sodium.to_hex(masterKey));
162
163
// Derive multiple subkeys for different purposes
164
const encryptionKey = sodium.crypto_kdf_derive_from_key(
165
32, // 32-byte key
166
1, // subkey ID 1
167
'encrypt_', // context (exactly 8 chars)
168
masterKey
169
);
170
171
const authKey = sodium.crypto_kdf_derive_from_key(
172
32, // 32-byte key
173
2, // subkey ID 2
174
'auth____', // context (exactly 8 chars)
175
masterKey
176
);
177
178
const signingKey = sodium.crypto_kdf_derive_from_key(
179
32, // 32-byte key
180
3, // subkey ID 3
181
'signing_', // context (exactly 8 chars)
182
masterKey
183
);
184
185
console.log('Encryption key:', sodium.to_hex(encryptionKey));
186
console.log('Auth key:', sodium.to_hex(authKey));
187
console.log('Signing key:', sodium.to_hex(signingKey));
188
189
// Keys are deterministic - same inputs produce same outputs
190
const encryptionKey2 = sodium.crypto_kdf_derive_from_key(32, 1, 'encrypt_', masterKey);
191
console.log('Keys are deterministic:',
192
sodium.memcmp(encryptionKey, encryptionKey2)); // true
193
```
194
195
### Hierarchical Key Derivation
196
197
```javascript
198
class KeyHierarchy {
199
constructor(masterKey = null) {
200
this.masterKey = masterKey || sodium.crypto_kdf_keygen();
201
this.derivedKeys = new Map();
202
}
203
204
// Derive key for specific purpose and ID
205
deriveKey(purpose, id, length = 32) {
206
// Ensure context is exactly 8 bytes
207
const context = (purpose + '________').substring(0, 8);
208
const cacheKey = `${purpose}-${id}-${length}`;
209
210
if (!this.derivedKeys.has(cacheKey)) {
211
const key = sodium.crypto_kdf_derive_from_key(length, id, context, this.masterKey);
212
this.derivedKeys.set(cacheKey, key);
213
}
214
215
return this.derivedKeys.get(cacheKey);
216
}
217
218
// Get encryption key for user ID
219
getUserEncryptionKey(userId) {
220
return this.deriveKey('userenc', userId);
221
}
222
223
// Get authentication key for user ID
224
getUserAuthKey(userId) {
225
return this.deriveKey('userauth', userId);
226
}
227
228
// Get session key for session ID
229
getSessionKey(sessionId) {
230
return this.deriveKey('session', sessionId);
231
}
232
233
// Get database encryption key for table ID
234
getTableKey(tableId) {
235
return this.deriveKey('table', tableId);
236
}
237
238
// Export master key (encrypt this in production!)
239
exportMasterKey() {
240
return sodium.to_base64(this.masterKey);
241
}
242
243
// Import master key
244
importMasterKey(base64Key) {
245
this.masterKey = sodium.from_base64(base64Key);
246
this.derivedKeys.clear(); // Clear cache
247
}
248
}
249
250
// Usage
251
const keyManager = new KeyHierarchy();
252
253
// Get keys for different users
254
const user1EncKey = keyManager.getUserEncryptionKey(1001);
255
const user1AuthKey = keyManager.getUserAuthKey(1001);
256
const user2EncKey = keyManager.getUserEncryptionKey(1002);
257
258
console.log('User 1 encryption key:', sodium.to_hex(user1EncKey));
259
console.log('User 1 auth key:', sodium.to_hex(user1AuthKey));
260
console.log('User 2 encryption key:', sodium.to_hex(user2EncKey));
261
262
// Keys are consistent
263
const user1EncKey2 = keyManager.getUserEncryptionKey(1001);
264
console.log('Consistent keys:',
265
sodium.memcmp(user1EncKey, user1EncKey2)); // true
266
```
267
268
### Key Exchange Protocol
269
270
```javascript
271
// Simulate client-server key exchange
272
function performKeyExchange() {
273
// Generate key pairs
274
const client = sodium.crypto_kx_keypair();
275
const server = sodium.crypto_kx_keypair();
276
277
console.log('Client public key:', sodium.to_hex(client.publicKey));
278
console.log('Server public key:', sodium.to_hex(server.publicKey));
279
280
// Client computes session keys
281
const clientKeys = sodium.crypto_kx_client_session_keys(
282
client.publicKey,
283
client.privateKey,
284
server.publicKey
285
);
286
287
// Server computes session keys
288
const serverKeys = sodium.crypto_kx_server_session_keys(
289
server.publicKey,
290
server.privateKey,
291
client.publicKey
292
);
293
294
// Verify key exchange worked correctly
295
console.log('Client Tx equals Server Rx:',
296
sodium.memcmp(clientKeys.sharedTx, serverKeys.sharedRx)); // true
297
console.log('Client Rx equals Server Tx:',
298
sodium.memcmp(clientKeys.sharedRx, serverKeys.sharedTx)); // true
299
300
return {
301
client: clientKeys,
302
server: serverKeys
303
};
304
}
305
306
const sessionKeys = performKeyExchange();
307
console.log('Client session keys:', {
308
tx: sodium.to_hex(sessionKeys.client.sharedTx),
309
rx: sodium.to_hex(sessionKeys.client.sharedRx)
310
});
311
```
312
313
### Secure Communication Channel
314
315
```javascript
316
class SecureChannel {
317
constructor(isClient = true) {
318
this.keyPair = sodium.crypto_kx_keypair();
319
this.isClient = isClient;
320
this.sessionKeys = null;
321
}
322
323
// Exchange keys with peer
324
exchangeKeys(peerPublicKey) {
325
if (this.isClient) {
326
this.sessionKeys = sodium.crypto_kx_client_session_keys(
327
this.keyPair.publicKey,
328
this.keyPair.privateKey,
329
peerPublicKey
330
);
331
} else {
332
this.sessionKeys = sodium.crypto_kx_server_session_keys(
333
this.keyPair.publicKey,
334
this.keyPair.privateKey,
335
peerPublicKey
336
);
337
}
338
}
339
340
// Encrypt message to send
341
encrypt(message) {
342
if (!this.sessionKeys) {
343
throw new Error('Key exchange not completed');
344
}
345
346
const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
347
const ciphertext = sodium.crypto_secretbox_easy(
348
message, nonce, this.sessionKeys.sharedTx
349
);
350
351
return { nonce, ciphertext };
352
}
353
354
// Decrypt received message
355
decrypt(nonce, ciphertext) {
356
if (!this.sessionKeys) {
357
throw new Error('Key exchange not completed');
358
}
359
360
return sodium.crypto_secretbox_open_easy(
361
ciphertext, nonce, this.sessionKeys.sharedRx
362
);
363
}
364
365
getPublicKey() {
366
return this.keyPair.publicKey;
367
}
368
}
369
370
// Simulate secure communication
371
const clientChannel = new SecureChannel(true);
372
const serverChannel = new SecureChannel(false);
373
374
// Exchange public keys
375
clientChannel.exchangeKeys(serverChannel.getPublicKey());
376
serverChannel.exchangeKeys(clientChannel.getPublicKey());
377
378
// Client sends message to server
379
const clientMessage = sodium.from_string('Hello from client');
380
const encrypted = clientChannel.encrypt(clientMessage);
381
382
console.log('Encrypted message:', sodium.to_hex(encrypted.ciphertext));
383
384
// Server receives and decrypts
385
const decrypted = serverChannel.decrypt(encrypted.nonce, encrypted.ciphertext);
386
console.log('Decrypted message:', sodium.to_string(decrypted)); // "Hello from client"
387
388
// Server responds
389
const serverMessage = sodium.from_string('Hello from server');
390
const serverEncrypted = serverChannel.encrypt(serverMessage);
391
const clientDecrypted = clientChannel.decrypt(serverEncrypted.nonce, serverEncrypted.ciphertext);
392
console.log('Server response:', sodium.to_string(clientDecrypted)); // "Hello from server"
393
```
394
395
### Multi-Purpose Key Derivation
396
397
```javascript
398
class ApplicationKeys {
399
constructor(appSecret) {
400
this.masterKey = appSecret || sodium.crypto_kdf_keygen();
401
}
402
403
// Database encryption keys
404
getDatabaseKey(dbName) {
405
return sodium.crypto_kdf_derive_from_key(
406
32, this.hashString(dbName), 'database', this.masterKey
407
);
408
}
409
410
// API authentication keys
411
getAPIKey(apiVersion) {
412
return sodium.crypto_kdf_derive_from_key(
413
32, apiVersion, 'api_key_', this.masterKey
414
);
415
}
416
417
// Session encryption keys
418
getSessionKey(sessionId) {
419
return sodium.crypto_kdf_derive_from_key(
420
32, sessionId, 'session_', this.masterKey
421
);
422
}
423
424
// File encryption keys
425
getFileKey(fileId) {
426
return sodium.crypto_kdf_derive_from_key(
427
32, fileId, 'file____', this.masterKey
428
);
429
}
430
431
// Hash string to number for subkey ID
432
hashString(str) {
433
const hash = sodium.crypto_generichash(8, sodium.from_string(str));
434
const view = new DataView(hash.buffer);
435
return view.getBigUint64(0, true); // little-endian
436
}
437
438
// Rotate master key (re-derive all keys)
439
rotateMasterKey() {
440
const oldMaster = this.masterKey;
441
this.masterKey = sodium.crypto_kdf_keygen();
442
443
// In practice, you'd need to re-encrypt all data with new keys
444
console.log('Master key rotated');
445
console.log('Old master:', sodium.to_hex(oldMaster));
446
console.log('New master:', sodium.to_hex(this.masterKey));
447
448
// Clear old key from memory
449
sodium.memzero(oldMaster);
450
}
451
}
452
453
// Usage
454
const appKeys = new ApplicationKeys();
455
456
// Get various application keys
457
const dbKey = appKeys.getDatabaseKey('users');
458
const apiKey = appKeys.getAPIKey(2);
459
const sessionKey = appKeys.getSessionKey(12345);
460
const fileKey = appKeys.getFileKey(98765);
461
462
console.log('Database key:', sodium.to_hex(dbKey));
463
console.log('API key:', sodium.to_hex(apiKey));
464
console.log('Session key:', sodium.to_hex(sessionKey));
465
console.log('File key:', sodium.to_hex(fileKey));
466
467
// Keys are deterministic
468
const dbKey2 = appKeys.getDatabaseKey('users');
469
console.log('Deterministic keys:', sodium.memcmp(dbKey, dbKey2)); // true
470
```
471
472
### Forward Secrecy with Key Exchange
473
474
```javascript
475
class ForwardSecureChannel {
476
constructor(isClient = true) {
477
this.isClient = isClient;
478
this.longTermKeyPair = sodium.crypto_kx_keypair();
479
this.currentEphemeralKeys = null;
480
this.sessionHistory = [];
481
}
482
483
// Generate new ephemeral keys
484
generateEphemeralKeys() {
485
this.currentEphemeralKeys = sodium.crypto_kx_keypair();
486
return this.currentEphemeralKeys.publicKey;
487
}
488
489
// Establish session with peer
490
establishSession(peerLongTermPk, peerEphemeralPk) {
491
if (!this.currentEphemeralKeys) {
492
throw new Error('Generate ephemeral keys first');
493
}
494
495
// Compute session keys using ephemeral keys
496
let sessionKeys;
497
if (this.isClient) {
498
sessionKeys = sodium.crypto_kx_client_session_keys(
499
this.currentEphemeralKeys.publicKey,
500
this.currentEphemeralKeys.privateKey,
501
peerEphemeralPk
502
);
503
} else {
504
sessionKeys = sodium.crypto_kx_server_session_keys(
505
this.currentEphemeralKeys.publicKey,
506
this.currentEphemeralKeys.privateKey,
507
peerEphemeralPk
508
);
509
}
510
511
// Store session info
512
const session = {
513
id: this.sessionHistory.length,
514
keys: sessionKeys,
515
timestamp: Date.now(),
516
peerLongTermPk: sodium.to_hex(peerLongTermPk)
517
};
518
519
this.sessionHistory.push(session);
520
521
// Clear ephemeral private key for forward secrecy
522
sodium.memzero(this.currentEphemeralKeys.privateKey);
523
this.currentEphemeralKeys = null;
524
525
return session;
526
}
527
528
// Get long-term public key
529
getLongTermPublicKey() {
530
return this.longTermKeyPair.publicKey;
531
}
532
533
// Encrypt with session keys
534
encryptMessage(sessionId, message) {
535
const session = this.sessionHistory[sessionId];
536
if (!session) {
537
throw new Error('Invalid session ID');
538
}
539
540
const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
541
const ciphertext = sodium.crypto_secretbox_easy(
542
message, nonce, session.keys.sharedTx
543
);
544
545
return { nonce, ciphertext };
546
}
547
548
// Decrypt with session keys
549
decryptMessage(sessionId, nonce, ciphertext) {
550
const session = this.sessionHistory[sessionId];
551
if (!session) {
552
throw new Error('Invalid session ID');
553
}
554
555
return sodium.crypto_secretbox_open_easy(
556
ciphertext, nonce, session.keys.sharedRx
557
);
558
}
559
560
// Clear old sessions for forward secrecy
561
clearOldSessions(keepCount = 1) {
562
const toRemove = this.sessionHistory.length - keepCount;
563
if (toRemove > 0) {
564
for (let i = 0; i < toRemove; i++) {
565
const session = this.sessionHistory[i];
566
sodium.memzero(session.keys.sharedTx);
567
sodium.memzero(session.keys.sharedRx);
568
}
569
this.sessionHistory.splice(0, toRemove);
570
console.log(`Cleared ${toRemove} old sessions`);
571
}
572
}
573
}
574
575
// Simulate forward secure communication
576
const client = new ForwardSecureChannel(true);
577
const server = new ForwardSecureChannel(false);
578
579
// Exchange long-term keys (would be done securely in practice)
580
const clientLongTermPk = client.getLongTermPublicKey();
581
const serverLongTermPk = server.getLongTermPublicKey();
582
583
// Establish first session
584
const clientEphemeral1 = client.generateEphemeralKeys();
585
const serverEphemeral1 = server.generateEphemeralKeys();
586
587
const clientSession1 = client.establishSession(serverLongTermPk, serverEphemeral1);
588
const serverSession1 = server.establishSession(clientLongTermPk, clientEphemeral1);
589
590
// Communicate
591
const message1 = sodium.from_string('First message');
592
const encrypted1 = client.encryptMessage(0, message1);
593
const decrypted1 = server.decryptMessage(0, encrypted1.nonce, encrypted1.ciphertext);
594
console.log('First session message:', sodium.to_string(decrypted1));
595
596
// Establish second session (forward secrecy)
597
const clientEphemeral2 = client.generateEphemeralKeys();
598
const serverEphemeral2 = server.generateEphemeralKeys();
599
600
const clientSession2 = client.establishSession(serverLongTermPk, serverEphemeral2);
601
const serverSession2 = server.establishSession(clientLongTermPk, clientEphemeral2);
602
603
// New session has different keys
604
console.log('Sessions have different keys:',
605
!sodium.memcmp(clientSession1.keys.sharedTx, clientSession2.keys.sharedTx)); // true
606
607
// Clear old sessions
608
client.clearOldSessions(1);
609
server.clearOldSessions(1);
610
```
611
612
## Security Considerations
613
614
- **Master Key Security**: Protect master keys - compromise exposes all derived keys
615
- **Context Separation**: Use different contexts for different purposes
616
- **Key Rotation**: Periodically rotate master keys and re-derive subkeys
617
- **Forward Secrecy**: Use ephemeral keys and clear old keys from memory
618
- **Deterministic Derivation**: Same inputs always produce same outputs
619
- **Memory Management**: Clear sensitive keys with memzero()
620
621
## Algorithm Selection Guide
622
623
- **crypto_kdf**: Use for general key derivation with BLAKE2b
624
- **crypto_kx**: Use for establishing shared secrets between parties
625
- **HKDF variants**: Use for standards compliance (though not directly exposed)
626
- **Key hierarchies**: Organize keys logically with clear context separation
627
- **Forward secrecy**: Combine KX with ephemeral keys for perfect forward secrecy
628
629
Key derivation and exchange are fundamental building blocks for secure cryptographic protocols and key management systems.