1#![no_std]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![doc = include_str!("../README.md")]
4#![doc(
5 html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg",
6 html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg"
7)]
8#![warn(
9 clippy::checked_conversions,
10 clippy::arithmetic_side_effects,
11 clippy::panic,
12 clippy::panic_in_result_fn,
13 clippy::unwrap_used,
14 missing_docs,
15 rust_2018_idioms,
16 unused_lifetimes,
17 unused_qualifications
18)]
19
20extern crate alloc;
21#[cfg(feature = "std")]
22extern crate std;
23
24mod errors;
25
26pub use crate::errors::{ParseError, VerifyError};
27
28use alloc::string::{String, ToString};
29use password_hash::{
30 PasswordHasher, PasswordVerifier,
31 phc::{ParamsString, PasswordHash, Salt},
32};
33
34#[cfg(not(any(feature = "argon2", feature = "pbkdf2", feature = "scrypt")))]
35compile_error!(
36 "please enable at least one password hash crate feature, e.g. argon2, pbkdf2, scrypt"
37);
38
39#[cfg(feature = "argon2")]
40use argon2::Argon2;
41#[cfg(feature = "pbkdf2")]
42use pbkdf2::Pbkdf2;
43#[cfg(feature = "scrypt")]
44use scrypt::Scrypt;
45
46pub fn generate_hash(password: impl AsRef<[u8]>) -> String {
51 let salt = Salt::generate();
52 generate_phc_hash(password.as_ref(), &salt)
53 .map(|hash| hash.to_string())
54 .expect("password hashing error")
55}
56
57#[allow(unreachable_code)]
59fn generate_phc_hash(password: &[u8], salt: &[u8]) -> password_hash::Result<PasswordHash> {
60 #[cfg(feature = "argon2")]
64 return Argon2::default().hash_password(password, salt);
65
66 #[cfg(feature = "scrypt")]
67 return Scrypt.hash_password(password, salt);
68
69 #[cfg(feature = "pbkdf2")]
70 return Pbkdf2.hash_password(password, salt);
71}
72
73pub fn verify_password(password: impl AsRef<[u8]>, hash: &str) -> Result<(), VerifyError> {
81 let hash = PasswordHash::new(hash).map_err(ParseError::new)?;
82
83 let algs: &[&dyn PasswordVerifier<PasswordHash>] = &[
84 #[cfg(feature = "argon2")]
85 &Argon2::default(),
86 #[cfg(feature = "pbkdf2")]
87 &Pbkdf2,
88 #[cfg(feature = "scrypt")]
89 &Scrypt,
90 ];
91
92 hash.verify_password(algs, password)
93 .map_err(|_| VerifyError::PasswordInvalid)
94}
95
96#[allow(unreachable_code)]
108pub fn is_hash_obsolete(hash: &str) -> Result<bool, ParseError> {
109 let hash = PasswordHash::new(hash).map_err(ParseError::new)?;
110
111 #[cfg(feature = "argon2")]
112 return Ok(hash.algorithm != argon2::Algorithm::default().ident()
113 || hash.params != default_params_string::<argon2::Params>());
114
115 #[cfg(feature = "scrypt")]
116 return Ok(hash.algorithm != scrypt::ALG_ID
117 || hash.params != default_params_string::<scrypt::Params>());
118
119 #[cfg(feature = "pbkdf2")]
120 return Ok(hash.algorithm != *pbkdf2::Algorithm::default().ident()
121 || hash.params != default_params_string::<pbkdf2::Params>());
122
123 Ok(true)
124}
125
126fn default_params_string<T>() -> ParamsString
127where
128 T: Default + TryInto<ParamsString, Error = password_hash::Error>,
129{
130 T::default().try_into().expect("invalid default params")
131}
132
133#[cfg(test)]
134mod tests {
135 use super::{generate_hash, is_hash_obsolete, verify_password};
136
137 const EXAMPLE_PASSWORD: &str = "password";
138
139 #[test]
140 fn happy_path() {
141 let hash = generate_hash(EXAMPLE_PASSWORD);
142 assert!(verify_password(EXAMPLE_PASSWORD, &hash).is_ok());
143 assert!(verify_password("bogus", &hash).is_err());
144 assert!(!is_hash_obsolete(&hash).expect("hash can be parsed"));
145 }
146
147 #[cfg(feature = "argon2")]
148 mod argon2 {
149 use super::{EXAMPLE_PASSWORD, verify_password};
150
151 const EXAMPLE_HASH: &str = "$argon2i$v=19$m=65536,t=1,p=1$c29tZXNhbHQAAAAAAAAAAA$+r0d29hqEB0yasKr55ZgICsQGSkl0v0kgwhd+U3wyRo";
153
154 #[test]
155 fn verify() {
156 assert!(verify_password(EXAMPLE_PASSWORD, EXAMPLE_HASH).is_ok());
157 assert!(verify_password("bogus", EXAMPLE_HASH).is_err());
158 }
159 }
160
161 #[cfg(feature = "pbkdf2")]
162 mod pdkdf2 {
163 use super::verify_password;
164
165 const EXAMPLE_PASSWORD: &[u8] = b"passwordPASSWORDpassword";
167
168 const EXAMPLE_HASH: &str = "$pbkdf2-sha256$i=4096,l=40\
170 $c2FsdFNBTFRzYWx0U0FMVHNhbHRTQUxUc2FsdFNBTFRzYWx0\
171 $NIyJ28vTKy8y2BS4EW6EzysXNH68GAAYHE4qH7jdU+HGNVGMfaxH6Q";
172
173 #[test]
174 fn verify() {
175 assert!(verify_password(EXAMPLE_PASSWORD, EXAMPLE_HASH).is_ok());
176 assert!(verify_password("bogus", EXAMPLE_HASH).is_err());
177 }
178 }
179
180 #[cfg(feature = "scrypt")]
181 mod scrypt {
182 use super::{EXAMPLE_PASSWORD, verify_password};
183
184 const EXAMPLE_HASH: &str = "$scrypt$ln=16,r=8,p=1$aM15713r3Xsvxbi31lqr1Q$nFNh2CVHVjNldFVKDHDlm4CbdRSCdEBsjjJxD+iCs5E";
186
187 #[test]
188 fn verify() {
189 assert!(verify_password(EXAMPLE_PASSWORD, EXAMPLE_HASH).is_ok());
190 assert!(verify_password("bogus", EXAMPLE_HASH).is_err());
191 }
192 }
193}