Skip to main content

devolutions_crypto/password_hash/
mod.rs

1//! Module for password hashing and verification. Use this if you need to store user passwords.
2//!
3//! You can use this module to hash a password and validate it afterward. This is the recommended
4//! way to verify a user password on login.
5//!
6//! The default algorithm (`PasswordHashVersion::Latest`) is **Argon2id**.
7//!
8//! ```rust
9//! use devolutions_crypto::password_hash::{hash_password, PasswordHashVersion};
10//!
11//! let password = b"somesuperstrongpa$$w0rd!";
12//!
13//! let hashed_password = hash_password(password, PasswordHashVersion::Latest).expect("hash password shouldn't fail");
14//!
15//! assert!(hashed_password.verify_password(b"somesuperstrongpa$$w0rd!"));
16//! assert!(!hashed_password.verify_password(b"someweakpa$$w0rd!"));
17//! ```
18
19mod password_hash_v1;
20mod password_hash_v2;
21
22use super::DataType;
23use super::Error;
24use super::Header;
25use super::HeaderType;
26use super::PasswordHashSubtype;
27pub use super::PasswordHashVersion;
28use super::Result;
29
30use super::Argon2;
31use super::Argon2Parameters;
32use super::DEFAULT_PBKDF2_ITERATIONS;
33use crate::key_derivation::DerivationParameters;
34
35use password_hash_v1::PasswordHashV1;
36use password_hash_v2::PasswordHashV2;
37
38use std::borrow::Borrow;
39use std::convert::TryFrom;
40
41#[cfg(feature = "fuzz")]
42use arbitrary::Arbitrary;
43
44/// A versionned password hash. Can be used to validate a password without storing the password.
45#[derive(Clone, Debug)]
46#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
47pub struct PasswordHash {
48    pub(crate) header: Header<PasswordHash>,
49    payload: PasswordHashPayload,
50}
51
52impl HeaderType for PasswordHash {
53    type Version = PasswordHashVersion;
54    type Subtype = PasswordHashSubtype;
55
56    fn data_type() -> DataType {
57        DataType::PasswordHash
58    }
59}
60
61#[derive(Clone, Debug)]
62#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
63enum PasswordHashPayload {
64    V1(PasswordHashV1),
65    V2(PasswordHashV2),
66}
67
68/// Creates a `PasswordHash` containing the password verifier.
69///
70/// Uses a secure default for each version:
71/// - `V1` (PBKDF2-HMAC-SHA256): [`DEFAULT_PBKDF2_ITERATIONS`] iterations.
72/// - `V2` / `Latest` (Argon2id): 64 MiB memory, 3 time iterations (OWASP recommendation).
73///
74/// Use [`hash_password_with_parameters`] when you need to tune the algorithm parameters.
75///
76/// # Arguments
77///  * `password` - The password to hash.
78///  * `version` - Version of the algorithm to use. Use `PasswordHashVersion::Latest` if you're
79///    not dealing with shared data.
80/// # Returns
81/// Returns the `PasswordHash` containing the password verifier.
82/// # Example
83/// ```rust
84/// use devolutions_crypto::password_hash::{hash_password, PasswordHashVersion};
85///
86/// let password = b"somesuperstrongpa$$w0rd!";
87///
88/// let hashed_password = hash_password(password, PasswordHashVersion::Latest);
89/// ```
90pub fn hash_password(password: &[u8], version: PasswordHashVersion) -> Result<PasswordHash> {
91    let mut header = Header::default();
92
93    let payload = match version {
94        PasswordHashVersion::V1 => {
95            header.version = PasswordHashVersion::V1;
96            PasswordHashPayload::V1(PasswordHashV1::hash_password(
97                password,
98                DEFAULT_PBKDF2_ITERATIONS,
99            )?)
100        }
101        PasswordHashVersion::V2 | PasswordHashVersion::Latest => {
102            header.version = PasswordHashVersion::V2;
103            let mut argon2_params = Argon2Parameters::default();
104            argon2_params.memory = password_hash_v2::defaults::MEMORY_KIB;
105            argon2_params.iterations = password_hash_v2::defaults::ITERATIONS;
106            let params = Argon2::with_params(argon2_params).parameters();
107            PasswordHashPayload::V2(PasswordHashV2::hash_password(password, params)?)
108        }
109    };
110
111    Ok(PasswordHash { header, payload })
112}
113
114/// Creates a `PasswordHash` using caller-supplied [`DerivationParameters`].
115///
116/// The derivation algorithm (Argon2id or PBKDF2) is determined by what is encoded inside
117/// `params`. Obtain fresh parameters via [`crate::key_derivation::Argon2::parameters`] or
118/// [`crate::key_derivation::Pbkdf2::parameters`].
119///
120/// # Example
121/// ```rust
122/// use devolutions_crypto::password_hash::hash_password_with_parameters;
123/// use devolutions_crypto::key_derivation::Argon2;
124/// use devolutions_crypto::Argon2Parameters;
125///
126/// let mut params_cfg = Argon2Parameters::default();
127/// params_cfg.memory = 131072; // 128 MiB
128/// params_cfg.iterations = 4;
129/// let params = Argon2::with_params(params_cfg).parameters();
130///
131/// let hash = hash_password_with_parameters(b"pa$$word", params).expect("should not fail");
132/// assert!(hash.verify_password(b"pa$$word"));
133/// ```
134pub fn hash_password_with_parameters(
135    password: &[u8],
136    params: DerivationParameters,
137) -> Result<PasswordHash> {
138    let mut header = Header::default();
139    header.version = PasswordHashVersion::V2;
140    let payload = PasswordHashPayload::V2(PasswordHashV2::hash_password(password, params)?);
141    Ok(PasswordHash { header, payload })
142}
143
144impl PasswordHash {
145    /// Verify if the `PasswordHash` matches with the specified password. Should execute in constant time.
146    /// # Arguments
147    ///  * `password` - The password to verify.
148    /// # Returns
149    /// Returns true if the password matches and false if it doesn't.
150    /// # Example
151    /// ```rust
152    /// use devolutions_crypto::password_hash::{hash_password, PasswordHashVersion};
153    ///
154    /// let password = b"somesuperstrongpa$$w0rd!";
155    ///
156    /// let hashed_password = hash_password(password, PasswordHashVersion::Latest).expect("hash password shouldn't fail");
157    /// assert!(hashed_password.verify_password(b"somesuperstrongpa$$w0rd!"));
158    /// assert!(!hashed_password.verify_password(b"someweakpa$$w0rd!"));
159    /// ```
160    pub fn verify_password(&self, password: &[u8]) -> bool {
161        match &self.payload {
162            PasswordHashPayload::V1(x) => x.verify_password(password),
163            PasswordHashPayload::V2(x) => x.verify_password(password),
164        }
165    }
166}
167
168impl From<PasswordHash> for Vec<u8> {
169    /// Serialize the structure into a `Vec<u8>`, for storage, transmission or use in another language.
170    fn from(data: PasswordHash) -> Self {
171        let mut header: Self = data.header.borrow().into();
172        let mut payload: Self = data.payload.into();
173        header.append(&mut payload);
174        header
175    }
176}
177
178impl TryFrom<&[u8]> for PasswordHash {
179    type Error = Error;
180
181    /// Parses the data. Can return an Error of the data is invalid or unrecognized.
182    fn try_from(data: &[u8]) -> Result<Self> {
183        if data.len() < Header::len() {
184            return Err(Error::InvalidLength);
185        };
186
187        let header = Header::try_from(&data[0..Header::len()])?;
188
189        let payload = match header.version {
190            PasswordHashVersion::V1 => {
191                PasswordHashPayload::V1(PasswordHashV1::try_from(&data[Header::len()..])?)
192            }
193            PasswordHashVersion::V2 => {
194                PasswordHashPayload::V2(PasswordHashV2::try_from(&data[Header::len()..])?)
195            }
196            _ => return Err(Error::UnknownVersion),
197        };
198
199        Ok(Self { header, payload })
200    }
201}
202
203impl From<PasswordHashPayload> for Vec<u8> {
204    fn from(data: PasswordHashPayload) -> Self {
205        match data {
206            PasswordHashPayload::V1(x) => x.into(),
207            PasswordHashPayload::V2(x) => x.into(),
208        }
209    }
210}
211
212#[test]
213fn hash_password_test() {
214    let pass = "thisisaveryveryverystrongPa$$w0rd , //".as_bytes();
215    let hash = hash_password(pass, PasswordHashVersion::Latest).unwrap();
216
217    assert!(hash.verify_password(pass));
218    assert!(!hash.verify_password("averybadpassword".as_bytes()))
219}
220
221#[test]
222fn password_v2_roundtrip_bytes() {
223    let pass = b"pa$$w0rd";
224
225    let mut argon2_params = Argon2Parameters::default();
226    argon2_params.memory = 32;
227    argon2_params.iterations = 2;
228    let params = Argon2::with_params(argon2_params).parameters();
229    let hash = hash_password_with_parameters(pass, params).unwrap();
230    let bytes: Vec<u8> = hash.into();
231
232    let hash2 = PasswordHash::try_from(bytes.as_slice()).unwrap();
233    assert!(hash2.verify_password(pass));
234    assert!(!hash2.verify_password(b"wrongpassword"));
235}
236
237#[test]
238fn password_v1_roundtrip_bytes() {
239    use crate::key_derivation::Pbkdf2;
240
241    let pass = b"pa$$word";
242    // Use very low iterations so the test finishes quickly.
243    let params = Pbkdf2::with_params(10).parameters().unwrap();
244    let hash = hash_password_with_parameters(pass, params).unwrap();
245    let bytes: Vec<u8> = hash.into();
246
247    let hash2 = PasswordHash::try_from(bytes.as_slice()).unwrap();
248    assert!(hash2.verify_password(pass));
249    assert!(!hash2.verify_password(b"wrongpassword"));
250}