hkdf.rs
1 //! HKDF (HMAC-based Key Derivation Function) implementation. 2 //! 3 //! HKDF is used to derive cryptographic keys from shared secrets (like DH outputs) 4 //! or other input key material. It is defined in [RFC 5869](https://tools.ietf.org/html/rfc5869) 5 //! and is the standard KDF used by TLS 1.3, Signal, and many other protocols. 6 //! 7 //! # Why Use HKDF? 8 //! 9 //! Raw shared secrets from key exchange (ECDH) should never be used directly as 10 //! encryption keys because: 11 //! 12 //! 1. **Non-uniform distribution**: DH outputs aren't uniformly random 13 //! 2. **Key separation**: Different purposes need different keys 14 //! 3. **Domain separation**: Prevents cross-protocol attacks 15 //! 16 //! HKDF solves all of these by extracting entropy and expanding it into 17 //! independent, uniformly-distributed keys. 18 //! 19 //! # How HKDF Works 20 //! 21 //! HKDF consists of two stages: 22 //! 23 //! 1. **Extract**: `PRK = HMAC-Hash(salt, IKM)` 24 //! - Compresses input key material (IKM) into a fixed-size pseudorandom key (PRK) 25 //! - Salt adds extra entropy and domain separation 26 //! 27 //! 2. **Expand**: `OKM = HMAC-Hash(PRK, info || counter)` 28 //! - Expands PRK into arbitrary-length output keying material (OKM) 29 //! - Info string provides application-specific context 30 //! 31 //! # Parameters 32 //! 33 //! | Parameter | Purpose | Example | 34 //! |-----------|---------|---------| 35 //! | `secret` | Input key material | DH shared secret | 36 //! | `salt` | Randomness/domain separation | Message ID, session ID | 37 //! | `info` | Application context | `"dead-drop-message-v1"` | 38 //! 39 //! # Domain Separation 40 //! 41 //! This module uses a `"DeadDrop-v1-"` prefix for all domain-specific functions 42 //! to ensure keys derived for different purposes are cryptographically independent. 43 //! 44 //! # Usage 45 //! 46 //! ``` 47 //! use dead_drop_core::crypto::hkdf::{derive_key, derive_key_32}; 48 //! 49 //! let shared_secret = [0x42u8; 32]; 50 //! let salt = b"message-id-12345"; 51 //! let info = b"dead-drop-message-v1"; 52 //! 53 //! // Derive a 32-byte key (most common) 54 //! let key = derive_key_32(&shared_secret, salt, info); 55 //! assert_eq!(key.len(), 32); 56 //! 57 //! // Derive arbitrary length key material 58 //! let key_material = derive_key(&shared_secret, salt, info, 64); 59 //! assert_eq!(key_material.len(), 64); 60 //! ``` 61 62 use hkdf::Hkdf; 63 use sha2::Sha256; 64 65 use crate::error::{DeadDropError, Result}; 66 67 /// Maximum output length for HKDF-SHA256 (255 * 32 bytes). 68 pub const MAX_OUTPUT_LENGTH: usize = 255 * 32; 69 70 /// Domain separation prefix for all Dead Drop key derivation. 71 const DEAD_DROP_DOMAIN: &[u8] = b"DeadDrop-v1-"; 72 73 /// Derive key material using HKDF-SHA256. 74 /// 75 /// # Arguments 76 /// 77 /// * `secret` - Input key material (e.g., shared secret from ECDH) 78 /// * `salt` - Optional salt value (can be empty, but salt improves security) 79 /// * `info` - Context and application-specific information 80 /// * `output_len` - Desired length of output key material 81 /// 82 /// # Returns 83 /// 84 /// Derived key material of the requested length. 85 /// 86 /// # Panics 87 /// 88 /// Panics if `output_len` exceeds `MAX_OUTPUT_LENGTH` (8160 bytes). 89 /// 90 /// # Example 91 /// 92 /// ``` 93 /// use dead_drop_core::crypto::hkdf::derive_key; 94 /// 95 /// let secret = [0x42u8; 32]; 96 /// let key = derive_key(&secret, b"salt", b"info", 32); 97 /// assert_eq!(key.len(), 32); 98 /// ``` 99 pub fn derive_key(secret: &[u8], salt: &[u8], info: &[u8], output_len: usize) -> Vec<u8> { 100 assert!( 101 output_len <= MAX_OUTPUT_LENGTH, 102 "HKDF output length {} exceeds maximum {}", 103 output_len, 104 MAX_OUTPUT_LENGTH 105 ); 106 107 let hk = Hkdf::<Sha256>::new(Some(salt), secret); 108 let mut okm = vec![0u8; output_len]; 109 hk.expand(info, &mut okm) 110 .expect("HKDF expand should not fail with valid output length"); 111 okm 112 } 113 114 /// Derive a 32-byte key using HKDF-SHA256. 115 /// 116 /// This is a convenience function for the common case of deriving 117 /// a single 32-byte key (e.g., for ChaCha20-Poly1305). 118 /// 119 /// # Arguments 120 /// 121 /// * `secret` - Input key material 122 /// * `salt` - Salt value 123 /// * `info` - Context information 124 /// 125 /// # Returns 126 /// 127 /// A 32-byte derived key. 128 pub fn derive_key_32(secret: &[u8], salt: &[u8], info: &[u8]) -> [u8; 32] { 129 let hk = Hkdf::<Sha256>::new(Some(salt), secret); 130 let mut okm = [0u8; 32]; 131 hk.expand(info, &mut okm) 132 .expect("HKDF expand should not fail for 32-byte output"); 133 okm 134 } 135 136 /// Derive a 16-byte key using HKDF-SHA256. 137 /// 138 /// Useful for deriving IDs or shorter keys. 139 pub fn derive_key_16(secret: &[u8], salt: &[u8], info: &[u8]) -> [u8; 16] { 140 let hk = Hkdf::<Sha256>::new(Some(salt), secret); 141 let mut okm = [0u8; 16]; 142 hk.expand(info, &mut okm) 143 .expect("HKDF expand should not fail for 16-byte output"); 144 okm 145 } 146 147 /// Derive a 64-byte key using HKDF-SHA256. 148 /// 149 /// Useful when you need both an encryption key and a MAC key. 150 pub fn derive_key_64(secret: &[u8], salt: &[u8], info: &[u8]) -> [u8; 64] { 151 let hk = Hkdf::<Sha256>::new(Some(salt), secret); 152 let mut okm = [0u8; 64]; 153 hk.expand(info, &mut okm) 154 .expect("HKDF expand should not fail for 64-byte output"); 155 okm 156 } 157 158 /// Try to derive key material, returning a Result instead of panicking. 159 /// 160 /// # Errors 161 /// 162 /// Returns `KeyDerivation` error if output length exceeds maximum. 163 pub fn try_derive_key( 164 secret: &[u8], 165 salt: &[u8], 166 info: &[u8], 167 output_len: usize, 168 ) -> Result<Vec<u8>> { 169 if output_len > MAX_OUTPUT_LENGTH { 170 return Err(DeadDropError::KeyDerivation(format!( 171 "Output length {} exceeds maximum {}", 172 output_len, MAX_OUTPUT_LENGTH 173 ))); 174 } 175 176 let hk = Hkdf::<Sha256>::new(Some(salt), secret); 177 let mut okm = vec![0u8; output_len]; 178 hk.expand(info, &mut okm) 179 .map_err(|_| DeadDropError::KeyDerivation("HKDF expansion failed".to_string()))?; 180 Ok(okm) 181 } 182 183 /// Derive multiple keys at once from the same secret. 184 /// 185 /// This is more efficient than calling `derive_key` multiple times 186 /// with different info strings, as it only performs one extraction. 187 /// 188 /// # Arguments 189 /// 190 /// * `secret` - Input key material 191 /// * `salt` - Salt value 192 /// * `key_specs` - List of (info, length) pairs 193 /// 194 /// # Returns 195 /// 196 /// A vector of derived keys, one for each spec. 197 pub fn derive_multiple_keys( 198 secret: &[u8], 199 salt: &[u8], 200 key_specs: &[(&[u8], usize)], 201 ) -> Vec<Vec<u8>> { 202 let hk = Hkdf::<Sha256>::new(Some(salt), secret); 203 204 key_specs 205 .iter() 206 .map(|(info, len)| { 207 let mut okm = vec![0u8; *len]; 208 hk.expand(info, &mut okm) 209 .expect("HKDF expand should not fail"); 210 okm 211 }) 212 .collect() 213 } 214 215 // ==================== Domain-Specific Key Derivation ==================== 216 217 /// Derive a message encryption key. 218 /// 219 /// Uses Dead Drop domain separation. 220 pub fn derive_message_key(shared_secret: &[u8; 32], message_id: &[u8; 16]) -> [u8; 32] { 221 let info = [DEAD_DROP_DOMAIN, b"message-key"].concat(); 222 derive_key_32(shared_secret, message_id, &info) 223 } 224 225 /// Derive a rotating ID for BLE advertisement. 226 /// 227 /// The rotating ID is derived from a shared secret and time slot, 228 /// allowing contacts to recognize each other without revealing 229 /// their identity to non-contacts. 230 pub fn derive_rotating_id(shared_secret: &[u8; 32], time_slot: u64) -> [u8; 16] { 231 let info = [DEAD_DROP_DOMAIN, b"rotating-id"].concat(); 232 let salt = time_slot.to_be_bytes(); 233 derive_key_16(shared_secret, &salt, &info) 234 } 235 236 /// Derive a storage encryption key from a passphrase. 237 /// 238 /// Uses a fixed salt for the device. In production, the salt 239 /// should be randomly generated and stored securely. 240 pub fn derive_storage_key(passphrase: &[u8], device_salt: &[u8]) -> [u8; 32] { 241 let info = [DEAD_DROP_DOMAIN, b"storage-key"].concat(); 242 derive_key_32(passphrase, device_salt, &info) 243 } 244 245 /// Derive a session key for transport encryption. 246 pub fn derive_session_key(shared_secret: &[u8; 32], session_id: &[u8]) -> [u8; 32] { 247 let info = [DEAD_DROP_DOMAIN, b"session-key"].concat(); 248 derive_key_32(shared_secret, session_id, &info) 249 } 250 251 /// Key derivation result containing multiple derived keys. 252 #[derive(Debug)] 253 pub struct DerivedKeys { 254 /// Encryption key (32 bytes) 255 pub encryption_key: [u8; 32], 256 /// MAC key (32 bytes) 257 pub mac_key: [u8; 32], 258 /// IV/Nonce seed (16 bytes) 259 pub iv_seed: [u8; 16], 260 } 261 262 /// Derive a complete key set for encryption. 263 /// 264 /// Derives an encryption key, MAC key, and IV seed from a single 265 /// secret in one efficient operation. 266 pub fn derive_key_set(secret: &[u8], salt: &[u8]) -> DerivedKeys { 267 let info = [DEAD_DROP_DOMAIN, b"key-set"].concat(); 268 let material = derive_key(secret, salt, &info, 80); // 32 + 32 + 16 269 270 let mut encryption_key = [0u8; 32]; 271 let mut mac_key = [0u8; 32]; 272 let mut iv_seed = [0u8; 16]; 273 274 encryption_key.copy_from_slice(&material[0..32]); 275 mac_key.copy_from_slice(&material[32..64]); 276 iv_seed.copy_from_slice(&material[64..80]); 277 278 DerivedKeys { 279 encryption_key, 280 mac_key, 281 iv_seed, 282 } 283 } 284 285 #[cfg(test)] 286 mod tests { 287 use super::*; 288 289 // ==================== Basic derive_key Tests ==================== 290 291 #[test] 292 fn test_derive_key_32_bytes() { 293 let secret = [0x42u8; 32]; 294 let salt = b"test-salt"; 295 let info = b"test-info"; 296 297 let key = derive_key(&secret, salt, info, 32); 298 299 assert_eq!(key.len(), 32); 300 } 301 302 #[test] 303 fn test_derive_key_various_lengths() { 304 let secret = [0x42u8; 32]; 305 let salt = b"salt"; 306 let info = b"info"; 307 308 for len in [1, 16, 32, 64, 128, 256] { 309 let key = derive_key(&secret, salt, info, len); 310 assert_eq!(key.len(), len); 311 } 312 } 313 314 #[test] 315 fn test_derive_key_deterministic() { 316 let secret = [0x42u8; 32]; 317 let salt = b"salt"; 318 let info = b"info"; 319 320 let key1 = derive_key(&secret, salt, info, 32); 321 let key2 = derive_key(&secret, salt, info, 32); 322 323 assert_eq!(key1, key2); 324 } 325 326 #[test] 327 fn test_derive_key_different_secrets() { 328 let secret1 = [0x42u8; 32]; 329 let secret2 = [0x43u8; 32]; 330 let salt = b"salt"; 331 let info = b"info"; 332 333 let key1 = derive_key(&secret1, salt, info, 32); 334 let key2 = derive_key(&secret2, salt, info, 32); 335 336 assert_ne!(key1, key2); 337 } 338 339 #[test] 340 fn test_derive_key_different_salts() { 341 let secret = [0x42u8; 32]; 342 let salt1 = b"salt1"; 343 let salt2 = b"salt2"; 344 let info = b"info"; 345 346 let key1 = derive_key(&secret, salt1, info, 32); 347 let key2 = derive_key(&secret, salt2, info, 32); 348 349 assert_ne!(key1, key2); 350 } 351 352 #[test] 353 fn test_derive_key_different_info() { 354 let secret = [0x42u8; 32]; 355 let salt = b"salt"; 356 let info1 = b"info1"; 357 let info2 = b"info2"; 358 359 let key1 = derive_key(&secret, salt, info1, 32); 360 let key2 = derive_key(&secret, salt, info2, 32); 361 362 assert_ne!(key1, key2); 363 } 364 365 #[test] 366 fn test_derive_key_empty_salt() { 367 let secret = [0x42u8; 32]; 368 let info = b"info"; 369 370 // Empty salt should work 371 let key = derive_key(&secret, b"", info, 32); 372 assert_eq!(key.len(), 32); 373 } 374 375 #[test] 376 fn test_derive_key_empty_info() { 377 let secret = [0x42u8; 32]; 378 let salt = b"salt"; 379 380 // Empty info should work 381 let key = derive_key(&secret, salt, b"", 32); 382 assert_eq!(key.len(), 32); 383 } 384 385 #[test] 386 fn test_derive_key_empty_secret() { 387 // Empty secret should work (though not recommended) 388 let key = derive_key(b"", b"salt", b"info", 32); 389 assert_eq!(key.len(), 32); 390 } 391 392 #[test] 393 fn test_derive_key_zero_length() { 394 let secret = [0x42u8; 32]; 395 let key = derive_key(&secret, b"salt", b"info", 0); 396 assert!(key.is_empty()); 397 } 398 399 #[test] 400 #[should_panic(expected = "HKDF output length")] 401 fn test_derive_key_exceeds_max() { 402 let secret = [0x42u8; 32]; 403 derive_key(&secret, b"salt", b"info", MAX_OUTPUT_LENGTH + 1); 404 } 405 406 // ==================== Fixed-Size Derive Tests ==================== 407 408 #[test] 409 fn test_derive_key_32() { 410 let secret = [0x42u8; 32]; 411 let key = derive_key_32(&secret, b"salt", b"info"); 412 413 assert_eq!(key.len(), 32); 414 } 415 416 #[test] 417 fn test_derive_key_16() { 418 let secret = [0x42u8; 32]; 419 let key = derive_key_16(&secret, b"salt", b"info"); 420 421 assert_eq!(key.len(), 16); 422 } 423 424 #[test] 425 fn test_derive_key_64() { 426 let secret = [0x42u8; 32]; 427 let key = derive_key_64(&secret, b"salt", b"info"); 428 429 assert_eq!(key.len(), 64); 430 } 431 432 #[test] 433 fn test_fixed_size_functions_consistent() { 434 let secret = [0x42u8; 32]; 435 let salt = b"salt"; 436 let info = b"info"; 437 438 let key_32 = derive_key_32(&secret, salt, info); 439 let key_var = derive_key(&secret, salt, info, 32); 440 441 // Fixed-size function should produce same result as variable 442 assert_eq!(key_32.as_slice(), key_var.as_slice()); 443 } 444 445 // ==================== try_derive_key Tests ==================== 446 447 #[test] 448 fn test_try_derive_key_success() { 449 let secret = [0x42u8; 32]; 450 let result = try_derive_key(&secret, b"salt", b"info", 32); 451 452 assert!(result.is_ok()); 453 assert_eq!(result.unwrap().len(), 32); 454 } 455 456 #[test] 457 fn test_try_derive_key_exceeds_max() { 458 let secret = [0x42u8; 32]; 459 let result = try_derive_key(&secret, b"salt", b"info", MAX_OUTPUT_LENGTH + 1); 460 461 assert!(result.is_err()); 462 assert!(matches!( 463 result.unwrap_err(), 464 DeadDropError::KeyDerivation(_) 465 )); 466 } 467 468 // ==================== derive_multiple_keys Tests ==================== 469 470 #[test] 471 fn test_derive_multiple_keys() { 472 let secret = [0x42u8; 32]; 473 let salt = b"salt"; 474 475 let specs: &[(&[u8], usize)] = &[(b"key1", 32), (b"key2", 16), (b"key3", 64)]; 476 477 let keys = derive_multiple_keys(&secret, salt, specs); 478 479 assert_eq!(keys.len(), 3); 480 assert_eq!(keys[0].len(), 32); 481 assert_eq!(keys[1].len(), 16); 482 assert_eq!(keys[2].len(), 64); 483 } 484 485 #[test] 486 fn test_derive_multiple_keys_different_info() { 487 let secret = [0x42u8; 32]; 488 let salt = b"salt"; 489 490 let specs: &[(&[u8], usize)] = &[(b"key1", 32), (b"key2", 32)]; 491 492 let keys = derive_multiple_keys(&secret, salt, specs); 493 494 // Different info strings should produce different keys 495 assert_ne!(keys[0], keys[1]); 496 } 497 498 #[test] 499 fn test_derive_multiple_keys_empty() { 500 let secret = [0x42u8; 32]; 501 let specs: &[(&[u8], usize)] = &[]; 502 503 let keys = derive_multiple_keys(&secret, b"salt", specs); 504 505 assert!(keys.is_empty()); 506 } 507 508 // ==================== Domain-Specific Function Tests ==================== 509 510 #[test] 511 fn test_derive_message_key() { 512 let shared_secret = [0x42u8; 32]; 513 let message_id = [0x01u8; 16]; 514 515 let key = derive_message_key(&shared_secret, &message_id); 516 517 assert_eq!(key.len(), 32); 518 } 519 520 #[test] 521 fn test_derive_message_key_different_ids() { 522 let shared_secret = [0x42u8; 32]; 523 let id1 = [0x01u8; 16]; 524 let id2 = [0x02u8; 16]; 525 526 let key1 = derive_message_key(&shared_secret, &id1); 527 let key2 = derive_message_key(&shared_secret, &id2); 528 529 assert_ne!(key1, key2); 530 } 531 532 #[test] 533 fn test_derive_rotating_id() { 534 let shared_secret = [0x42u8; 32]; 535 let time_slot = 12345u64; 536 537 let id = derive_rotating_id(&shared_secret, time_slot); 538 539 assert_eq!(id.len(), 16); 540 } 541 542 #[test] 543 fn test_derive_rotating_id_different_slots() { 544 let shared_secret = [0x42u8; 32]; 545 546 let id1 = derive_rotating_id(&shared_secret, 1); 547 let id2 = derive_rotating_id(&shared_secret, 2); 548 549 // Different time slots should produce different IDs 550 assert_ne!(id1, id2); 551 } 552 553 #[test] 554 fn test_derive_rotating_id_deterministic() { 555 let shared_secret = [0x42u8; 32]; 556 let time_slot = 12345u64; 557 558 let id1 = derive_rotating_id(&shared_secret, time_slot); 559 let id2 = derive_rotating_id(&shared_secret, time_slot); 560 561 // Same inputs should produce same output 562 assert_eq!(id1, id2); 563 } 564 565 #[test] 566 fn test_derive_storage_key() { 567 let passphrase = b"my-secure-passphrase"; 568 let device_salt = b"device-unique-salt"; 569 570 let key = derive_storage_key(passphrase, device_salt); 571 572 assert_eq!(key.len(), 32); 573 } 574 575 #[test] 576 fn test_derive_storage_key_different_passphrases() { 577 let passphrase1 = b"password1"; 578 let passphrase2 = b"password2"; 579 let device_salt = b"salt"; 580 581 let key1 = derive_storage_key(passphrase1, device_salt); 582 let key2 = derive_storage_key(passphrase2, device_salt); 583 584 assert_ne!(key1, key2); 585 } 586 587 #[test] 588 fn test_derive_session_key() { 589 let shared_secret = [0x42u8; 32]; 590 let session_id = b"session-12345"; 591 592 let key = derive_session_key(&shared_secret, session_id); 593 594 assert_eq!(key.len(), 32); 595 } 596 597 // ==================== derive_key_set Tests ==================== 598 599 #[test] 600 fn test_derive_key_set() { 601 let secret = [0x42u8; 32]; 602 let salt = b"salt"; 603 604 let keys = derive_key_set(&secret, salt); 605 606 assert_eq!(keys.encryption_key.len(), 32); 607 assert_eq!(keys.mac_key.len(), 32); 608 assert_eq!(keys.iv_seed.len(), 16); 609 } 610 611 #[test] 612 fn test_derive_key_set_all_different() { 613 let secret = [0x42u8; 32]; 614 let salt = b"salt"; 615 616 let keys = derive_key_set(&secret, salt); 617 618 // All three derived values should be different 619 assert_ne!(keys.encryption_key.as_slice(), keys.mac_key.as_slice()); 620 assert_ne!( 621 keys.encryption_key[..16].as_ref(), 622 keys.iv_seed.as_slice() 623 ); 624 } 625 626 #[test] 627 fn test_derive_key_set_deterministic() { 628 let secret = [0x42u8; 32]; 629 let salt = b"salt"; 630 631 let keys1 = derive_key_set(&secret, salt); 632 let keys2 = derive_key_set(&secret, salt); 633 634 assert_eq!(keys1.encryption_key, keys2.encryption_key); 635 assert_eq!(keys1.mac_key, keys2.mac_key); 636 assert_eq!(keys1.iv_seed, keys2.iv_seed); 637 } 638 639 // ==================== Domain Separation Tests ==================== 640 641 #[test] 642 fn test_domain_specific_functions_differ() { 643 let secret = [0x42u8; 32]; 644 645 // Use the same salt where possible 646 let message_key = derive_message_key(&secret, &[0x01u8; 16]); 647 let session_key = derive_session_key(&secret, &[0x01u8; 16]); 648 649 // Different domain separation should produce different keys 650 // even with same inputs otherwise 651 assert_ne!(message_key, session_key); 652 } 653 654 // ==================== Edge Case Tests ==================== 655 656 #[test] 657 fn test_derive_key_long_inputs() { 658 let secret = vec![0x42u8; 1024]; 659 let salt = vec![0x01u8; 1024]; 660 let info = vec![0x02u8; 1024]; 661 662 let key = derive_key(&secret, &salt, &info, 32); 663 assert_eq!(key.len(), 32); 664 } 665 666 #[test] 667 fn test_derive_key_max_output() { 668 let secret = [0x42u8; 32]; 669 let key = derive_key(&secret, b"salt", b"info", MAX_OUTPUT_LENGTH); 670 assert_eq!(key.len(), MAX_OUTPUT_LENGTH); 671 } 672 673 #[test] 674 fn test_derived_key_not_zero() { 675 // Even with zero inputs, output shouldn't be all zeros 676 let secret = [0u8; 32]; 677 let key = derive_key_32(&secret, &[], &[]); 678 679 assert_ne!(key, [0u8; 32]); 680 } 681 682 // ==================== RFC 5869 Consistency Tests ==================== 683 684 #[test] 685 fn test_hkdf_expansion_prefix_property() { 686 // HKDF has the property that shorter outputs are prefixes of longer ones 687 let secret = [0x42u8; 32]; 688 let salt = b"salt"; 689 let info = b"info"; 690 691 let short = derive_key(&secret, salt, info, 16); 692 let long = derive_key(&secret, salt, info, 32); 693 694 // The short key should be a prefix of the long key 695 assert_eq!(&short[..], &long[..16]); 696 } 697 }