| ... | @@ -662,15 +662,27 @@ const PhcFormatHasher = struct { | ... | @@ -662,15 +662,27 @@ const PhcFormatHasher = struct { |
| 662 | password: []const u8, | 662 | password: []const u8, |
| 663 | params: Params, | 663 | params: Params, |
| 664 | buf: []u8, | 664 | buf: []u8, |
| 665 | /// Filled with cryptographically secure entropy. | 665 | io: std.Io, |
| 666 | salt: *const [salt_length]u8, | | |
| 667 | ) HasherError![]const u8 { | 666 | ) HasherError![]const u8 { |
| 668 | const hash = bcrypt(password, salt, params); | 667 | var salt: [salt_length]u8 = undefined; |
| | 668 | io.random(&salt); |
| | 669 | return createWithSalt(password, params, buf, salt); |
| | 670 | } |
| | 671 | |
| | 672 | /// Return a deterministic hash of the password encoded as a PHC-format string. |
| | 673 | /// Uses the provided salt instead of generating one randomly. |
| | 674 | fn createWithSalt( |
| | 675 | password: []const u8, |
| | 676 | params: Params, |
| | 677 | buf: []u8, |
| | 678 | salt: [salt_length]u8, |
| | 679 | ) HasherError![]const u8 { |
| | 680 | const hash = bcrypt(password, &salt, params); |
| 669 | | 681 | |
| 670 | return phc_format.serialize(HashResult{ | 682 | return phc_format.serialize(HashResult{ |
| 671 | .alg_id = alg_id, | 683 | .alg_id = alg_id, |
| 672 | .r = params.rounds_log, | 684 | .r = params.rounds_log, |
| 673 | .salt = try BinValue(salt_length).fromSlice(salt), | 685 | .salt = try BinValue(salt_length).fromSlice(&salt), |
| 674 | .hash = try BinValue(dk_length).fromSlice(&hash), | 686 | .hash = try BinValue(dk_length).fromSlice(&hash), |
| 675 | }, buf); | 687 | }, buf); |
| 676 | } | 688 | } |
| ... | @@ -708,7 +720,19 @@ const CryptFormatHasher = struct { | ... | @@ -708,7 +720,19 @@ const CryptFormatHasher = struct { |
| 708 | password: []const u8, | 720 | password: []const u8, |
| 709 | params: Params, | 721 | params: Params, |
| 710 | buf: []u8, | 722 | buf: []u8, |
| 711 | /// Filled with cryptographically secure entropy. | 723 | io: std.Io, |
| | 724 | ) HasherError![]const u8 { |
| | 725 | var salt: [salt_length]u8 = undefined; |
| | 726 | io.random(&salt); |
| | 727 | return createWithSalt(password, params, buf, &salt); |
| | 728 | } |
| | 729 | |
| | 730 | /// Return a deterministic hash of the password encoded into the modular crypt format. |
| | 731 | /// Uses the provided salt instead of generating one randomly. |
| | 732 | fn createWithSalt( |
| | 733 | password: []const u8, |
| | 734 | params: Params, |
| | 735 | buf: []u8, |
| 712 | salt: *const [salt_length]u8, | 736 | salt: *const [salt_length]u8, |
| 713 | ) HasherError![]const u8 { | 737 | ) HasherError![]const u8 { |
| 714 | if (buf.len < pwhash_str_length) return HasherError.NoSpaceLeft; | 738 | if (buf.len < pwhash_str_length) return HasherError.NoSpaceLeft; |
| ... | @@ -770,12 +794,26 @@ pub fn strHash( | ... | @@ -770,12 +794,26 @@ pub fn strHash( |
| 770 | password: []const u8, | 794 | password: []const u8, |
| 771 | options: HashOptions, | 795 | options: HashOptions, |
| 772 | out: []u8, | 796 | out: []u8, |
| 773 | /// Filled with cryptographically secure entropy. | 797 | io: std.Io, |
| 774 | salt: *const [salt_length]u8, | | |
| 775 | ) Error![]const u8 { | 798 | ) Error![]const u8 { |
| 776 | switch (options.encoding) { | 799 | switch (options.encoding) { |
| 777 | .phc => return PhcFormatHasher.create(password, options.params, out, salt), | 800 | .phc => return PhcFormatHasher.create(password, options.params, out, io), |
| 778 | .crypt => return CryptFormatHasher.create(password, options.params, out, salt), | 801 | .crypt => return CryptFormatHasher.create(password, options.params, out, io), |
| | 802 | } |
| | 803 | } |
| | 804 | |
| | 805 | /// Compute a deterministic hash of a password using the bcrypt key derivation function. |
| | 806 | /// The function returns a string that includes all the parameters required for verification. |
| | 807 | /// Uses the provided salt instead of generating one randomly. |
| | 808 | pub fn strHashWithSalt( |
| | 809 | password: []const u8, |
| | 810 | options: HashOptions, |
| | 811 | out: []u8, |
| | 812 | salt: [salt_length]u8, |
| | 813 | ) Error![]const u8 { |
| | 814 | switch (options.encoding) { |
| | 815 | .phc => return PhcFormatHasher.createWithSalt(password, options.params, out, salt), |
| | 816 | .crypt => return CryptFormatHasher.createWithSalt(password, options.params, out, &salt), |
| 779 | } | 817 | } |
| 780 | } | 818 | } |
| 781 | | 819 | |
| ... | @@ -821,11 +859,7 @@ test "bcrypt crypt format" { | ... | @@ -821,11 +859,7 @@ test "bcrypt crypt format" { |
| 821 | var verify_options: VerifyOptions = .{ .silently_truncate_password = false }; | 859 | var verify_options: VerifyOptions = .{ .silently_truncate_password = false }; |
| 822 | | 860 | |
| 823 | var buf: [hash_length]u8 = undefined; | 861 | var buf: [hash_length]u8 = undefined; |
| 824 | const s = s: { | 862 | const s = try strHash("password", hash_options, &buf, io); |
| 825 | var salt: [salt_length]u8 = undefined; | | |
| 826 | io.random(&salt); | | |
| 827 | break :s try strHash("password", hash_options, &buf, &salt); | | |
| 828 | }; | | |
| 829 | | 863 | |
| 830 | try testing.expect(mem.startsWith(u8, s, crypt_format.prefix)); | 864 | try testing.expect(mem.startsWith(u8, s, crypt_format.prefix)); |
| 831 | try strVerify(s, "password", verify_options); | 865 | try strVerify(s, "password", verify_options); |
| ... | @@ -835,11 +869,7 @@ test "bcrypt crypt format" { | ... | @@ -835,11 +869,7 @@ test "bcrypt crypt format" { |
| 835 | ); | 869 | ); |
| 836 | | 870 | |
| 837 | var long_buf: [hash_length]u8 = undefined; | 871 | var long_buf: [hash_length]u8 = undefined; |
| 838 | var long_s = s: { | 872 | var long_s = try strHash("password" ** 100, hash_options, &long_buf, io); |
| 839 | var salt: [salt_length]u8 = undefined; | | |
| 840 | io.random(&salt); | | |
| 841 | break :s try strHash("password" ** 100, hash_options, &long_buf, &salt); | | |
| 842 | }; | | |
| 843 | | 873 | |
| 844 | try testing.expect(mem.startsWith(u8, long_s, crypt_format.prefix)); | 874 | try testing.expect(mem.startsWith(u8, long_s, crypt_format.prefix)); |
| 845 | try strVerify(long_s, "password" ** 100, verify_options); | 875 | try strVerify(long_s, "password" ** 100, verify_options); |
| ... | @@ -850,11 +880,7 @@ test "bcrypt crypt format" { | ... | @@ -850,11 +880,7 @@ test "bcrypt crypt format" { |
| 850 | | 880 | |
| 851 | hash_options.params.silently_truncate_password = true; | 881 | hash_options.params.silently_truncate_password = true; |
| 852 | verify_options.silently_truncate_password = true; | 882 | verify_options.silently_truncate_password = true; |
| 853 | long_s = s: { | 883 | long_s = try strHash("password" ** 100, hash_options, &long_buf, io); |
| 854 | var salt: [salt_length]u8 = undefined; | | |
| 855 | io.random(&salt); | | |
| 856 | break :s try strHash("password" ** 100, hash_options, &long_buf, &salt); | | |
| 857 | }; | | |
| 858 | try strVerify(long_s, "password" ** 101, verify_options); | 884 | try strVerify(long_s, "password" ** 101, verify_options); |
| 859 | | 885 | |
| 860 | try strVerify( | 886 | try strVerify( |
| ... | @@ -874,11 +900,7 @@ test "bcrypt phc format" { | ... | @@ -874,11 +900,7 @@ test "bcrypt phc format" { |
| 874 | const prefix = "$bcrypt$"; | 900 | const prefix = "$bcrypt$"; |
| 875 | | 901 | |
| 876 | var buf: [hash_length * 2]u8 = undefined; | 902 | var buf: [hash_length * 2]u8 = undefined; |
| 877 | const s = s: { | 903 | const s = try strHash("password", hash_options, &buf, io); |
| 878 | var salt: [salt_length]u8 = undefined; | | |
| 879 | io.random(&salt); | | |
| 880 | break :s try strHash("password", hash_options, &buf, &salt); | | |
| 881 | }; | | |
| 882 | | 904 | |
| 883 | try testing.expect(mem.startsWith(u8, s, prefix)); | 905 | try testing.expect(mem.startsWith(u8, s, prefix)); |
| 884 | try strVerify(s, "password", verify_options); | 906 | try strVerify(s, "password", verify_options); |
| ... | @@ -888,11 +910,7 @@ test "bcrypt phc format" { | ... | @@ -888,11 +910,7 @@ test "bcrypt phc format" { |
| 888 | ); | 910 | ); |
| 889 | | 911 | |
| 890 | var long_buf: [hash_length * 2]u8 = undefined; | 912 | var long_buf: [hash_length * 2]u8 = undefined; |
| 891 | var long_s = s: { | 913 | var long_s = try strHash("password" ** 100, hash_options, &long_buf, io); |
| 892 | var salt: [salt_length]u8 = undefined; | | |
| 893 | io.random(&salt); | | |
| 894 | break :s try strHash("password" ** 100, hash_options, &long_buf, &salt); | | |
| 895 | }; | | |
| 896 | | 914 | |
| 897 | try testing.expect(mem.startsWith(u8, long_s, prefix)); | 915 | try testing.expect(mem.startsWith(u8, long_s, prefix)); |
| 898 | try strVerify(long_s, "password" ** 100, verify_options); | 916 | try strVerify(long_s, "password" ** 100, verify_options); |
| ... | @@ -903,11 +921,7 @@ test "bcrypt phc format" { | ... | @@ -903,11 +921,7 @@ test "bcrypt phc format" { |
| 903 | | 921 | |
| 904 | hash_options.params.silently_truncate_password = true; | 922 | hash_options.params.silently_truncate_password = true; |
| 905 | verify_options.silently_truncate_password = true; | 923 | verify_options.silently_truncate_password = true; |
| 906 | long_s = s: { | 924 | long_s = try strHash("password" ** 100, hash_options, &long_buf, io); |
| 907 | var salt: [salt_length]u8 = undefined; | | |
| 908 | io.random(&salt); | | |
| 909 | break :s try strHash("password" ** 100, hash_options, &long_buf, &salt); | | |
| 910 | }; | | |
| 911 | try strVerify(long_s, "password" ** 101, verify_options); | 925 | try strVerify(long_s, "password" ** 101, verify_options); |
| 912 | | 926 | |
| 913 | try strVerify( | 927 | try strVerify( |
| ... | @@ -917,6 +931,25 @@ test "bcrypt phc format" { | ... | @@ -917,6 +931,25 @@ test "bcrypt phc format" { |
| 917 | ); | 931 | ); |
| 918 | } | 932 | } |
| 919 | | 933 | |
| | 934 | test "strHashWithSalt deterministic" { |
| | 935 | const password = "testpass"; |
| | 936 | const salt: [salt_length]u8 = "0123456789abcdef".*; |
| | 937 | const params: Params = .{ .rounds_log = 5, .silently_truncate_password = false }; |
| | 938 | |
| | 939 | var buf1: [hash_length * 2]u8 = undefined; |
| | 940 | var buf2: [hash_length * 2]u8 = undefined; |
| | 941 | |
| | 942 | const str1 = try strHashWithSalt(password, .{ .params = params, .encoding = .phc }, &buf1, salt); |
| | 943 | const str2 = try strHashWithSalt(password, .{ .params = params, .encoding = .phc }, &buf2, salt); |
| | 944 | try testing.expectEqualStrings(str1, str2); |
| | 945 | try strVerify(str1, password, .{ .silently_truncate_password = false }); |
| | 946 | |
| | 947 | const str3 = try strHashWithSalt(password, .{ .params = params, .encoding = .crypt }, &buf1, salt); |
| | 948 | const str4 = try strHashWithSalt(password, .{ .params = params, .encoding = .crypt }, &buf2, salt); |
| | 949 | try testing.expectEqualStrings(str3, str4); |
| | 950 | try strVerify(str3, password, .{ .silently_truncate_password = false }); |
| | 951 | } |
| | 952 | |
| 920 | test "openssh kdf" { | 953 | test "openssh kdf" { |
| 921 | var key: [100]u8 = undefined; | 954 | var key: [100]u8 = undefined; |
| 922 | const pass = "password"; | 955 | const pass = "password"; |