apple_cryptokit/key_derivation/
mod.rs

1use crate::error::{CryptoKitError, Result};
2
3pub mod hkdf_sha256;
4pub mod hkdf_sha384;
5pub mod hkdf_sha512;
6
7/// Hash algorithm enumeration
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum HashAlgorithm {
10    /// SHA256 hash algorithm
11    SHA256,
12    /// SHA384 hash algorithm
13    SHA384,
14    /// SHA512 hash algorithm
15    SHA512,
16}
17
18impl HashAlgorithm {
19    /// Get the output length of the hash algorithm (in bytes)
20    pub fn output_length(&self) -> usize {
21        match self {
22            HashAlgorithm::SHA256 => 32,
23            HashAlgorithm::SHA384 => 48,
24            HashAlgorithm::SHA512 => 64,
25        }
26    }
27
28    /// Get the maximum output length of HKDF (255 * hash_length)
29    pub fn max_hkdf_output_length(&self) -> usize {
30        255 * self.output_length()
31    }
32}
33
34/// Generic trait for key derivation functions
35pub trait KeyDerivationFunction {
36    /// Derive key from input key material
37    fn derive(
38        input_key_material: &[u8],
39        salt: &[u8],
40        info: &[u8],
41        output_length: usize,
42    ) -> Result<Vec<u8>> {
43        let mut output = vec![0u8; output_length];
44        Self::derive_to(input_key_material, salt, info, &mut output)?;
45        Ok(output)
46    }
47
48    /// Derive key from input key material to the provided buffer (zero-allocation)
49    ///
50    /// # Parameters
51    /// - `output`: Buffer whose length determines the length of the derived key
52    ///
53    /// # Returns
54    /// Number of bytes written (same as output.len())
55    fn derive_to(
56        input_key_material: &[u8],
57        salt: &[u8],
58        info: &[u8],
59        output: &mut [u8],
60    ) -> Result<usize>;
61}
62
63/// Generic HKDF implementation
64pub struct HKDF;
65
66impl HKDF {
67    /// Perform HKDF key derivation using the specified hash algorithm
68    ///
69    /// # Parameters
70    /// * `algorithm` - The hash algorithm to use
71    /// * `input_key_material` - Input Key Material (IKM)
72    /// * `salt` - Optional salt value, recommended to use a random value
73    /// * `info` - Optional context and application-specific information
74    /// * `output_length` - Desired output key length
75    ///
76    /// # Returns
77    /// Derived key data
78    ///
79    /// # Errors
80    /// * `CryptoKitError::InvalidInput` - If the input key material is empty
81    /// * `CryptoKitError::InvalidLength` - If the output length is invalid
82    /// * `CryptoKitError::DerivationFailed` - If key derivation fails
83    ///
84    /// # Example
85    /// ```rust,no_run
86    /// use apple_cryptokit::key_derivation::{HKDF, HashAlgorithm};
87    ///
88    /// let ikm = b"input key material";
89    /// let salt = b"optional salt";
90    /// let info = b"application context";
91    ///
92    /// let derived_key = HKDF::derive_key(
93    ///     HashAlgorithm::SHA256,
94    ///     ikm,
95    ///     salt,
96    ///     info,
97    ///     32
98    /// ).unwrap();
99    /// ```
100    pub fn derive_key(
101        algorithm: HashAlgorithm,
102        input_key_material: &[u8],
103        salt: &[u8],
104        info: &[u8],
105        output_length: usize,
106    ) -> Result<Vec<u8>> {
107        // Validate input parameters
108        if input_key_material.is_empty() {
109            return Err(CryptoKitError::InvalidInput(
110                "Input key material cannot be empty".to_string(),
111            ));
112        }
113
114        if output_length == 0 || output_length > algorithm.max_hkdf_output_length() {
115            return Err(CryptoKitError::InvalidLength);
116        }
117
118        match algorithm {
119            HashAlgorithm::SHA256 => {
120                hkdf_sha256::HKDF_SHA256::derive(input_key_material, salt, info, output_length)
121            }
122            HashAlgorithm::SHA384 => {
123                hkdf_sha384::HKDF_SHA384::derive(input_key_material, salt, info, output_length)
124            }
125            HashAlgorithm::SHA512 => {
126                hkdf_sha512::HKDF_SHA512::derive(input_key_material, salt, info, output_length)
127            }
128        }
129    }
130
131    /// Perform HKDF key derivation to the provided buffer using the specified hash algorithm (zero-allocation)
132    ///
133    /// # Parameters
134    /// * `algorithm` - The hash algorithm to use
135    /// * `input_key_material` - Input Key Material (IKM)
136    /// * `salt` - Optional salt value, recommended to use a random value
137    /// * `info` - Optional context and application-specific information
138    /// * `output` - Output buffer whose length determines the length of the derived key
139    ///
140    /// # Returns
141    /// Number of bytes written to output
142    pub fn derive_key_to(
143        algorithm: HashAlgorithm,
144        input_key_material: &[u8],
145        salt: &[u8],
146        info: &[u8],
147        output: &mut [u8],
148    ) -> Result<usize> {
149        // Validate input parameters
150        if input_key_material.is_empty() {
151            return Err(CryptoKitError::InvalidInput(
152                "Input key material cannot be empty".to_string(),
153            ));
154        }
155
156        let output_length = output.len();
157        if output_length == 0 || output_length > algorithm.max_hkdf_output_length() {
158            return Err(CryptoKitError::InvalidLength);
159        }
160
161        match algorithm {
162            HashAlgorithm::SHA256 => {
163                hkdf_sha256::HKDF_SHA256::derive_to(input_key_material, salt, info, output)
164            }
165            HashAlgorithm::SHA384 => {
166                hkdf_sha384::HKDF_SHA384::derive_to(input_key_material, salt, info, output)
167            }
168            HashAlgorithm::SHA512 => {
169                hkdf_sha512::HKDF_SHA512::derive_to(input_key_material, salt, info, output)
170            }
171        }
172    }
173
174    /// Convenience method: HKDF key derivation using SHA256
175    pub fn derive_key_sha256(
176        input_key_material: &[u8],
177        salt: &[u8],
178        info: &[u8],
179        output_length: usize,
180    ) -> Result<Vec<u8>> {
181        Self::derive_key(
182            HashAlgorithm::SHA256,
183            input_key_material,
184            salt,
185            info,
186            output_length,
187        )
188    }
189
190    /// Convenience method: HKDF key derivation using SHA384
191    pub fn derive_key_sha384(
192        input_key_material: &[u8],
193        salt: &[u8],
194        info: &[u8],
195        output_length: usize,
196    ) -> Result<Vec<u8>> {
197        Self::derive_key(
198            HashAlgorithm::SHA384,
199            input_key_material,
200            salt,
201            info,
202            output_length,
203        )
204    }
205
206    /// Convenience method: HKDF key derivation using SHA512
207    pub fn derive_key_sha512(
208        input_key_material: &[u8],
209        salt: &[u8],
210        info: &[u8],
211        output_length: usize,
212    ) -> Result<Vec<u8>> {
213        Self::derive_key(
214            HashAlgorithm::SHA512,
215            input_key_material,
216            salt,
217            info,
218            output_length,
219        )
220    }
221
222    /// Derive symmetric key from shared secret
223    ///
224    /// This is a convenience method for deriving symmetric encryption keys from shared secrets
225    /// obtained from operations such as elliptic curve key agreement
226    ///
227    /// # Parameters
228    /// * `shared_secret` - Shared secret data
229    /// * `algorithm` - Hash algorithm
230    /// * `salt` - Optional salt value
231    /// * `info` - Application context information
232    /// * `output_length` - Output key length
233    pub fn derive_symmetric_key(
234        shared_secret: &[u8],
235        algorithm: HashAlgorithm,
236        salt: Option<&[u8]>,
237        info: &[u8],
238        output_length: usize,
239    ) -> Result<Vec<u8>> {
240        let salt = salt.unwrap_or(&[]);
241        Self::derive_key(algorithm, shared_secret, salt, info, output_length)
242    }
243}
244
245/// HKDF-SHA256 key derivation implementation (re-exported)
246pub use hkdf_sha256::HKDF_SHA256;
247
248/// HKDF-SHA384 key derivation implementation (re-export)
249pub use hkdf_sha384::HKDF_SHA384;
250
251/// HKDF-SHA512 key derivation implementation (re-export)
252pub use hkdf_sha512::HKDF_SHA512;
253
254// ============================================================================
255// Convenience functions
256// ============================================================================
257
258/// Convenience function: HKDF-SHA256 key derivation
259///
260/// # Parameters
261/// * `input_key_material` - Input key material
262/// * `salt` - Salt value (optional, recommended)
263/// * `info` - Application-specific information (optional)
264/// * `output_length` - Output key length
265///
266/// # Example
267/// ```rust,no_run
268/// use apple_cryptokit::key_derivation::hkdf_sha256_derive;
269///
270/// let ikm = b"input key material";
271/// let salt = b"optional salt";
272/// let info = b"application context";
273///
274/// let derived_key = hkdf_sha256_derive(ikm, salt, info, 32).unwrap();
275/// ```
276pub fn hkdf_sha256_derive(
277    input_key_material: &[u8],
278    salt: &[u8],
279    info: &[u8],
280    output_length: usize,
281) -> Result<Vec<u8>> {
282    hkdf_sha256::hkdf_sha256_derive(input_key_material, salt, info, output_length)
283}
284
285/// Convenience function: HKDF-SHA384 key derivation
286///
287/// # Parameters
288/// * `input_key_material` - Input key material
289/// * `salt` - Salt value (optional, recommended)
290/// * `info` - Application-specific information (optional)
291/// * `output_length` - Output key length
292pub fn hkdf_sha384_derive(
293    input_key_material: &[u8],
294    salt: &[u8],
295    info: &[u8],
296    output_length: usize,
297) -> Result<Vec<u8>> {
298    hkdf_sha384::hkdf_sha384_derive(input_key_material, salt, info, output_length)
299}
300
301/// Convenience function: HKDF-SHA512 key derivation
302///
303/// # Parameters
304/// * `input_key_material` - Input key material
305/// * `salt` - Salt value (optional, recommended)
306/// * `info` - Application-specific information (optional)
307/// * `output_length` - Output key length
308pub fn hkdf_sha512_derive(
309    input_key_material: &[u8],
310    salt: &[u8],
311    info: &[u8],
312    output_length: usize,
313) -> Result<Vec<u8>> {
314    hkdf_sha512::hkdf_sha512_derive(input_key_material, salt, info, output_length)
315}
316
317// ============================================================================
318// Predefined common key lengths
319// ============================================================================
320
321/// Common symmetric key length definitions
322pub mod key_sizes {
323    /// AES-128 key length (16 bytes)
324    pub const AES_128: usize = 16;
325    /// AES-192 key length (24 bytes)
326    pub const AES_192: usize = 24;
327    /// AES-256 key length (32 bytes)
328    pub const AES_256: usize = 32;
329    /// ChaCha20 key length (32 bytes)
330    pub const CHACHA20: usize = 32;
331    /// HMAC-SHA256 recommended key length (32 bytes)
332    pub const HMAC_SHA256: usize = 32;
333    /// HMAC-SHA384 recommended key length (48 bytes)
334    pub const HMAC_SHA384: usize = 48;
335    /// HMAC-SHA512 recommended key length (64 bytes)
336    pub const HMAC_SHA512: usize = 64;
337}
338
339// ============================================================================
340// Test module
341// ============================================================================
342
343#[cfg(test)]
344mod tests {
345    use super::*;
346
347    #[test]
348    fn test_hash_algorithm_properties() {
349        assert_eq!(HashAlgorithm::SHA256.output_length(), 32);
350        assert_eq!(HashAlgorithm::SHA384.output_length(), 48);
351        assert_eq!(HashAlgorithm::SHA512.output_length(), 64);
352
353        assert_eq!(HashAlgorithm::SHA256.max_hkdf_output_length(), 255 * 32);
354        assert_eq!(HashAlgorithm::SHA384.max_hkdf_output_length(), 255 * 48);
355        assert_eq!(HashAlgorithm::SHA512.max_hkdf_output_length(), 255 * 64);
356    }
357
358    #[test]
359    fn test_invalid_input_validation() {
360        // Test empty input key material
361        let result = HKDF::derive_key(HashAlgorithm::SHA256, &[], b"salt", b"info", 32);
362        assert!(matches!(result, Err(CryptoKitError::InvalidInput(_))));
363
364        // Test zero output length
365        let result = HKDF::derive_key(HashAlgorithm::SHA256, b"ikm", b"salt", b"info", 0);
366        assert!(matches!(result, Err(CryptoKitError::InvalidLength)));
367
368        // Test excessive output length
369        let result = HKDF::derive_key(
370            HashAlgorithm::SHA256,
371            b"ikm",
372            b"salt",
373            b"info",
374            255 * 32 + 1,
375        );
376        assert!(matches!(result, Err(CryptoKitError::InvalidLength)));
377    }
378}