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}