devolutions_crypto/password_hash/
mod.rs1mod 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#[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
68pub 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
114pub 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 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 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 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 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}