bitwarden_crypto/keys/
kdf.rs

1use std::{num::NonZeroU32, pin::Pin};
2
3use generic_array::GenericArray;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6use sha2::Digest;
7#[cfg(feature = "wasm")]
8use tsify_next::Tsify;
9use typenum::U32;
10use zeroize::Zeroize;
11
12use crate::CryptoError;
13
14const PBKDF2_MIN_ITERATIONS: u32 = 5000;
15
16const ARGON2ID_MIN_MEMORY: u32 = 16 * 1024;
17const ARGON2ID_MIN_ITERATIONS: u32 = 2;
18const ARGON2ID_MIN_PARALLELISM: u32 = 1;
19
20/// Holding struct for key material derived from a KDF.
21///
22/// The internal key material should not be used directly for cryptographic operations. Instead it
23/// MUST be converted to the appropriate type such as `SymmetricCryptoKey`, `MasterKey` or any other
24/// key type. This can be done by either directly consuming the key material or by stretching it
25/// further using HKDF (HMAC-based Key Derivation Function).
26///
27/// Uses a pinned heap data structure, as noted in [Pinned heap data][crate#pinned-heap-data]
28pub struct KdfDerivedKeyMaterial(pub(super) Pin<Box<GenericArray<u8, U32>>>);
29
30impl KdfDerivedKeyMaterial {
31    /// Derive a key from a secret and salt using the provided KDF.
32    pub(super) fn derive_kdf_key(
33        secret: &[u8],
34        salt: &[u8],
35        kdf: &Kdf,
36    ) -> Result<Self, CryptoError> {
37        let mut hash = match kdf {
38            Kdf::PBKDF2 { iterations } => {
39                let iterations = iterations.get();
40                if iterations < PBKDF2_MIN_ITERATIONS {
41                    return Err(CryptoError::InsufficientKdfParameters);
42                }
43
44                crate::util::pbkdf2(secret, salt, iterations)
45            }
46            Kdf::Argon2id {
47                iterations,
48                memory,
49                parallelism,
50            } => {
51                let memory = memory.get() * 1024; // Convert MiB to KiB;
52                let iterations = iterations.get();
53                let parallelism = parallelism.get();
54
55                if memory < ARGON2ID_MIN_MEMORY
56                    || iterations < ARGON2ID_MIN_ITERATIONS
57                    || parallelism < ARGON2ID_MIN_PARALLELISM
58                {
59                    return Err(CryptoError::InsufficientKdfParameters);
60                }
61
62                use argon2::*;
63
64                let params = Params::new(memory, iterations, parallelism, Some(32))?;
65                let argon = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
66
67                let salt_sha = sha2::Sha256::new().chain_update(salt).finalize();
68
69                let mut hash = [0u8; 32];
70                argon.hash_password_into(secret, &salt_sha, &mut hash)?;
71
72                // Argon2 is using some stack memory that is not zeroed. Eventually some function
73                // will overwrite the stack, but we use this trick to force the used
74                // stack to be zeroed.
75                #[inline(never)]
76                fn clear_stack() {
77                    std::hint::black_box([0u8; 4096]);
78                }
79                clear_stack();
80
81                hash
82            }
83        };
84        let key_material = Box::pin(GenericArray::clone_from_slice(&hash));
85        hash.zeroize();
86        Ok(KdfDerivedKeyMaterial(key_material))
87    }
88
89    /// Derives a users master key from their password, email and KDF.
90    ///
91    /// Note: the email is trimmed and converted to lowercase before being used.
92    pub(super) fn derive(password: &str, email: &str, kdf: &Kdf) -> Result<Self, CryptoError> {
93        Self::derive_kdf_key(
94            password.as_bytes(),
95            email.trim().to_lowercase().as_bytes(),
96            kdf,
97        )
98    }
99}
100
101/// Key Derivation Function for Bitwarden Account
102///
103/// In Bitwarden accounts can use multiple KDFs to derive their master key from their password. This
104/// Enum represents all the possible KDFs.
105#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)]
106#[serde(rename_all = "camelCase", deny_unknown_fields)]
107#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
108#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
109pub enum Kdf {
110    PBKDF2 {
111        iterations: NonZeroU32,
112    },
113    Argon2id {
114        iterations: NonZeroU32,
115        memory: NonZeroU32,
116        parallelism: NonZeroU32,
117    },
118}
119
120impl Default for Kdf {
121    /// Default KDF for new accounts.
122    fn default() -> Self {
123        Kdf::PBKDF2 {
124            iterations: default_pbkdf2_iterations(),
125        }
126    }
127}
128
129/// Default PBKDF2 iterations
130pub fn default_pbkdf2_iterations() -> NonZeroU32 {
131    NonZeroU32::new(600_000).expect("Non-zero number")
132}
133/// Default Argon2 iterations
134pub fn default_argon2_iterations() -> NonZeroU32 {
135    NonZeroU32::new(3).expect("Non-zero number")
136}
137/// Default Argon2 memory
138pub fn default_argon2_memory() -> NonZeroU32 {
139    NonZeroU32::new(64).expect("Non-zero number")
140}
141/// Default Argon2 parallelism
142pub fn default_argon2_parallelism() -> NonZeroU32 {
143    NonZeroU32::new(4).expect("Non-zero number")
144}
145
146#[cfg(test)]
147mod tests {
148    use std::num::{NonZero, NonZeroU32};
149
150    use crate::keys::kdf::{Kdf, KdfDerivedKeyMaterial};
151
152    #[test]
153    fn test_derive_kdf_minimums() {
154        fn nz(n: u32) -> NonZero<u32> {
155            NonZero::new(n).unwrap()
156        }
157
158        let secret = [0u8; 32];
159        let salt = [0u8; 32];
160
161        for kdf in [
162            Kdf::PBKDF2 {
163                iterations: nz(4999),
164            },
165            Kdf::Argon2id {
166                iterations: nz(1),
167                memory: nz(16),
168                parallelism: nz(1),
169            },
170            Kdf::Argon2id {
171                iterations: nz(2),
172                memory: nz(15),
173                parallelism: nz(1),
174            },
175            Kdf::Argon2id {
176                iterations: nz(1),
177                memory: nz(15),
178                parallelism: nz(1),
179            },
180        ] {
181            assert_eq!(
182                KdfDerivedKeyMaterial::derive_kdf_key(&secret, &salt, &kdf)
183                    .err()
184                    .unwrap()
185                    .to_string(),
186                "Insufficient KDF parameters"
187            );
188        }
189    }
190
191    #[test]
192    fn test_master_key_derive_pbkdf2() {
193        let kdf_key = KdfDerivedKeyMaterial::derive(
194            "67t9b5g67$%Dh89n",
195            "test_key",
196            &Kdf::PBKDF2 {
197                iterations: NonZeroU32::new(10000).unwrap(),
198            },
199        )
200        .unwrap();
201
202        assert_eq!(
203            [
204                31, 79, 104, 226, 150, 71, 177, 90, 194, 80, 172, 209, 17, 129, 132, 81, 138, 167,
205                69, 167, 254, 149, 2, 27, 39, 197, 64, 42, 22, 195, 86, 75
206            ],
207            kdf_key.0.as_slice()
208        );
209    }
210
211    #[test]
212    fn test_master_key_derive_argon2() {
213        let kdf_key = KdfDerivedKeyMaterial::derive(
214            "67t9b5g67$%Dh89n",
215            "test_key",
216            &Kdf::Argon2id {
217                iterations: NonZeroU32::new(4).unwrap(),
218                memory: NonZeroU32::new(32).unwrap(),
219                parallelism: NonZeroU32::new(2).unwrap(),
220            },
221        )
222        .unwrap();
223
224        assert_eq!(
225            [
226                207, 240, 225, 177, 162, 19, 163, 76, 98, 106, 179, 175, 224, 9, 17, 240, 20, 147,
227                237, 47, 246, 150, 141, 184, 62, 225, 131, 242, 51, 53, 225, 242
228            ],
229            kdf_key.0.as_slice()
230        );
231    }
232}