secured_cipher_key/
lib.rs

1use hmac::Hmac;
2use pbkdf2::pbkdf2;
3use rand_core::{OsRng, RngCore};
4use sha2::Sha256;
5
6/// `Key` holds a public key and a salt value.
7/// This struct is specifically designed for use in symmetric encryption,
8/// and is compatible with multiple encryption algorithms.
9pub struct Key<const P: usize, const S: usize> {
10  /// Public key.
11  pub pubk: [u8; P],
12
13  /// Salt value.
14  pub salt: [u8; S],
15
16  /// Key derivation strategy.
17  pub strategy: KeyDerivationStrategy,
18}
19
20impl<const P: usize, const S: usize> Key<P, S> {
21  /// Constructs a new `Key` using a specified password and a number of rounds for key derivation.
22  /// The method automatically generates a random salt for each key.
23  ///
24  /// # Arguments
25  /// * `password` - A byte slice representing the password from which the key will be derived.
26  /// * `rounds` - The number of iterations used in the PBKDF2 key derivation function.
27  ///
28  /// # Returns
29  /// An instance of `Key` containing the derived public key and the generated salt.
30  ///
31  /// # Panics
32  /// Panics if the key derivation fails.
33  pub fn new(password: &[u8], strategy: KeyDerivationStrategy) -> Self {
34    // Generate a random salt value
35    let salt = random_bytes::<S>();
36
37    // Derive the public key using PBKDF2 algorithm
38    let mut pubk = [0; P];
39    match strategy {
40      KeyDerivationStrategy::PBKDF2(rounds) => {
41        if pbkdf2::<Hmac<Sha256>>(password, &salt, rounds as u32, &mut pubk).is_err() {
42          panic!("Key derivation failed")
43        }
44      }
45    }
46
47    Self {
48      pubk,
49      salt,
50      strategy,
51    }
52  }
53
54  /// Constructs a new `Key` using a specified password, a provided salt, and a number of rounds for key derivation.
55  ///
56  /// # Arguments
57  /// * `password` - A byte slice representing the password from which the key will be derived.
58  /// * `salt` - An array representing the salt to be used in the key derivation.
59  /// * `rounds` - The number of iterations used in the PBKDF2 key derivation function.
60  ///
61  /// # Returns
62  /// An instance of `Key` containing the derived public key and the provided salt.
63  ///
64  /// # Panics
65  /// Panics if the key derivation fails.
66  pub fn with_salt(password: &[u8], salt: [u8; S], strategy: KeyDerivationStrategy) -> Self {
67    // Derive the public key using PBKDF2 algorithm with the provided salt
68    let mut pubk = [0; P];
69
70    match strategy {
71      KeyDerivationStrategy::PBKDF2(rounds) => {
72        if pbkdf2::<Hmac<Sha256>>(password, &salt, rounds as u32, &mut pubk).is_err() {
73          panic!("Key derivation failed")
74        }
75      }
76    }
77
78    Self {
79      pubk,
80      salt,
81      strategy,
82    }
83  }
84}
85
86/// Generates a random byte array of a specified size.
87pub fn random_bytes<const S: usize>() -> [u8; S] {
88  let mut bytes = [0; S];
89  OsRng.fill_bytes(&mut bytes);
90  bytes
91}
92
93#[derive(Clone, Debug)]
94pub enum KeyDerivationStrategy {
95  PBKDF2(usize),
96}
97
98impl Default for KeyDerivationStrategy {
99  fn default() -> Self {
100    KeyDerivationStrategy::PBKDF2(900_000)
101  }
102}
103
104impl TryFrom<Vec<u8>> for KeyDerivationStrategy {
105  type Error = String;
106
107  fn try_from(bytes: Vec<u8>) -> Result<Self, String> {
108    match bytes[0] {
109      0 => {
110        let rounds_bytes = &bytes[1..];
111        let rounds = usize::from_be_bytes(rounds_bytes.try_into().or(Err("Invalid rounds bytes"))?);
112        Ok(KeyDerivationStrategy::PBKDF2(rounds))
113      }
114      _ => Err("Invalid key derivation strategy".to_string()),
115    }
116  }
117}
118
119impl From<KeyDerivationStrategy> for Vec<u8> {
120  fn from(strategy: KeyDerivationStrategy) -> Self {
121    match strategy {
122      KeyDerivationStrategy::PBKDF2(rounds) => [vec![0u8], rounds.to_be_bytes().to_vec()].concat(),
123    }
124  }
125}
126
127impl PartialEq for KeyDerivationStrategy {
128  fn eq(&self, other: &Self) -> bool {
129    match (self, other) {
130      (KeyDerivationStrategy::PBKDF2(rounds), KeyDerivationStrategy::PBKDF2(rounds2)) => {
131        rounds == rounds2
132      }
133    }
134  }
135}
136
137#[cfg(test)]
138mod tests {
139  use super::*;
140
141  #[test]
142  fn test_key_derivation() {
143    let password = "password".as_bytes();
144
145    let key = Key::<32, 32>::new(password, KeyDerivationStrategy::PBKDF2(10_000));
146    let key2 = Key::<32, 32>::with_salt(password, key.salt, KeyDerivationStrategy::PBKDF2(10_000));
147
148    assert_eq!(key.pubk, key2.pubk);
149  }
150
151  #[test]
152  fn test_key_derivation_with_different_salt() {
153    let password = "password".as_bytes();
154
155    let key = Key::<32, 32>::new(password, KeyDerivationStrategy::PBKDF2(10_000));
156    let key2 = Key::<32, 32>::new(password, KeyDerivationStrategy::PBKDF2(10_000));
157
158    assert_ne!(key.pubk, key2.pubk);
159  }
160
161  #[test]
162  fn test_key_derivation_with_different_rounds() {
163    let password = "password".as_bytes();
164
165    let key = Key::<32, 32>::new(password, KeyDerivationStrategy::PBKDF2(10_000));
166    let key2 = Key::<32, 32>::new(password, KeyDerivationStrategy::PBKDF2(11_000));
167
168    assert_ne!(key.pubk, key2.pubk);
169  }
170
171  #[test]
172  fn test_key_strategy_serialization_deserialization() {
173    let strategy = KeyDerivationStrategy::PBKDF2(10_000);
174
175    let serialized: Vec<u8> = strategy.clone().into();
176    let deserialized = KeyDerivationStrategy::try_from(serialized).unwrap();
177
178    assert_eq!(strategy, deserialized);
179  }
180}