@@ -412,21 +412,19 @@ pub const Params = struct {
412
412
/// log2 of the number of rounds
413
413
rounds_log : u6 ,
414
414
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
+
415
422
/// Minimum recommended parameters according to the
416
423
/// [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 };
418
425
};
419
426
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 (
430
428
password : []const u8 ,
431
429
salt : [salt_length ]u8 ,
432
430
params : Params ,
@@ -465,17 +463,15 @@ pub fn bcrypt(
465
463
///
466
464
/// The function returns the hash as a `dk_length` byte array, that doesn't include anything besides the hash output.
467
465
///
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 (
473
469
password : []const u8 ,
474
470
salt : [salt_length ]u8 ,
475
471
params : Params ,
476
472
) [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 );
479
475
}
480
476
481
477
var pre_hash : [HmacSha512 .mac_length ]u8 = undefined ;
@@ -485,7 +481,7 @@ pub fn bcryptWithoutTruncation(
485
481
var pre_hash_b64 : [Encoder .calcSize (pre_hash .len )]u8 = undefined ;
486
482
_ = Encoder .encode (& pre_hash_b64 , & pre_hash );
487
483
488
- return bcrypt (& pre_hash_b64 , salt , params );
484
+ return bcryptWithTruncation (& pre_hash_b64 , salt , params );
489
485
}
490
486
491
487
const pbkdf_prf = struct {
@@ -629,9 +625,8 @@ const crypt_format = struct {
629
625
password : []const u8 ,
630
626
salt : [salt_length ]u8 ,
631
627
params : Params ,
632
- silently_truncate_password : bool ,
633
628
) [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 );
635
630
636
631
var salt_str : [salt_str_length ]u8 = undefined ;
637
632
_ = Codec .Encoder .encode (salt_str [0.. ], salt [0.. ]);
@@ -666,13 +661,12 @@ const PhcFormatHasher = struct {
666
661
fn create (
667
662
password : []const u8 ,
668
663
params : Params ,
669
- silently_truncate_password : bool ,
670
664
buf : []u8 ,
671
665
) HasherError ! []const u8 {
672
666
var salt : [salt_length ]u8 = undefined ;
673
667
crypto .random .bytes (& salt );
674
668
675
- const hash = if ( silently_truncate_password ) bcrypt ( password , salt , params ) else bcryptWithoutTruncation (password , salt , params );
669
+ const hash = bcrypt (password , salt , params );
676
670
677
671
return phc_format .serialize (HashResult {
678
672
.alg_id = alg_id ,
@@ -694,8 +688,11 @@ const PhcFormatHasher = struct {
694
688
if (hash_result .salt .len != salt_length or hash_result .hash .len != dk_length )
695
689
return HasherError .InvalidEncoding ;
696
690
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 );
699
696
const expected_hash = hash_result .hash .constSlice ();
700
697
701
698
if (! mem .eql (u8 , & hash , expected_hash )) return HasherError .PasswordVerificationFailed ;
@@ -711,15 +708,14 @@ const CryptFormatHasher = struct {
711
708
fn create (
712
709
password : []const u8 ,
713
710
params : Params ,
714
- silently_truncate_password : bool ,
715
711
buf : []u8 ,
716
712
) HasherError ! []const u8 {
717
713
if (buf .len < pwhash_str_length ) return HasherError .NoSpaceLeft ;
718
714
719
715
var salt : [salt_length ]u8 = undefined ;
720
716
crypto .random .bytes (& salt );
721
717
722
- const hash = crypt_format .strHashInternal (password , salt , params , silently_truncate_password );
718
+ const hash = crypt_format .strHashInternal (password , salt , params );
723
719
@memcpy (buf [0.. hash .len ], & hash );
724
720
725
721
return buf [0.. pwhash_str_length ];
@@ -742,7 +738,10 @@ const CryptFormatHasher = struct {
742
738
var salt : [salt_length ]u8 = undefined ;
743
739
crypt_format .Codec .Decoder .decode (salt [0.. ], salt_str [0.. ]) catch return HasherError .InvalidEncoding ;
744
740
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
+ });
746
745
if (! mem .eql (u8 , wanted_s [0.. ], str [0.. ])) return HasherError .PasswordVerificationFailed ;
747
746
}
748
747
};
@@ -755,9 +754,6 @@ pub const HashOptions = struct {
755
754
params : Params ,
756
755
/// Encoding to use for the output of the hash function.
757
756
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 ,
761
757
};
762
758
763
759
/// Compute a hash of a password using 2^rounds_log rounds of the bcrypt key stretching function.
@@ -773,8 +769,8 @@ pub fn strHash(
773
769
out : []u8 ,
774
770
) Error ! []const u8 {
775
771
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 ),
778
774
}
779
775
}
780
776
@@ -783,7 +779,7 @@ pub const VerifyOptions = struct {
783
779
/// For `bcrypt`, that can be left to `null`.
784
780
allocator : ? mem.Allocator = null ,
785
781
/// 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 ,
787
783
};
788
784
789
785
/// Verify that a previously computed hash is valid for a given password.
@@ -811,11 +807,10 @@ test "bcrypt codec" {
811
807
812
808
test "bcrypt crypt format" {
813
809
var hash_options = HashOptions {
814
- .params = .{ .rounds_log = 5 },
810
+ .params = .{ .rounds_log = 5 , . silently_truncate_password = false },
815
811
.encoding = .crypt ,
816
- .silently_truncate_password = false ,
817
812
};
818
- var verify_options = VerifyOptions {};
813
+ var verify_options = VerifyOptions { . silently_truncate_password = false };
819
814
820
815
var buf : [hash_length ]u8 = undefined ;
821
816
const s = try strHash ("password" , hash_options , & buf );
@@ -837,7 +832,7 @@ test "bcrypt crypt format" {
837
832
strVerify (long_s , "password" ** 101 , verify_options ),
838
833
);
839
834
840
- hash_options .silently_truncate_password = true ;
835
+ hash_options .params . silently_truncate_password = true ;
841
836
verify_options .silently_truncate_password = true ;
842
837
long_s = try strHash ("password" ** 100 , hash_options , & long_buf );
843
838
try strVerify (long_s , "password" ** 101 , verify_options );
@@ -851,11 +846,10 @@ test "bcrypt crypt format" {
851
846
852
847
test "bcrypt phc format" {
853
848
var hash_options = HashOptions {
854
- .params = .{ .rounds_log = 5 },
849
+ .params = .{ .rounds_log = 5 , . silently_truncate_password = false },
855
850
.encoding = .phc ,
856
- .silently_truncate_password = false ,
857
851
};
858
- var verify_options = VerifyOptions {};
852
+ var verify_options = VerifyOptions { . silently_truncate_password = false };
859
853
const prefix = "$bcrypt$" ;
860
854
861
855
var buf : [hash_length * 2 ]u8 = undefined ;
@@ -878,7 +872,7 @@ test "bcrypt phc format" {
878
872
strVerify (long_s , "password" ** 101 , verify_options ),
879
873
);
880
874
881
- hash_options .silently_truncate_password = true ;
875
+ hash_options .params . silently_truncate_password = true ;
882
876
verify_options .silently_truncate_password = true ;
883
877
long_s = try strHash ("password" ** 100 , hash_options , & long_buf );
884
878
try strVerify (long_s , "password" ** 101 , verify_options );
0 commit comments