or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

aead.mdauth.mdbox.mdhash.mdindex.mdkey-derivation.mdsecretbox.mdsign.mdstreaming.mdutilities.md
tile.json

key-derivation.mddocs/

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.