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::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]
28#[cfg_attr(feature = "dangerous-crypto-debug", derive(Debug))]
29pub struct KdfDerivedKeyMaterial(pub(super) Pin<Box<GenericArray<u8, U32>>>);
30
31impl KdfDerivedKeyMaterial {
32    /// Derive a key from a secret and salt using the provided KDF.
33    pub(super) fn derive_kdf_key(
34        secret: &[u8],
35        salt: &[u8],
36        kdf: &Kdf,
37    ) -> Result<Self, CryptoError> {
38        match kdf {
39            Kdf::PBKDF2 { iterations } => {
40                let iterations = iterations.get();
41                if iterations < PBKDF2_MIN_ITERATIONS {
42                    return Err(CryptoError::InsufficientKdfParameters);
43                }
44
45                let mut hash = crate::util::pbkdf2(secret, salt, iterations);
46
47                let key_material = Box::pin(hash.into());
48                hash.zeroize();
49                Ok(KdfDerivedKeyMaterial(key_material))
50            }
51            Kdf::Argon2id {
52                iterations,
53                memory,
54                parallelism,
55            } => {
56                let memory = memory.get() * 1024; // Convert MiB to KiB;
57                let iterations = iterations.get();
58                let parallelism = parallelism.get();
59
60                if memory < ARGON2ID_MIN_MEMORY
61                    || iterations < ARGON2ID_MIN_ITERATIONS
62                    || parallelism < ARGON2ID_MIN_PARALLELISM
63                {
64                    return Err(CryptoError::InsufficientKdfParameters);
65                }
66
67                let salt_sha = sha2::Sha256::new().chain_update(salt).finalize();
68
69                let mut hash = Box::pin(GenericArray::<u8, U32>::default());
70
71                use argon2::*;
72                let params = Params::new(memory, iterations, parallelism, Some(32))?;
73                let argon = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
74                argon.hash_password_into(secret, &salt_sha, hash.as_mut_slice())?;
75
76                // Argon2 is using some stack memory that is not zeroed. Eventually some function
77                // will overwrite the stack, but we use this trick to force the used
78                // stack to be zeroed.
79                #[inline(never)]
80                fn clear_stack() {
81                    std::hint::black_box([0u8; 4096]);
82                }
83                clear_stack();
84
85                Ok(KdfDerivedKeyMaterial(hash))
86            }
87        }
88    }
89
90    /// Derives a users master key from their password, email and KDF.
91    ///
92    /// Note: the email is trimmed and converted to lowercase before being used.
93    pub(super) fn derive(password: &str, email: &str, kdf: &Kdf) -> Result<Self, CryptoError> {
94        Self::derive_kdf_key(
95            password.as_bytes(),
96            email.trim().to_lowercase().as_bytes(),
97            kdf,
98        )
99    }
100}
101
102#[deprecated(
103    note = "This function is only meant as a temporary stop-gap to expose KDF derivation in PureCrypto until the higher-level consumers are moved to the SDK directly. DO NOT USE THIS OUTSIDE OF PureCrypto!"
104)]
105/// Derives KDF material given a password, salt and kdf configuration. This function is
106/// a stop-gap solution and should not be used outside of PureCrypto.
107///
108/// The clean-up ticket is tracked here:
109/// `https://bitwarden.atlassian.net/browse/PM-23168`
110pub fn dangerous_derive_kdf_material(
111    password: &[u8],
112    salt: &[u8],
113    kdf: &Kdf,
114) -> Result<Vec<u8>, CryptoError> {
115    KdfDerivedKeyMaterial::derive_kdf_key(password, salt, kdf).map(|kdf_key| kdf_key.0.to_vec())
116}
117
118/// Key Derivation Function for Bitwarden Account
119///
120/// In Bitwarden accounts can use multiple KDFs to derive their master key from their password. This
121/// Enum represents all the possible KDFs.
122#[allow(missing_docs)]
123#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone, PartialEq)]
124#[serde(rename_all = "camelCase", deny_unknown_fields)]
125#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
126#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
127pub enum Kdf {
128    PBKDF2 {
129        iterations: NonZeroU32,
130    },
131    Argon2id {
132        iterations: NonZeroU32,
133        memory: NonZeroU32,
134        parallelism: NonZeroU32,
135    },
136}
137
138impl Kdf {
139    /// Default KDF for new encryption V1 accounts.
140    pub fn default_pbkdf2() -> Kdf {
141        Kdf::PBKDF2 {
142            iterations: default_pbkdf2_iterations(),
143        }
144    }
145
146    /// Default KDF for new encryption V2 accounts.
147    pub fn default_argon2() -> Kdf {
148        Kdf::Argon2id {
149            iterations: default_argon2_iterations(),
150            memory: default_argon2_memory(),
151            parallelism: default_argon2_parallelism(),
152        }
153    }
154}
155
156/// Default PBKDF2 iterations
157fn default_pbkdf2_iterations() -> NonZeroU32 {
158    NonZeroU32::new(600_000).expect("Non-zero number")
159}
160/// Default Argon2 iterations
161fn default_argon2_iterations() -> NonZeroU32 {
162    NonZeroU32::new(6).expect("Non-zero number")
163}
164/// Default Argon2 memory
165fn default_argon2_memory() -> NonZeroU32 {
166    NonZeroU32::new(32).expect("Non-zero number")
167}
168/// Default Argon2 parallelism
169fn default_argon2_parallelism() -> NonZeroU32 {
170    NonZeroU32::new(3).expect("Non-zero number")
171}
172
173#[cfg(test)]
174mod tests {
175    use std::num::{NonZero, NonZeroU32};
176
177    use crate::keys::kdf::{Kdf, KdfDerivedKeyMaterial};
178
179    #[test]
180    fn test_derive_kdf_minimums() {
181        fn nz(n: u32) -> NonZero<u32> {
182            NonZero::new(n).unwrap()
183        }
184
185        let secret = [0u8; 32];
186        let salt = [0u8; 32];
187
188        for kdf in [
189            Kdf::PBKDF2 {
190                iterations: nz(4999),
191            },
192            Kdf::Argon2id {
193                iterations: nz(1),
194                memory: nz(16),
195                parallelism: nz(1),
196            },
197            Kdf::Argon2id {
198                iterations: nz(2),
199                memory: nz(15),
200                parallelism: nz(1),
201            },
202            Kdf::Argon2id {
203                iterations: nz(1),
204                memory: nz(15),
205                parallelism: nz(1),
206            },
207        ] {
208            assert_eq!(
209                KdfDerivedKeyMaterial::derive_kdf_key(&secret, &salt, &kdf)
210                    .err()
211                    .unwrap()
212                    .to_string(),
213                "Insufficient KDF parameters"
214            );
215        }
216    }
217
218    #[test]
219    fn test_master_key_derive_pbkdf2() {
220        let kdf_key = KdfDerivedKeyMaterial::derive(
221            "67t9b5g67$%Dh89n",
222            "test_key",
223            &Kdf::PBKDF2 {
224                iterations: NonZeroU32::new(10000).unwrap(),
225            },
226        )
227        .unwrap();
228
229        assert_eq!(
230            [
231                31, 79, 104, 226, 150, 71, 177, 90, 194, 80, 172, 209, 17, 129, 132, 81, 138, 167,
232                69, 167, 254, 149, 2, 27, 39, 197, 64, 42, 22, 195, 86, 75
233            ],
234            kdf_key.0.as_slice()
235        );
236    }
237
238    #[test]
239    fn test_master_key_derive_argon2() {
240        let kdf_key = KdfDerivedKeyMaterial::derive(
241            "67t9b5g67$%Dh89n",
242            "test_key",
243            &Kdf::Argon2id {
244                iterations: NonZeroU32::new(4).unwrap(),
245                memory: NonZeroU32::new(32).unwrap(),
246                parallelism: NonZeroU32::new(2).unwrap(),
247            },
248        )
249        .unwrap();
250
251        assert_eq!(
252            [
253                207, 240, 225, 177, 162, 19, 163, 76, 98, 106, 179, 175, 224, 9, 17, 240, 20, 147,
254                237, 47, 246, 150, 141, 184, 62, 225, 131, 242, 51, 53, 225, 242
255            ],
256            kdf_key.0.as_slice()
257        );
258    }
259}