voided_core/encryption/
key.rs

1//! Key generation and derivation
2
3use crate::{Error, Result};
4use alloc::vec::Vec;
5use rand::RngCore;
6use zeroize::{Zeroize, ZeroizeOnDrop};
7
8/// 256-bit encryption key
9#[derive(Clone, Zeroize, ZeroizeOnDrop)]
10pub struct Key([u8; 32]);
11
12impl Key {
13    /// Key size in bytes
14    pub const SIZE: usize = 32;
15
16    /// Create a key from raw bytes
17    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
18        if bytes.len() != Self::SIZE {
19            return Err(Error::InvalidKeyLength {
20                expected: Self::SIZE,
21                actual: bytes.len(),
22            });
23        }
24        let mut key = [0u8; 32];
25        key.copy_from_slice(bytes);
26        Ok(Key(key))
27    }
28
29    /// Get the raw key bytes
30    pub fn as_bytes(&self) -> &[u8; 32] {
31        &self.0
32    }
33
34    /// Export key as Base64 string
35    pub fn to_base64(&self) -> String {
36        use base64::{Engine, engine::general_purpose::STANDARD};
37        STANDARD.encode(&self.0)
38    }
39
40    /// Import key from Base64 string
41    pub fn from_base64(encoded: &str) -> Result<Self> {
42        use base64::{Engine, engine::general_purpose::STANDARD};
43        let bytes = STANDARD.decode(encoded)?;
44        Self::from_bytes(&bytes)
45    }
46}
47
48impl AsRef<[u8]> for Key {
49    fn as_ref(&self) -> &[u8] {
50        &self.0
51    }
52}
53
54impl core::fmt::Debug for Key {
55    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
56        f.debug_struct("Key")
57            .field("length", &Self::SIZE)
58            .finish_non_exhaustive()
59    }
60}
61
62/// Generate a random 256-bit encryption key
63pub fn generate_key() -> Key {
64    let mut key = [0u8; 32];
65    rand::thread_rng().fill_bytes(&mut key);
66    Key(key)
67}
68
69/// Derive a key using HKDF-SHA256
70///
71/// # Arguments
72///
73/// * `input_key_material` - Input key material (IKM)
74/// * `salt` - Optional salt (can be empty)
75/// * `info` - Context and application specific information
76///
77/// # Returns
78///
79/// Derived 256-bit key
80pub fn derive_key_hkdf(
81    input_key_material: &[u8],
82    salt: Option<&[u8]>,
83    info: &[u8],
84) -> Result<Key> {
85    use hkdf::Hkdf;
86    use sha2::Sha256;
87
88    let hk = Hkdf::<Sha256>::new(salt, input_key_material);
89    let mut okm = [0u8; 32];
90    
91    hk.expand(info, &mut okm)
92        .map_err(|e| Error::KeyDerivationFailed(e.to_string()))?;
93
94    Ok(Key(okm))
95}
96
97/// Derive a key using PBKDF2-HMAC-SHA256
98///
99/// # Arguments
100///
101/// * `password` - Password to derive from
102/// * `salt` - Salt bytes (should be at least 16 bytes)
103/// * `iterations` - Number of iterations (minimum 100,000 recommended)
104///
105/// # Returns
106///
107/// Derived 256-bit key
108pub fn derive_key_pbkdf2(
109    password: &[u8],
110    salt: &[u8],
111    iterations: u32,
112) -> Result<Key> {
113    use pbkdf2::pbkdf2_hmac;
114    use sha2::Sha256;
115
116    let mut key = [0u8; 32];
117    pbkdf2_hmac::<Sha256>(password, salt, iterations, &mut key);
118
119    Ok(Key(key))
120}
121
122/// Generate a random salt for key derivation
123#[allow(dead_code)]
124pub fn generate_salt(length: usize) -> Vec<u8> {
125    let mut salt = vec![0u8; length];
126    rand::thread_rng().fill_bytes(&mut salt);
127    salt
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn test_key_generation() {
136        let key1 = generate_key();
137        let key2 = generate_key();
138        
139        // Keys should be different
140        assert_ne!(key1.as_bytes(), key2.as_bytes());
141        
142        // Key should be correct size
143        assert_eq!(key1.as_bytes().len(), 32);
144    }
145
146    #[test]
147    fn test_key_base64_roundtrip() {
148        let key = generate_key();
149        let encoded = key.to_base64();
150        let decoded = Key::from_base64(&encoded).unwrap();
151        
152        assert_eq!(key.as_bytes(), decoded.as_bytes());
153    }
154
155    #[test]
156    fn test_pbkdf2_derivation() {
157        let password = b"test password";
158        let salt = b"random salt here";
159        let iterations = 1000; // Lower for tests
160
161        let key1 = derive_key_pbkdf2(password, salt, iterations).unwrap();
162        let key2 = derive_key_pbkdf2(password, salt, iterations).unwrap();
163
164        // Same inputs should produce same key
165        assert_eq!(key1.as_bytes(), key2.as_bytes());
166    }
167
168    #[test]
169    fn test_hkdf_derivation() {
170        let ikm = b"input key material";
171        let salt = b"optional salt";
172        let info = b"context info";
173
174        let key1 = derive_key_hkdf(ikm, Some(salt), info).unwrap();
175        let key2 = derive_key_hkdf(ikm, Some(salt), info).unwrap();
176
177        // Same inputs should produce same key
178        assert_eq!(key1.as_bytes(), key2.as_bytes());
179    }
180}
181