@@ -412,21 +412,19 @@ pub const Params = struct {
412412 /// log2 of the number of rounds
413413 rounds_log : u6 ,
414414
415+ /// As originally defined, bcrypt silently truncates passwords to 72 bytes.
416+ /// In order to overcome this limitation, if `silently_truncate_password` is set to `false`,
417+ /// long passwords will be automatically pre-hashed using HMAC-SHA512 before being passed to bcrypt.
418+ /// Only set `silently_truncate_password` to `true` for compatibility with traditional bcrypt implementations,
419+ /// or if you want to handle the truncation yourself.
420+ silently_truncate_password : bool ,
421+
415422 /// Minimum recommended parameters according to the
416423 /// [OWASP cheat sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html).
417- pub const owasp = Self { .rounds_log = 10 };
424+ pub const owasp = Self { .rounds_log = 10 , . silently_truncate_password = false };
418425};
419426
420- /// Compute a hash of a password using 2^rounds_log rounds of the bcrypt key stretching function.
421- /// bcrypt is a computationally expensive and cache-hard function, explicitly designed to slow down exhaustive searches.
422- ///
423- /// The function returns the hash as a `dk_length` byte array, that doesn't include anything besides the hash output.
424- ///
425- /// For a generic key-derivation function, use `bcrypt.pbkdf()` instead.
426- ///
427- /// IMPORTANT: by design, bcrypt silently truncates passwords to 72 bytes.
428- /// If this is an issue for your application, use `bcryptWithoutTruncation` instead.
429- pub fn bcrypt (
427+ fn bcryptWithTruncation (
430428 password : []const u8 ,
431429 salt : [salt_length ]u8 ,
432430 params : Params ,
@@ -465,17 +463,15 @@ pub fn bcrypt(
465463///
466464/// The function returns the hash as a `dk_length` byte array, that doesn't include anything besides the hash output.
467465///
468- /// For a generic key-derivation function, use `bcrypt.pbkdf()` instead.
469- ///
470- /// This function is identical to `bcrypt`, except that it doesn't silently truncate passwords.
471- /// Instead, passwords longer than 72 bytes are pre-hashed using HMAC-SHA512 before being passed to bcrypt.
472- pub fn bcryptWithoutTruncation (
466+ /// This function was designed for password storage, not for key derivation.
467+ /// For key derivation, use `bcrypt.pbkdf()` or `bcrypt.opensshKdf()` instead.
468+ pub fn bcrypt (
473469 password : []const u8 ,
474470 salt : [salt_length ]u8 ,
475471 params : Params ,
476472) [dk_length ]u8 {
477- if (password .len <= 72 ) {
478- return bcrypt (password , salt , params );
473+ if (password .len <= 72 or params . silently_truncate_password ) {
474+ return bcryptWithTruncation (password , salt , params );
479475 }
480476
481477 var pre_hash : [HmacSha512 .mac_length ]u8 = undefined ;
@@ -485,7 +481,7 @@ pub fn bcryptWithoutTruncation(
485481 var pre_hash_b64 : [Encoder .calcSize (pre_hash .len )]u8 = undefined ;
486482 _ = Encoder .encode (& pre_hash_b64 , & pre_hash );
487483
488- return bcrypt (& pre_hash_b64 , salt , params );
484+ return bcryptWithTruncation (& pre_hash_b64 , salt , params );
489485}
490486
491487const pbkdf_prf = struct {
@@ -629,9 +625,8 @@ const crypt_format = struct {
629625 password : []const u8 ,
630626 salt : [salt_length ]u8 ,
631627 params : Params ,
632- silently_truncate_password : bool ,
633628 ) [hash_length ]u8 {
634- var dk = if ( silently_truncate_password ) bcrypt ( password , salt , params ) else bcryptWithoutTruncation (password , salt , params );
629+ var dk = bcrypt (password , salt , params );
635630
636631 var salt_str : [salt_str_length ]u8 = undefined ;
637632 _ = Codec .Encoder .encode (salt_str [0.. ], salt [0.. ]);
@@ -666,13 +661,12 @@ const PhcFormatHasher = struct {
666661 fn create (
667662 password : []const u8 ,
668663 params : Params ,
669- silently_truncate_password : bool ,
670664 buf : []u8 ,
671665 ) HasherError ! []const u8 {
672666 var salt : [salt_length ]u8 = undefined ;
673667 crypto .random .bytes (& salt );
674668
675- const hash = if ( silently_truncate_password ) bcrypt ( password , salt , params ) else bcryptWithoutTruncation (password , salt , params );
669+ const hash = bcrypt (password , salt , params );
676670
677671 return phc_format .serialize (HashResult {
678672 .alg_id = alg_id ,
@@ -694,8 +688,11 @@ const PhcFormatHasher = struct {
694688 if (hash_result .salt .len != salt_length or hash_result .hash .len != dk_length )
695689 return HasherError .InvalidEncoding ;
696690
697- const params = Params { .rounds_log = hash_result .r };
698- const hash = if (silently_truncate_password ) bcrypt (password , hash_result .salt .buf , params ) else bcryptWithoutTruncation (password , hash_result .salt .buf , params );
691+ const params = Params {
692+ .rounds_log = hash_result .r ,
693+ .silently_truncate_password = silently_truncate_password ,
694+ };
695+ const hash = bcrypt (password , hash_result .salt .buf , params );
699696 const expected_hash = hash_result .hash .constSlice ();
700697
701698 if (! mem .eql (u8 , & hash , expected_hash )) return HasherError .PasswordVerificationFailed ;
@@ -711,15 +708,14 @@ const CryptFormatHasher = struct {
711708 fn create (
712709 password : []const u8 ,
713710 params : Params ,
714- silently_truncate_password : bool ,
715711 buf : []u8 ,
716712 ) HasherError ! []const u8 {
717713 if (buf .len < pwhash_str_length ) return HasherError .NoSpaceLeft ;
718714
719715 var salt : [salt_length ]u8 = undefined ;
720716 crypto .random .bytes (& salt );
721717
722- const hash = crypt_format .strHashInternal (password , salt , params , silently_truncate_password );
718+ const hash = crypt_format .strHashInternal (password , salt , params );
723719 @memcpy (buf [0.. hash .len ], & hash );
724720
725721 return buf [0.. pwhash_str_length ];
@@ -742,7 +738,10 @@ const CryptFormatHasher = struct {
742738 var salt : [salt_length ]u8 = undefined ;
743739 crypt_format .Codec .Decoder .decode (salt [0.. ], salt_str [0.. ]) catch return HasherError .InvalidEncoding ;
744740
745- const wanted_s = crypt_format .strHashInternal (password , salt , .{ .rounds_log = rounds_log }, silently_truncate_password );
741+ const wanted_s = crypt_format .strHashInternal (password , salt , .{
742+ .rounds_log = rounds_log ,
743+ .silently_truncate_password = silently_truncate_password ,
744+ });
746745 if (! mem .eql (u8 , wanted_s [0.. ], str [0.. ])) return HasherError .PasswordVerificationFailed ;
747746 }
748747};
@@ -755,9 +754,6 @@ pub const HashOptions = struct {
755754 params : Params ,
756755 /// Encoding to use for the output of the hash function.
757756 encoding : pwhash.Encoding ,
758- /// Whether to silently truncate the password to 72 bytes, or pre-hash the password when it is longer.
759- /// The default is `true`, for compatibility with the original bcrypt implementation.
760- silently_truncate_password : bool = true ,
761757};
762758
763759/// Compute a hash of a password using 2^rounds_log rounds of the bcrypt key stretching function.
@@ -773,8 +769,8 @@ pub fn strHash(
773769 out : []u8 ,
774770) Error ! []const u8 {
775771 switch (options .encoding ) {
776- .phc = > return PhcFormatHasher .create (password , options .params , options . silently_truncate_password , out ),
777- .crypt = > return CryptFormatHasher .create (password , options .params , options . silently_truncate_password , out ),
772+ .phc = > return PhcFormatHasher .create (password , options .params , out ),
773+ .crypt = > return CryptFormatHasher .create (password , options .params , out ),
778774 }
779775}
780776
@@ -783,7 +779,7 @@ pub const VerifyOptions = struct {
783779 /// For `bcrypt`, that can be left to `null`.
784780 allocator : ? mem.Allocator = null ,
785781 /// Whether to silently truncate the password to 72 bytes, or pre-hash the password when it is longer.
786- silently_truncate_password : bool = false ,
782+ silently_truncate_password : bool ,
787783};
788784
789785/// Verify that a previously computed hash is valid for a given password.
@@ -811,11 +807,10 @@ test "bcrypt codec" {
811807
812808test "bcrypt crypt format" {
813809 var hash_options = HashOptions {
814- .params = .{ .rounds_log = 5 },
810+ .params = .{ .rounds_log = 5 , . silently_truncate_password = false },
815811 .encoding = .crypt ,
816- .silently_truncate_password = false ,
817812 };
818- var verify_options = VerifyOptions {};
813+ var verify_options = VerifyOptions { . silently_truncate_password = false };
819814
820815 var buf : [hash_length ]u8 = undefined ;
821816 const s = try strHash ("password" , hash_options , & buf );
@@ -837,7 +832,7 @@ test "bcrypt crypt format" {
837832 strVerify (long_s , "password" ** 101 , verify_options ),
838833 );
839834
840- hash_options .silently_truncate_password = true ;
835+ hash_options .params . silently_truncate_password = true ;
841836 verify_options .silently_truncate_password = true ;
842837 long_s = try strHash ("password" ** 100 , hash_options , & long_buf );
843838 try strVerify (long_s , "password" ** 101 , verify_options );
@@ -851,11 +846,10 @@ test "bcrypt crypt format" {
851846
852847test "bcrypt phc format" {
853848 var hash_options = HashOptions {
854- .params = .{ .rounds_log = 5 },
849+ .params = .{ .rounds_log = 5 , . silently_truncate_password = false },
855850 .encoding = .phc ,
856- .silently_truncate_password = false ,
857851 };
858- var verify_options = VerifyOptions {};
852+ var verify_options = VerifyOptions { . silently_truncate_password = false };
859853 const prefix = "$bcrypt$" ;
860854
861855 var buf : [hash_length * 2 ]u8 = undefined ;
@@ -878,7 +872,7 @@ test "bcrypt phc format" {
878872 strVerify (long_s , "password" ** 101 , verify_options ),
879873 );
880874
881- hash_options .silently_truncate_password = true ;
875+ hash_options .params . silently_truncate_password = true ;
882876 verify_options .silently_truncate_password = true ;
883877 long_s = try strHash ("password" ** 100 , hash_options , & long_buf );
884878 try strVerify (long_s , "password" ** 101 , verify_options );
0 commit comments