@@ -265,6 +265,23 @@ pub fn non_truncating_hash<P: AsRef<[u8]>>(password: P, cost: u32) -> BcryptResu
265265 } )
266266}
267267
268+ /// Generates a password hash using the cost given, returning a fixed-size stack buffer.
269+ /// The salt is generated randomly using the OS randomness.
270+ /// The returned buffer is always exactly 60 bytes of valid UTF-8 (version 2b format).
271+ #[ cfg( any( feature = "alloc" , feature = "std" ) ) ]
272+ pub fn hash_bytes < P : AsRef < [ u8 ] > > ( password : P , cost : u32 ) -> BcryptResult < [ u8 ; 60 ] > {
273+ hash_with_result ( password, cost) . map ( |r| r. format ( ) )
274+ }
275+
276+ /// Generates a password hash using the cost given, returning a fixed-size stack buffer.
277+ /// The salt is generated randomly using the OS randomness.
278+ /// The returned buffer is always exactly 60 bytes of valid UTF-8 (version 2b format).
279+ /// Will return BcryptError::Truncation if password is longer than 72 bytes
280+ #[ cfg( any( feature = "alloc" , feature = "std" ) ) ]
281+ pub fn non_truncating_hash_bytes < P : AsRef < [ u8 ] > > ( password : P , cost : u32 ) -> BcryptResult < [ u8 ; 60 ] > {
282+ non_truncating_hash_with_result ( password, cost) . map ( |r| r. format ( ) )
283+ }
284+
268285/// Generates a password hash using the cost given.
269286/// The salt is generated randomly using the OS randomness.
270287/// The function returns a result structure and allows to format the hash in different versions.
@@ -306,6 +323,17 @@ pub fn hash_with_salt<P: AsRef<[u8]>>(
306323 _hash_password ( password. as_ref ( ) , cost, salt, false )
307324}
308325
326+ /// Generates a password given a hash and a cost, returning a fixed-size stack buffer.
327+ /// The returned buffer is always exactly 60 bytes of valid UTF-8 (version 2b format).
328+ #[ cfg( any( feature = "alloc" , feature = "std" ) ) ]
329+ pub fn hash_with_salt_bytes < P : AsRef < [ u8 ] > > (
330+ password : P ,
331+ cost : u32 ,
332+ salt : [ u8 ; 16 ] ,
333+ ) -> BcryptResult < [ u8 ; 60 ] > {
334+ _hash_password ( password. as_ref ( ) , cost, salt, false ) . map ( |r| r. format ( ) )
335+ }
336+
309337/// Generates a password given a hash and a cost.
310338/// The function returns a result structure and allows to format the hash in different versions.
311339/// Will return BcryptError::Truncation if password is longer than 72 bytes
@@ -318,6 +346,18 @@ pub fn non_truncating_hash_with_salt<P: AsRef<[u8]>>(
318346 _hash_password ( password. as_ref ( ) , cost, salt, true )
319347}
320348
349+ /// Generates a password given a hash and a cost, returning a fixed-size stack buffer.
350+ /// The returned buffer is always exactly 60 bytes of valid UTF-8 (version 2b format).
351+ /// Will return BcryptError::Truncation if password is longer than 72 bytes
352+ #[ cfg( any( feature = "alloc" , feature = "std" ) ) ]
353+ pub fn non_truncating_hash_with_salt_bytes < P : AsRef < [ u8 ] > > (
354+ password : P ,
355+ cost : u32 ,
356+ salt : [ u8 ; 16 ] ,
357+ ) -> BcryptResult < [ u8 ; 60 ] > {
358+ _hash_password ( password. as_ref ( ) , cost, salt, true ) . map ( |r| r. format ( ) )
359+ }
360+
321361/// Verify the password against the hash by extracting the salt from the hash and recomputing the
322362/// hash from the password. If `err_on_truncation` is set to true, then this method will return
323363/// `BcryptError::Truncation`.
@@ -356,7 +396,8 @@ mod tests {
356396 vec,
357397 vec:: Vec ,
358398 } ,
359- hash, hash_with_salt, non_truncating_verify, split_hash, verify,
399+ hash, hash_bytes, hash_with_salt, hash_with_salt_bytes, non_truncating_hash_bytes,
400+ non_truncating_hash_with_salt_bytes, non_truncating_verify, split_hash, verify,
360401 } ;
361402 use base64:: Engine as _;
362403 use core:: convert:: TryInto ;
@@ -630,6 +671,66 @@ mod tests {
630671 ) ;
631672 }
632673
674+ #[ test]
675+ fn hash_bytes_returns_valid_utf8_bcrypt_string ( ) {
676+ let result = hash_bytes ( "hunter2" , 4 ) . unwrap ( ) ;
677+ let s = core:: str:: from_utf8 ( & result) . unwrap ( ) ;
678+ assert ! ( s. starts_with( "$2b$04$" ) ) ;
679+ assert_eq ! ( s. len( ) , 60 ) ;
680+ assert ! ( verify( "hunter2" , s) . unwrap( ) ) ;
681+ }
682+
683+ #[ test]
684+ fn non_truncating_hash_bytes_returns_valid_utf8_bcrypt_string ( ) {
685+ let result = non_truncating_hash_bytes ( "hunter2" , 4 ) . unwrap ( ) ;
686+ let s = core:: str:: from_utf8 ( & result) . unwrap ( ) ;
687+ assert ! ( s. starts_with( "$2b$04$" ) ) ;
688+ assert_eq ! ( s. len( ) , 60 ) ;
689+ assert ! ( verify( "hunter2" , s) . unwrap( ) ) ;
690+ }
691+
692+ #[ test]
693+ fn non_truncating_hash_bytes_errors_on_long_password ( ) {
694+ use core:: iter;
695+ let result = non_truncating_hash_bytes ( iter:: repeat ( "x" ) . take ( 72 ) . collect :: < String > ( ) , 4 ) ;
696+ assert ! ( matches!( result, Err ( BcryptError :: Truncation ( 73 ) ) ) ) ;
697+ }
698+
699+ #[ test]
700+ fn hash_with_salt_bytes_matches_hash_with_salt ( ) {
701+ let salt = [
702+ 38 , 113 , 212 , 141 , 108 , 213 , 195 , 166 , 201 , 38 , 20 , 13 , 47 , 40 , 104 , 18 ,
703+ ] ;
704+ let expected = hash_with_salt ( "My S3cre7 P@55w0rd!" , 5 , salt)
705+ . unwrap ( )
706+ . to_string ( ) ;
707+ let result = hash_with_salt_bytes ( "My S3cre7 P@55w0rd!" , 5 , salt) . unwrap ( ) ;
708+ let s = core:: str:: from_utf8 ( & result) . unwrap ( ) ;
709+ assert_eq ! ( expected, s) ;
710+ }
711+
712+ #[ test]
713+ fn non_truncating_hash_with_salt_bytes_errors_on_long_password ( ) {
714+ use core:: iter;
715+ let salt = [ 0u8 ; 16 ] ;
716+ let result = non_truncating_hash_with_salt_bytes (
717+ iter:: repeat ( "x" ) . take ( 72 ) . collect :: < String > ( ) ,
718+ 4 ,
719+ salt,
720+ ) ;
721+ assert ! ( matches!( result, Err ( BcryptError :: Truncation ( 73 ) ) ) ) ;
722+ }
723+
724+ #[ test]
725+ fn hash_bytes_matches_hash_string ( ) {
726+ let salt = [ 0u8 ; 16 ] ;
727+ let result_parts = _hash_password ( "hunter2" . as_bytes ( ) , 4 , salt, false ) . unwrap ( ) ;
728+ let from_parts = result_parts. format_for_version ( Version :: TwoB ) ;
729+ let bytes_result = hash_with_salt_bytes ( "hunter2" , 4 , salt) . unwrap ( ) ;
730+ let from_bytes = core:: str:: from_utf8 ( & bytes_result) . unwrap ( ) ;
731+ assert_eq ! ( from_parts, from_bytes) ;
732+ }
733+
633734 quickcheck ! {
634735 fn can_verify_arbitrary_own_generated( pass: Vec <u8 >) -> BcryptResult <bool > {
635736 let mut pass = pass;
0 commit comments