| ... | @@ -412,21 +412,19 @@ pub const Params = struct { | ... | @@ -412,21 +412,19 @@ pub const Params = struct { |
| 412 | /// log2 of the number of rounds | 412 | /// log2 of the number of rounds |
| 413 | rounds_log: u6, | 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 | /// Minimum recommended parameters according to the | 422 | /// Minimum recommended parameters according to the |
| 416 | /// [OWASP cheat sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html). | 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. | 427 | fn bcryptWithTruncation( |
| 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( | | |
| 430 | password: []const u8, | 428 | password: []const u8, |
| 431 | salt: [salt_length]u8, | 429 | salt: [salt_length]u8, |
| 432 | params: Params, | 430 | params: Params, |
| ... | @@ -465,17 +463,15 @@ pub fn bcrypt( | ... | @@ -465,17 +463,15 @@ pub fn bcrypt( |
| 465 | /// | 463 | /// |
| 466 | /// The function returns the hash as a `dk_length` byte array, that doesn't include anything besides the hash output. | 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. | 466 | /// This function was designed for password storage, not for key derivation. |
| 469 | /// | 467 | /// For key derivation, use `bcrypt.pbkdf()` or `bcrypt.opensshKdf()` instead. |
| 470 | /// This function is identical to `bcrypt`, except that it doesn't silently truncate passwords. | 468 | pub fn bcrypt( |
| 471 | /// Instead, passwords longer than 72 bytes are pre-hashed using HMAC-SHA512 before being passed to bcrypt. | | |
| 472 | pub fn bcryptWithoutTruncation( | | |
| 473 | password: []const u8, | 469 | password: []const u8, |
| 474 | salt: [salt_length]u8, | 470 | salt: [salt_length]u8, |
| 475 | params: Params, | 471 | params: Params, |
| 476 | ) [dk_length]u8 { | 472 | ) [dk_length]u8 { |
| 477 | if (password.len <= 72) { | 473 | if (password.len <= 72 or params.silently_truncate_password) { |
| 478 | return bcrypt(password, salt, params); | 474 | return bcryptWithTruncation(password, salt, params); |
| 479 | } | 475 | } |
| 480 | | 476 | |
| 481 | var pre_hash: [HmacSha512.mac_length]u8 = undefined; | 477 | var pre_hash: [HmacSha512.mac_length]u8 = undefined; |
| ... | @@ -485,7 +481,7 @@ pub fn bcryptWithoutTruncation( | ... | @@ -485,7 +481,7 @@ pub fn bcryptWithoutTruncation( |
| 485 | var pre_hash_b64: [Encoder.calcSize(pre_hash.len)]u8 = undefined; | 481 | var pre_hash_b64: [Encoder.calcSize(pre_hash.len)]u8 = undefined; |
| 486 | _ = Encoder.encode(&pre_hash_b64, &pre_hash); | 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 | const pbkdf_prf = struct { | 487 | const pbkdf_prf = struct { |
| ... | @@ -629,9 +625,8 @@ const crypt_format = struct { | ... | @@ -629,9 +625,8 @@ const crypt_format = struct { |
| 629 | password: []const u8, | 625 | password: []const u8, |
| 630 | salt: [salt_length]u8, | 626 | salt: [salt_length]u8, |
| 631 | params: Params, | 627 | params: Params, |
| 632 | silently_truncate_password: bool, | | |
| 633 | ) [hash_length]u8 { | 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 | var salt_str: [salt_str_length]u8 = undefined; | 631 | var salt_str: [salt_str_length]u8 = undefined; |
| 637 | _ = Codec.Encoder.encode(salt_str[0..], salt[0..]); | 632 | _ = Codec.Encoder.encode(salt_str[0..], salt[0..]); |
| ... | @@ -666,13 +661,12 @@ const PhcFormatHasher = struct { | ... | @@ -666,13 +661,12 @@ const PhcFormatHasher = struct { |
| 666 | fn create( | 661 | fn create( |
| 667 | password: []const u8, | 662 | password: []const u8, |
| 668 | params: Params, | 663 | params: Params, |
| 669 | silently_truncate_password: bool, | | |
| 670 | buf: []u8, | 664 | buf: []u8, |
| 671 | ) HasherError![]const u8 { | 665 | ) HasherError![]const u8 { |
| 672 | var salt: [salt_length]u8 = undefined; | 666 | var salt: [salt_length]u8 = undefined; |
| 673 | crypto.random.bytes(&salt); | 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 | return phc_format.serialize(HashResult{ | 671 | return phc_format.serialize(HashResult{ |
| 678 | .alg_id = alg_id, | 672 | .alg_id = alg_id, |
| ... | @@ -694,8 +688,11 @@ const PhcFormatHasher = struct { | ... | @@ -694,8 +688,11 @@ const PhcFormatHasher = struct { |
| 694 | if (hash_result.salt.len != salt_length or hash_result.hash.len != dk_length) | 688 | if (hash_result.salt.len != salt_length or hash_result.hash.len != dk_length) |
| 695 | return HasherError.InvalidEncoding; | 689 | return HasherError.InvalidEncoding; |
| 696 | | 690 | |
| 697 | const params = Params{ .rounds_log = hash_result.r }; | 691 | const params = Params{ |
| 698 | const hash = if (silently_truncate_password) bcrypt(password, hash_result.salt.buf, params) else bcryptWithoutTruncation(password, hash_result.salt.buf, 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 | const expected_hash = hash_result.hash.constSlice(); | 696 | const expected_hash = hash_result.hash.constSlice(); |
| 700 | | 697 | |
| 701 | if (!mem.eql(u8, &hash, expected_hash)) return HasherError.PasswordVerificationFailed; | 698 | if (!mem.eql(u8, &hash, expected_hash)) return HasherError.PasswordVerificationFailed; |
| ... | @@ -711,7 +708,6 @@ const CryptFormatHasher = struct { | ... | @@ -711,7 +708,6 @@ const CryptFormatHasher = struct { |
| 711 | fn create( | 708 | fn create( |
| 712 | password: []const u8, | 709 | password: []const u8, |
| 713 | params: Params, | 710 | params: Params, |
| 714 | silently_truncate_password: bool, | | |
| 715 | buf: []u8, | 711 | buf: []u8, |
| 716 | ) HasherError![]const u8 { | 712 | ) HasherError![]const u8 { |
| 717 | if (buf.len < pwhash_str_length) return HasherError.NoSpaceLeft; | 713 | if (buf.len < pwhash_str_length) return HasherError.NoSpaceLeft; |
| ... | @@ -719,7 +715,7 @@ const CryptFormatHasher = struct { | ... | @@ -719,7 +715,7 @@ const CryptFormatHasher = struct { |
| 719 | var salt: [salt_length]u8 = undefined; | 715 | var salt: [salt_length]u8 = undefined; |
| 720 | crypto.random.bytes(&salt); | 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 | @memcpy(buf[0..hash.len], &hash); | 719 | @memcpy(buf[0..hash.len], &hash); |
| 724 | | 720 | |
| 725 | return buf[0..pwhash_str_length]; | 721 | return buf[0..pwhash_str_length]; |
| ... | @@ -742,7 +738,10 @@ const CryptFormatHasher = struct { | ... | @@ -742,7 +738,10 @@ const CryptFormatHasher = struct { |
| 742 | var salt: [salt_length]u8 = undefined; | 738 | var salt: [salt_length]u8 = undefined; |
| 743 | crypt_format.Codec.Decoder.decode(salt[0..], salt_str[0..]) catch return HasherError.InvalidEncoding; | 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 | if (!mem.eql(u8, wanted_s[0..], str[0..])) return HasherError.PasswordVerificationFailed; | 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,9 +754,6 @@ pub const HashOptions = struct { |
| 755 | params: Params, | 754 | params: Params, |
| 756 | /// Encoding to use for the output of the hash function. | 755 | /// Encoding to use for the output of the hash function. |
| 757 | encoding: pwhash.Encoding, | 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 | /// Compute a hash of a password using 2^rounds_log rounds of the bcrypt key stretching function. | 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,8 +769,8 @@ pub fn strHash( |
| 773 | out: []u8, | 769 | out: []u8, |
| 774 | ) Error![]const u8 { | 770 | ) Error![]const u8 { |
| 775 | switch (options.encoding) { | 771 | switch (options.encoding) { |
| 776 | .phc => return PhcFormatHasher.create(password, options.params, options.silently_truncate_password, out), | 772 | .phc => return PhcFormatHasher.create(password, options.params, out), |
| 777 | .crypt => return CryptFormatHasher.create(password, options.params, options.silently_truncate_password, 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,7 +779,7 @@ pub const VerifyOptions = struct { |
| 783 | /// For `bcrypt`, that can be left to `null`. | 779 | /// For `bcrypt`, that can be left to `null`. |
| 784 | allocator: ?mem.Allocator = null, | 780 | allocator: ?mem.Allocator = null, |
| 785 | /// Whether to silently truncate the password to 72 bytes, or pre-hash the password when it is longer. | 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 | /// Verify that a previously computed hash is valid for a given password. | 785 | /// Verify that a previously computed hash is valid for a given password. |
| ... | @@ -811,11 +807,10 @@ test "bcrypt codec" { | ... | @@ -811,11 +807,10 @@ test "bcrypt codec" { |
| 811 | | 807 | |
| 812 | test "bcrypt crypt format" { | 808 | test "bcrypt crypt format" { |
| 813 | var hash_options = HashOptions{ | 809 | var hash_options = HashOptions{ |
| 814 | .params = .{ .rounds_log = 5 }, | 810 | .params = .{ .rounds_log = 5, .silently_truncate_password = false }, |
| 815 | .encoding = .crypt, | 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 | var buf: [hash_length]u8 = undefined; | 815 | var buf: [hash_length]u8 = undefined; |
| 821 | const s = try strHash("password", hash_options, &buf); | 816 | const s = try strHash("password", hash_options, &buf); |
| ... | @@ -837,7 +832,7 @@ test "bcrypt crypt format" { | ... | @@ -837,7 +832,7 @@ test "bcrypt crypt format" { |
| 837 | strVerify(long_s, "password" ** 101, verify_options), | 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 | verify_options.silently_truncate_password = true; | 836 | verify_options.silently_truncate_password = true; |
| 842 | long_s = try strHash("password" ** 100, hash_options, &long_buf); | 837 | long_s = try strHash("password" ** 100, hash_options, &long_buf); |
| 843 | try strVerify(long_s, "password" ** 101, verify_options); | 838 | try strVerify(long_s, "password" ** 101, verify_options); |
| ... | @@ -851,11 +846,10 @@ test "bcrypt crypt format" { | ... | @@ -851,11 +846,10 @@ test "bcrypt crypt format" { |
| 851 | | 846 | |
| 852 | test "bcrypt phc format" { | 847 | test "bcrypt phc format" { |
| 853 | var hash_options = HashOptions{ | 848 | var hash_options = HashOptions{ |
| 854 | .params = .{ .rounds_log = 5 }, | 849 | .params = .{ .rounds_log = 5, .silently_truncate_password = false }, |
| 855 | .encoding = .phc, | 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 | const prefix = "$bcrypt$"; | 853 | const prefix = "$bcrypt$"; |
| 860 | | 854 | |
| 861 | var buf: [hash_length * 2]u8 = undefined; | 855 | var buf: [hash_length * 2]u8 = undefined; |
| ... | @@ -878,7 +872,7 @@ test "bcrypt phc format" { | ... | @@ -878,7 +872,7 @@ test "bcrypt phc format" { |
| 878 | strVerify(long_s, "password" ** 101, verify_options), | 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 | verify_options.silently_truncate_password = true; | 876 | verify_options.silently_truncate_password = true; |
| 883 | long_s = try strHash("password" ** 100, hash_options, &long_buf); | 877 | long_s = try strHash("password" ** 100, hash_options, &long_buf); |
| 884 | try strVerify(long_s, "password" ** 101, verify_options); | 878 | try strVerify(long_s, "password" ** 101, verify_options); |