@@ -252,6 +252,23 @@ pub fn non_truncating_hash<P: AsRef<[u8]>>(password: P, cost: u32) -> BcryptResu
252252 } )
253253}
254254
255+ /// Generates a password hash using the cost given, returning a fixed-size stack buffer.
256+ /// The salt is generated randomly using the OS randomness.
257+ /// The returned buffer is always exactly 60 bytes of valid UTF-8 (version 2b format).
258+ #[ cfg( any( feature = "alloc" , feature = "std" ) ) ]
259+ pub fn hash_bytes < P : AsRef < [ u8 ] > > ( password : P , cost : u32 ) -> BcryptResult < [ u8 ; 60 ] > {
260+ hash_with_result ( password, cost) . map ( |r| r. format ( ) )
261+ }
262+
263+ /// Generates a password hash using the cost given, returning a fixed-size stack buffer.
264+ /// The salt is generated randomly using the OS randomness.
265+ /// The returned buffer is always exactly 60 bytes of valid UTF-8 (version 2b format).
266+ /// Will return BcryptError::Truncation if password is longer than 72 bytes
267+ #[ cfg( any( feature = "alloc" , feature = "std" ) ) ]
268+ pub fn non_truncating_hash_bytes < P : AsRef < [ u8 ] > > ( password : P , cost : u32 ) -> BcryptResult < [ u8 ; 60 ] > {
269+ non_truncating_hash_with_result ( password, cost) . map ( |r| r. format ( ) )
270+ }
271+
255272/// Generates a password hash using the cost given.
256273/// The salt is generated randomly using the OS randomness.
257274/// The function returns a result structure and allows to format the hash in different versions.
@@ -293,6 +310,17 @@ pub fn hash_with_salt<P: AsRef<[u8]>>(
293310 _hash_password ( password. as_ref ( ) , cost, salt, false )
294311}
295312
313+ /// Generates a password given a hash and a cost, returning a fixed-size stack buffer.
314+ /// The returned buffer is always exactly 60 bytes of valid UTF-8 (version 2b format).
315+ #[ cfg( any( feature = "alloc" , feature = "std" ) ) ]
316+ pub fn hash_with_salt_bytes < P : AsRef < [ u8 ] > > (
317+ password : P ,
318+ cost : u32 ,
319+ salt : [ u8 ; 16 ] ,
320+ ) -> BcryptResult < [ u8 ; 60 ] > {
321+ _hash_password ( password. as_ref ( ) , cost, salt, false ) . map ( |r| r. format ( ) )
322+ }
323+
296324/// Generates a password given a hash and a cost.
297325/// The function returns a result structure and allows to format the hash in different versions.
298326/// Will return BcryptError::Truncation if password is longer than 72 bytes
@@ -305,6 +333,18 @@ pub fn non_truncating_hash_with_salt<P: AsRef<[u8]>>(
305333 _hash_password ( password. as_ref ( ) , cost, salt, true )
306334}
307335
336+ /// Generates a password given a hash and a cost, returning a fixed-size stack buffer.
337+ /// The returned buffer is always exactly 60 bytes of valid UTF-8 (version 2b format).
338+ /// Will return BcryptError::Truncation if password is longer than 72 bytes
339+ #[ cfg( any( feature = "alloc" , feature = "std" ) ) ]
340+ pub fn non_truncating_hash_with_salt_bytes < P : AsRef < [ u8 ] > > (
341+ password : P ,
342+ cost : u32 ,
343+ salt : [ u8 ; 16 ] ,
344+ ) -> BcryptResult < [ u8 ; 60 ] > {
345+ _hash_password ( password. as_ref ( ) , cost, salt, true ) . map ( |r| r. format ( ) )
346+ }
347+
308348/// Verify the password against the hash by extracting the salt from the hash and recomputing the
309349/// hash from the password. If `err_on_truncation` is set to true, then this method will return
310350/// `BcryptError::Truncation`.
@@ -343,7 +383,8 @@ mod tests {
343383 vec,
344384 vec:: Vec ,
345385 } ,
346- hash, hash_with_salt, non_truncating_verify, split_hash, verify,
386+ hash, hash_bytes, hash_with_salt, hash_with_salt_bytes, non_truncating_hash_bytes,
387+ non_truncating_hash_with_salt_bytes, non_truncating_verify, split_hash, verify,
347388 } ;
348389 use base64:: Engine as _;
349390 use core:: convert:: TryInto ;
@@ -617,6 +658,66 @@ mod tests {
617658 ) ;
618659 }
619660
661+ #[ test]
662+ fn hash_bytes_returns_valid_utf8_bcrypt_string ( ) {
663+ let result = hash_bytes ( "hunter2" , 4 ) . unwrap ( ) ;
664+ let s = core:: str:: from_utf8 ( & result) . unwrap ( ) ;
665+ assert ! ( s. starts_with( "$2b$04$" ) ) ;
666+ assert_eq ! ( s. len( ) , 60 ) ;
667+ assert ! ( verify( "hunter2" , s) . unwrap( ) ) ;
668+ }
669+
670+ #[ test]
671+ fn non_truncating_hash_bytes_returns_valid_utf8_bcrypt_string ( ) {
672+ let result = non_truncating_hash_bytes ( "hunter2" , 4 ) . unwrap ( ) ;
673+ let s = core:: str:: from_utf8 ( & result) . unwrap ( ) ;
674+ assert ! ( s. starts_with( "$2b$04$" ) ) ;
675+ assert_eq ! ( s. len( ) , 60 ) ;
676+ assert ! ( verify( "hunter2" , s) . unwrap( ) ) ;
677+ }
678+
679+ #[ test]
680+ fn non_truncating_hash_bytes_errors_on_long_password ( ) {
681+ use core:: iter;
682+ let result = non_truncating_hash_bytes ( iter:: repeat ( "x" ) . take ( 72 ) . collect :: < String > ( ) , 4 ) ;
683+ assert ! ( matches!( result, Err ( BcryptError :: Truncation ( 73 ) ) ) ) ;
684+ }
685+
686+ #[ test]
687+ fn hash_with_salt_bytes_matches_hash_with_salt ( ) {
688+ let salt = [
689+ 38 , 113 , 212 , 141 , 108 , 213 , 195 , 166 , 201 , 38 , 20 , 13 , 47 , 40 , 104 , 18 ,
690+ ] ;
691+ let expected = hash_with_salt ( "My S3cre7 P@55w0rd!" , 5 , salt)
692+ . unwrap ( )
693+ . to_string ( ) ;
694+ let result = hash_with_salt_bytes ( "My S3cre7 P@55w0rd!" , 5 , salt) . unwrap ( ) ;
695+ let s = core:: str:: from_utf8 ( & result) . unwrap ( ) ;
696+ assert_eq ! ( expected, s) ;
697+ }
698+
699+ #[ test]
700+ fn non_truncating_hash_with_salt_bytes_errors_on_long_password ( ) {
701+ use core:: iter;
702+ let salt = [ 0u8 ; 16 ] ;
703+ let result = non_truncating_hash_with_salt_bytes (
704+ iter:: repeat ( "x" ) . take ( 72 ) . collect :: < String > ( ) ,
705+ 4 ,
706+ salt,
707+ ) ;
708+ assert ! ( matches!( result, Err ( BcryptError :: Truncation ( 73 ) ) ) ) ;
709+ }
710+
711+ #[ test]
712+ fn hash_bytes_matches_hash_string ( ) {
713+ let salt = [ 0u8 ; 16 ] ;
714+ let result_parts = _hash_password ( "hunter2" . as_bytes ( ) , 4 , salt, false ) . unwrap ( ) ;
715+ let from_parts = result_parts. format_for_version ( Version :: TwoB ) ;
716+ let bytes_result = hash_with_salt_bytes ( "hunter2" , 4 , salt) . unwrap ( ) ;
717+ let from_bytes = core:: str:: from_utf8 ( & bytes_result) . unwrap ( ) ;
718+ assert_eq ! ( from_parts, from_bytes) ;
719+ }
720+
620721 quickcheck ! {
621722 fn can_verify_arbitrary_own_generated( pass: Vec <u8 >) -> BcryptResult <bool > {
622723 let mut pass = pass;
0 commit comments