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 way to verify a user password on login.
4//! ```rust
5//! use devolutions_crypto::password_hash::{hash_password, PasswordHashVersion};
6//!
7//! let password = b"somesuperstrongpa$$w0rd!";
8//!
9//! let hashed_password = hash_password(password, 10000, PasswordHashVersion::Latest);
10//!
11//! assert!(hashed_password.verify_password(b"somesuperstrongpa$$w0rd!"));
12//! assert!(!hashed_password.verify_password(b"someweakpa$$w0rd!"));
13//! ```
14
15mod password_hash_v1;
16
17use super::DataType;
18use super::Error;
19use super::Header;
20use super::HeaderType;
21use super::PasswordHashSubtype;
22pub use super::PasswordHashVersion;
23use super::Result;
24
25use password_hash_v1::PasswordHashV1;
26
27use std::borrow::Borrow;
28use std::convert::TryFrom;
29
30#[cfg(feature = "fuzz")]
31use arbitrary::Arbitrary;
32
33/// A versionned password hash. Can be used to validate a password without storing the password.
34#[derive(Clone, Debug)]
35#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
36pub struct PasswordHash {
37    pub(crate) header: Header<PasswordHash>,
38    payload: PasswordHashPayload,
39}
40
41impl HeaderType for PasswordHash {
42    type Version = PasswordHashVersion;
43    type Subtype = PasswordHashSubtype;
44
45    fn data_type() -> DataType {
46        DataType::PasswordHash
47    }
48}
49
50#[derive(Clone, Debug)]
51#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
52enum PasswordHashPayload {
53    V1(PasswordHashV1),
54}
55
56/// Creates a `PasswordHash` containing the password verifier.
57/// # Arguments
58///  * `password` - The password to hash.
59///  * `iterations` - The number of iterations of the password hash.
60///                     A higher number is slower but harder to brute-force.
61///                     The recommended is 10000, but the number can be set by the user.
62///  * `version` - Version of the library to hash the password with. Use `PasswordHashVersion::Latest` if you're not dealing with shared data.
63/// # Returns
64/// Returns the `PasswordHash` containing the password verifier.
65/// # Example
66/// ```rust
67/// use devolutions_crypto::password_hash::{hash_password, PasswordHashVersion};
68///
69/// let password = b"somesuperstrongpa$$w0rd!";
70///
71/// let hashed_password = hash_password(password, 10000, PasswordHashVersion::Latest);
72/// ```
73pub fn hash_password(
74    password: &[u8],
75    iterations: u32,
76    version: PasswordHashVersion,
77) -> PasswordHash {
78    let mut header = Header::default();
79
80    let payload = match version {
81        PasswordHashVersion::V1 | PasswordHashVersion::Latest => {
82            header.version = PasswordHashVersion::V1;
83            PasswordHashPayload::V1(PasswordHashV1::hash_password(password, iterations))
84        }
85    };
86
87    PasswordHash { header, payload }
88}
89
90impl PasswordHash {
91    /// Verify if the `PasswordHash` matches with the specified password. Should execute in constant time.
92    /// # Arguments
93    ///  * `password` - The password to verify.
94    /// # Returns
95    /// Returns true if the password matches and false if it doesn't.
96    /// # Example
97    /// ```rust
98    /// use devolutions_crypto::password_hash::{hash_password, PasswordHashVersion};
99    ///
100    /// let password = b"somesuperstrongpa$$w0rd!";
101    ///
102    /// let hashed_password = hash_password(password, 10000, PasswordHashVersion::Latest);
103    /// assert!(hashed_password.verify_password(b"somesuperstrongpa$$w0rd!"));
104    /// assert!(!hashed_password.verify_password(b"someweakpa$$w0rd!"));
105    /// ```
106    pub fn verify_password(&self, password: &[u8]) -> bool {
107        match &self.payload {
108            PasswordHashPayload::V1(x) => x.verify_password(password),
109        }
110    }
111}
112
113impl From<PasswordHash> for Vec<u8> {
114    /// Serialize the structure into a `Vec<u8>`, for storage, transmission or use in another language.
115    fn from(data: PasswordHash) -> Self {
116        let mut header: Self = data.header.borrow().into();
117        let mut payload: Self = data.payload.into();
118        header.append(&mut payload);
119        header
120    }
121}
122
123impl TryFrom<&[u8]> for PasswordHash {
124    type Error = Error;
125
126    /// Parses the data. Can return an Error of the data is invalid or unrecognized.
127    fn try_from(data: &[u8]) -> Result<Self> {
128        if data.len() < Header::len() {
129            return Err(Error::InvalidLength);
130        };
131
132        let header = Header::try_from(&data[0..Header::len()])?;
133
134        let payload = match header.version {
135            PasswordHashVersion::V1 => {
136                PasswordHashPayload::V1(PasswordHashV1::try_from(&data[Header::len()..])?)
137            }
138            _ => return Err(Error::UnknownVersion),
139        };
140
141        Ok(Self { header, payload })
142    }
143}
144
145impl From<PasswordHashPayload> for Vec<u8> {
146    fn from(data: PasswordHashPayload) -> Self {
147        match data {
148            PasswordHashPayload::V1(x) => x.into(),
149        }
150    }
151}
152
153#[test]
154fn password_test() {
155    let pass = "thisisaveryveryverystrongPa$$w0rd , //".as_bytes();
156    let iterations = 10u32;
157
158    let hash = hash_password(pass, iterations, PasswordHashVersion::Latest);
159
160    assert!(hash.verify_password(pass));
161    assert!(!hash.verify_password("averybadpassword".as_bytes()))
162}