11//! Implementation of the `password-hash` traits for Modular Crypt Format (MCF) password hash
2- //! strings which begin with `$7$ `:
2+ //! strings which begin with `$pbkdf$`, `$pbkdf-sha256$`, or `$pbkdf-sha512 `:
33//!
4- //! <https://man.archlinux.org/man/crypt.5#scrypt>
4+ //! <https://passlib.readthedocs.io/en/stable/lib/passlib.hash.pbkdf2_digest.html>
5+ //!
6+ //! PBKDF2's MCF strings can be distinguished from PHC strings by whether the parameters
7+ //! field contains `rounds=` or not: if the number of rounds does NOT contain `rounds=`, but just a
8+ //! bare number of rounds, then it's MCF format. If it DOES contain `rounds=`, then it's PHC.
59
610pub use mcf:: { PasswordHash , PasswordHashRef } ;
711
812use crate :: { Algorithm , Params , Pbkdf2 , pbkdf2_hmac} ;
9- use alloc:: string:: String ;
13+ use alloc:: { string:: String , vec :: Vec } ;
1014use mcf:: Base64 ;
11- use password_hash:: { CustomizedPasswordHasher , Error , PasswordHasher , Result , Version } ;
15+ use password_hash:: {
16+ CustomizedPasswordHasher , Error , PasswordHasher , PasswordVerifier , Result , Version ,
17+ } ;
1218use sha2:: { Sha256 , Sha512 } ;
1319
1420#[ cfg( feature = "sha1" ) ]
1521use sha1:: Sha1 ;
1622
17- #[ cfg( test) ]
18- use alloc:: vec:: Vec ;
19-
2023impl CustomizedPasswordHasher < PasswordHash > for Pbkdf2 {
2124 type Params = Params ;
2225
@@ -75,10 +78,67 @@ impl PasswordHasher<PasswordHash> for Pbkdf2 {
7578 }
7679}
7780
81+ impl PasswordVerifier < PasswordHash > for Pbkdf2 {
82+ fn verify_password ( & self , password : & [ u8 ] , hash : & PasswordHash ) -> Result < ( ) > {
83+ self . verify_password ( password, hash. as_password_hash_ref ( ) )
84+ }
85+ }
86+
87+ impl PasswordVerifier < PasswordHashRef > for Pbkdf2 {
88+ fn verify_password ( & self , password : & [ u8 ] , hash : & PasswordHashRef ) -> Result < ( ) > {
89+ let algorithm = hash. id ( ) . parse :: < Algorithm > ( ) ?;
90+ let mut fields = hash. fields ( ) ;
91+ let mut next = fields. next ( ) . ok_or ( Error :: EncodingInvalid ) ?;
92+ let mut params = Params :: default ( ) ;
93+
94+ // decode params
95+ if let Ok ( p) = next. as_str ( ) . parse :: < Params > ( ) {
96+ params = p;
97+ next = fields. next ( ) . ok_or ( Error :: EncodingInvalid ) ?;
98+ }
99+
100+ let salt = base64_decode ( next. as_str ( ) ) ?;
101+
102+ // decode expected password hash
103+ let expected = fields
104+ . next ( )
105+ . ok_or ( Error :: EncodingInvalid )
106+ . and_then ( |field| base64_decode ( field. as_str ( ) ) ) ?;
107+
108+ // should be the last field
109+ if fields. next ( ) . is_some ( ) {
110+ return Err ( Error :: EncodingInvalid ) ;
111+ }
112+
113+ let mut buffer = [ 0u8 ; Params :: MAX_LENGTH ] ;
114+ let out = buffer. get_mut ( ..expected. len ( ) ) . ok_or ( Error :: OutputSize ) ?;
115+
116+ let f = match algorithm {
117+ #[ cfg( feature = "sha1" ) ]
118+ Algorithm :: Pbkdf2Sha1 => pbkdf2_hmac :: < Sha1 > ,
119+ Algorithm :: Pbkdf2Sha256 => pbkdf2_hmac :: < Sha256 > ,
120+ Algorithm :: Pbkdf2Sha512 => pbkdf2_hmac :: < Sha512 > ,
121+ } ;
122+
123+ f ( password, & salt, params. rounds ( ) , out) ;
124+
125+ // TODO(tarcieri): use `subtle` or `ctutils` for comparison
126+ if out
127+ . iter ( )
128+ . zip ( expected. iter ( ) )
129+ . fold ( 0 , |acc, ( a, b) | acc | ( a ^ b) )
130+ == 0
131+ {
132+ Ok ( ( ) )
133+ } else {
134+ Err ( Error :: PasswordInvalid )
135+ }
136+ }
137+ }
138+
78139// Base64 support: PBKDF2 uses a variant of standard unpadded Base64 which substitutes the `+`
79140// character for `.` and this is a distinct encoding from the bcrypt and crypt Base64 variants.
80141
81- #[ cfg( test) ]
82142fn base64_decode ( base64 : & str ) -> Result < Vec < u8 > > {
83143 Base64 :: B64
84144 . decode_vec ( & base64. replace ( '.' , "+" ) )
@@ -92,10 +152,10 @@ fn base64_encode(bytes: &[u8]) -> String {
92152// TODO(tarcieri): tests for SHA-1 and SHA-512
93153#[ cfg( test) ]
94154mod tests {
95- use super :: base64_decode;
155+ use super :: { Error , base64_decode} ;
96156 use crate :: { Params , Pbkdf2 } ;
97157 use mcf:: PasswordHash ;
98- use password_hash:: CustomizedPasswordHasher ;
158+ use password_hash:: { CustomizedPasswordHasher , PasswordVerifier } ;
99159
100160 // Example adapted from:
101161 // <https://passlib.readthedocs.io/en/stable/lib/passlib.hash.pbkdf2_digest.html>
@@ -110,12 +170,22 @@ mod tests {
110170 let salt = base64_decode ( EXAMPLE_SALT ) . unwrap ( ) ;
111171 let params = Params :: new ( EXAMPLE_ROUNDS ) ;
112172
113- let actual_hash: PasswordHash = Pbkdf2 :: default ( )
173+ let actual_hash: PasswordHash = Pbkdf2 :: SHA256
114174 . hash_password_with_params ( EXAMPLE_PASSWORD , salt. as_slice ( ) , params)
115175 . unwrap ( ) ;
116176
117177 let expected_hash = PasswordHash :: new ( EXAMPLE_HASH ) . unwrap ( ) ;
118178 assert_eq ! ( expected_hash, actual_hash) ;
179+
180+ assert_eq ! (
181+ Pbkdf2 :: SHA256 . verify_password( EXAMPLE_PASSWORD , & actual_hash) ,
182+ Ok ( ( ) )
183+ ) ;
184+
185+ assert_eq ! (
186+ Pbkdf2 :: SHA256 . verify_password( b"bogus" , & actual_hash) ,
187+ Err ( Error :: PasswordInvalid )
188+ ) ;
119189 }
120190
121191 // Example adapted from:
@@ -136,5 +206,15 @@ mod tests {
136206
137207 let expected_hash = PasswordHash :: new ( EXAMPLE_HASH ) . unwrap ( ) ;
138208 assert_eq ! ( expected_hash, actual_hash) ;
209+
210+ assert_eq ! (
211+ Pbkdf2 :: SHA512 . verify_password( EXAMPLE_PASSWORD , & actual_hash) ,
212+ Ok ( ( ) )
213+ ) ;
214+
215+ assert_eq ! (
216+ Pbkdf2 :: SHA512 . verify_password( b"bogus" , & actual_hash) ,
217+ Err ( Error :: PasswordInvalid )
218+ ) ;
139219 }
140220}
0 commit comments