1 //! This crate defines a set of traits which describe the functionality of 2 //! [password hashing algorithms]. 3 //! 4 //! Provides a `no_std`-friendly implementation of the 5 //! [Password Hashing Competition (PHC) string format specification][PHC] 6 //! (a well-defined subset of the [Modular Crypt Format a.k.a. MCF][MCF]) which 7 //! works in conjunction with the traits this crate defines. 8 //! 9 //! # Supported Crates 10 //! 11 //! See [RustCrypto/password-hashes] for algorithm implementations which use 12 //! this crate for interoperability: 13 //! 14 //! - [`argon2`] - Argon2 memory hard key derivation function 15 //! - [`pbkdf2`] - Password-Based Key Derivation Function v2 16 //! - [`scrypt`] - scrypt key derivation function 17 //! 18 //! # Usage 19 //! 20 //! This crate represents password hashes using the [`PasswordHash`] type, which 21 //! represents a parsed "PHC string" with the following format: 22 //! 23 //! ```text 24 //! $<id>[$v=<version>][$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]] 25 //! ``` 26 //! 27 //! For more information, please see the documentation for [`PasswordHash`]. 28 //! 29 //! [password hashing algorithms]: https://en.wikipedia.org/wiki/Cryptographic_hash_function#Password_verification 30 //! [PHC]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md 31 //! [MCF]: https://passlib.readthedocs.io/en/stable/modular_crypt_format.html 32 //! [RustCrypto/password-hashes]: https://github.com/RustCrypto/password-hashes 33 //! [`argon2`]: https://docs.rs/argon2 34 //! [`pbkdf2`]: https://docs.rs/pbkdf2 35 //! [`scrypt`]: https://docs.rs/scrypt 36 37 #![no_std] 38 #![cfg_attr(docsrs, feature(doc_cfg))] 39 #![doc( 40 html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", 41 html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg", 42 html_root_url = "https://docs.rs/password-hash/0.2.3" 43 )] 44 #![forbid(unsafe_code)] 45 #![warn(missing_docs, rust_2018_idioms)] 46 47 #[cfg(all(feature = "alloc", test))] 48 extern crate alloc; 49 50 #[cfg(feature = "std")] 51 extern crate std; 52 53 #[cfg(feature = "rand_core")] 54 #[cfg_attr(docsrs, doc(cfg(feature = "rand_core")))] 55 pub use rand_core; 56 57 mod encoding; 58 mod errors; 59 mod ident; 60 mod output; 61 mod params; 62 mod salt; 63 mod value; 64 65 pub use crate::{ 66 encoding::Encoding, 67 errors::{B64Error, Error, Result}, 68 ident::Ident, 69 output::Output, 70 params::ParamsString, 71 salt::{Salt, SaltString}, 72 value::{Decimal, Value}, 73 }; 74 75 use core::{ 76 convert::{TryFrom, TryInto}, 77 fmt::{self, Debug}, 78 }; 79 80 /// Separator character used in password hashes (e.g. `$6$...`). 81 const PASSWORD_HASH_SEPARATOR: char = '$'; 82 83 /// Trait for password hashing functions. 84 pub trait PasswordHasher { 85 /// Algorithm-specific parameters. 86 type Params: Clone 87 + Debug 88 + Default 89 + for<'a> TryFrom<&'a PasswordHash<'a>, Error = Error> 90 + for<'a> TryInto<ParamsString, Error = Error>; 91 92 /// Simple API for computing a [`PasswordHash`] from a password and 93 /// [`Salt`] value. 94 /// 95 /// Uses the default recommended parameters for a given algorithm. hash_password_simple<'a, S>(&self, password: &[u8], salt: &'a S) -> Result<PasswordHash<'a>> where S: AsRef<str> + ?Sized,96 fn hash_password_simple<'a, S>(&self, password: &[u8], salt: &'a S) -> Result<PasswordHash<'a>> 97 where 98 S: AsRef<str> + ?Sized, 99 { 100 self.hash_password( 101 password, 102 None, 103 Self::Params::default(), 104 Salt::try_from(salt.as_ref())?, 105 ) 106 } 107 108 /// Compute a [`PasswordHash`] with the given algorithm [`Ident`] 109 /// (or `None` for the recommended default), password, salt, and 110 /// parameters. hash_password<'a>( &self, password: &[u8], algorithm: Option<Ident<'a>>, params: Self::Params, salt: impl Into<Salt<'a>>, ) -> Result<PasswordHash<'a>>111 fn hash_password<'a>( 112 &self, 113 password: &[u8], 114 algorithm: Option<Ident<'a>>, 115 params: Self::Params, 116 salt: impl Into<Salt<'a>>, 117 ) -> Result<PasswordHash<'a>>; 118 } 119 120 /// Trait for password verification. 121 /// 122 /// Automatically impl'd for any type that impls [`PasswordHasher`]. 123 /// 124 /// This trait is object safe and can be used to implement abstractions over 125 /// multiple password hashing algorithms. One such abstraction is provided by 126 /// the [`PasswordHash::verify_password`] method. 127 pub trait PasswordVerifier { 128 /// Compute this password hashing function against the provided password 129 /// using the parameters from the provided password hash and see if the 130 /// computed output matches. verify_password(&self, password: &[u8], hash: &PasswordHash<'_>) -> Result<()>131 fn verify_password(&self, password: &[u8], hash: &PasswordHash<'_>) -> Result<()>; 132 } 133 134 impl<T: PasswordHasher> PasswordVerifier for T { verify_password(&self, password: &[u8], hash: &PasswordHash<'_>) -> Result<()>135 fn verify_password(&self, password: &[u8], hash: &PasswordHash<'_>) -> Result<()> { 136 if let (Some(salt), Some(expected_output)) = (&hash.salt, &hash.hash) { 137 let computed_hash = self.hash_password( 138 password, 139 Some(hash.algorithm), 140 T::Params::try_from(&hash)?, 141 *salt, 142 )?; 143 144 if let Some(computed_output) = &computed_hash.hash { 145 // See notes on `Output` about the use of a constant-time comparison 146 if expected_output == computed_output { 147 return Ok(()); 148 } 149 } 150 } 151 152 Err(Error::Password) 153 } 154 } 155 156 /// Trait for password hashing algorithms which support the legacy 157 /// [Modular Crypt Format (MCF)][MCF]. 158 /// 159 /// [MCF]: https://passlib.readthedocs.io/en/stable/modular_crypt_format.html 160 pub trait McfHasher { 161 /// Upgrade an MCF hash to a PHC hash. MCF follow this rough format: 162 /// 163 /// ```text 164 /// $<id>$<content> 165 /// ``` 166 /// 167 /// MCF hashes are otherwise largely unstructured and parsed according to 168 /// algorithm-specific rules so hashers must parse a raw string themselves. upgrade_mcf_hash<'a>(&self, hash: &'a str) -> Result<PasswordHash<'a>>169 fn upgrade_mcf_hash<'a>(&self, hash: &'a str) -> Result<PasswordHash<'a>>; 170 171 /// Verify a password hash in MCF format against the provided password. verify_mcf_hash(&self, password: &[u8], mcf_hash: &str) -> Result<()> where Self: PasswordVerifier,172 fn verify_mcf_hash(&self, password: &[u8], mcf_hash: &str) -> Result<()> 173 where 174 Self: PasswordVerifier, 175 { 176 self.verify_password(password, &self.upgrade_mcf_hash(mcf_hash)?) 177 } 178 } 179 180 /// Password hash. 181 /// 182 /// This type corresponds to the parsed representation of a PHC string as 183 /// described in the [PHC string format specification][1]. 184 /// 185 /// PHC strings have the following format: 186 /// 187 /// ```text 188 /// $<id>[$v=<version>][$<param>=<value>(,<param>=<value>)*][$<salt>[$<hash>]] 189 /// ``` 190 /// 191 /// where: 192 /// 193 /// - `<id>` is the symbolic name for the function 194 /// - `<version>` is the algorithm version 195 /// - `<param>` is a parameter name 196 /// - `<value>` is a parameter value 197 /// - `<salt>` is an encoding of the salt 198 /// - `<hash>` is an encoding of the hash output 199 /// 200 /// The string is then the concatenation, in that order, of: 201 /// 202 /// - a `$` sign; 203 /// - the function symbolic name; 204 /// - optionally, a `$` sign followed by the algorithm version with a `v=version` format; 205 /// - optionally, a `$` sign followed by one or several parameters, each with a `name=value` format; 206 /// the parameters are separated by commas; 207 /// - optionally, a `$` sign followed by the (encoded) salt value; 208 /// - optionally, a `$` sign followed by the (encoded) hash output (the hash output may be present 209 /// only if the salt is present). 210 /// 211 /// [1]: https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md#specification 212 #[derive(Clone, Debug, Eq, PartialEq)] 213 pub struct PasswordHash<'a> { 214 /// Password hashing algorithm identifier. 215 /// 216 /// This corresponds to the `<id>` field in a PHC string, a.k.a. the 217 /// symbolic name for the function. 218 pub algorithm: Ident<'a>, 219 220 /// Optional version field. 221 /// 222 /// This corresponds to the `<version>` field in a PHC string. 223 pub version: Option<Decimal>, 224 225 /// Algorithm-specific parameters. 226 /// 227 /// This corresponds to the set of `$<param>=<value>(,<param>=<value>)*` 228 /// name/value pairs in a PHC string. 229 pub params: ParamsString, 230 231 /// [`Salt`] string for personalizing a password hash output. 232 /// 233 /// This corresponds to the `<salt>` value in a PHC string. 234 pub salt: Option<Salt<'a>>, 235 236 /// Password hashing function [`Output`], a.k.a. hash/digest. 237 /// 238 /// This corresponds to the `<hash>` output in a PHC string. 239 pub hash: Option<Output>, 240 } 241 242 impl<'a> PasswordHash<'a> { 243 /// Parse a password hash from a string in the PHC string format. new(s: &'a str) -> Result<Self>244 pub fn new(s: &'a str) -> Result<Self> { 245 Self::parse(s, Encoding::B64) 246 } 247 248 /// Parse a password hash from the given [`Encoding`]. parse(s: &'a str, encoding: Encoding) -> Result<Self>249 pub fn parse(s: &'a str, encoding: Encoding) -> Result<Self> { 250 if s.is_empty() { 251 return Err(Error::PhcStringTooShort); 252 } 253 254 let mut fields = s.split(PASSWORD_HASH_SEPARATOR); 255 let beginning = fields.next().expect("no first field"); 256 257 if beginning.chars().next().is_some() { 258 return Err(Error::PhcStringInvalid); 259 } 260 261 let algorithm = fields 262 .next() 263 .ok_or(Error::PhcStringTooShort) 264 .and_then(Ident::try_from)?; 265 266 let mut version = None; 267 let mut params = ParamsString::new(); 268 let mut salt = None; 269 let mut hash = None; 270 271 let mut next_field = fields.next(); 272 273 if let Some(field) = next_field { 274 // v=<version> 275 if field.starts_with("v=") && !field.contains(params::PARAMS_DELIMITER) { 276 version = Some(Value::new(&field[2..]).and_then(|value| value.decimal())?); 277 next_field = None; 278 } 279 } 280 281 if next_field.is_none() { 282 next_field = fields.next(); 283 } 284 285 if let Some(field) = next_field { 286 // <param>=<value> 287 if field.contains(params::PAIR_DELIMITER) { 288 params = field.parse()?; 289 next_field = None; 290 } 291 } 292 293 if next_field.is_none() { 294 next_field = fields.next(); 295 } 296 297 if let Some(s) = next_field { 298 salt = Some(s.try_into()?); 299 } 300 301 if let Some(field) = fields.next() { 302 hash = Some(Output::decode(field, encoding)?); 303 } 304 305 if fields.next().is_some() { 306 return Err(Error::PhcStringTooLong); 307 } 308 309 Ok(Self { 310 algorithm, 311 version, 312 params, 313 salt, 314 hash, 315 }) 316 } 317 318 /// Generate a password hash using the supplied algorithm. generate( phf: impl PasswordHasher, password: impl AsRef<[u8]>, salt: &'a str, ) -> Result<Self>319 pub fn generate( 320 phf: impl PasswordHasher, 321 password: impl AsRef<[u8]>, 322 salt: &'a str, 323 ) -> Result<Self> { 324 phf.hash_password_simple(password.as_ref(), salt) 325 } 326 327 /// Verify this password hash using the specified set of supported 328 /// [`PasswordHasher`] trait objects. verify_password( &self, phfs: &[&dyn PasswordVerifier], password: impl AsRef<[u8]>, ) -> Result<()>329 pub fn verify_password( 330 &self, 331 phfs: &[&dyn PasswordVerifier], 332 password: impl AsRef<[u8]>, 333 ) -> Result<()> { 334 for &phf in phfs { 335 if phf.verify_password(password.as_ref(), self).is_ok() { 336 return Ok(()); 337 } 338 } 339 340 Err(Error::Password) 341 } 342 343 /// Get the [`Encoding`] that this [`PasswordHash`] is serialized with. encoding(&self) -> Encoding344 pub fn encoding(&self) -> Encoding { 345 self.hash.map(|h| h.encoding()).unwrap_or_default() 346 } 347 } 348 349 // Note: this uses `TryFrom` instead of `FromStr` to support a lifetime on 350 // the `str` the value is being parsed from. 351 impl<'a> TryFrom<&'a str> for PasswordHash<'a> { 352 type Error = Error; 353 try_from(s: &'a str) -> Result<Self>354 fn try_from(s: &'a str) -> Result<Self> { 355 Self::new(s) 356 } 357 } 358 359 impl<'a> fmt::Display for PasswordHash<'a> { fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result360 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 361 write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, self.algorithm)?; 362 363 if let Some(version) = self.version { 364 write!(f, "{}v={}", PASSWORD_HASH_SEPARATOR, version)?; 365 } 366 367 if !self.params.is_empty() { 368 write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, self.params)?; 369 } 370 371 if let Some(salt) = &self.salt { 372 write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, salt)?; 373 } 374 375 if let Some(hash) = &self.hash { 376 write!(f, "{}{}", PASSWORD_HASH_SEPARATOR, hash)?; 377 } 378 379 Ok(()) 380 } 381 } 382