/ core / src / crypto / hkdf.rs
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  }