| ... | ... | @@ -662,15 +662,27 @@ const PhcFormatHasher = struct { |
| 662 | 662 | password: []const u8, |
| 663 | 663 | params: Params, |
| 664 | 664 | buf: []u8, |
| 665 | | /// Filled with cryptographically secure entropy. |
| 666 | | salt: *const [salt_length]u8, |
| 665 | io: std.Io, |
| 667 | 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 | 682 | return phc_format.serialize(HashResult{ |
| 671 | 683 | .alg_id = alg_id, |
| 672 | 684 | .r = params.rounds_log, |
| 673 | | .salt = try BinValue(salt_length).fromSlice(salt), |
| 685 | .salt = try BinValue(salt_length).fromSlice(&salt), |
| 674 | 686 | .hash = try BinValue(dk_length).fromSlice(&hash), |
| 675 | 687 | }, buf); |
| 676 | 688 | } |
| ... | ... | @@ -708,7 +720,19 @@ const CryptFormatHasher = struct { |
| 708 | 720 | password: []const u8, |
| 709 | 721 | params: Params, |
| 710 | 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 | 736 | salt: *const [salt_length]u8, |
| 713 | 737 | ) HasherError![]const u8 { |
| 714 | 738 | if (buf.len < pwhash_str_length) return HasherError.NoSpaceLeft; |
| ... | ... | @@ -770,12 +794,26 @@ pub fn strHash( |
| 770 | 794 | password: []const u8, |
| 771 | 795 | options: HashOptions, |
| 772 | 796 | out: []u8, |
| 773 | | /// Filled with cryptographically secure entropy. |
| 774 | | salt: *const [salt_length]u8, |
| 797 | io: std.Io, |
| 775 | 798 | ) Error![]const u8 { |
| 776 | 799 | switch (options.encoding) { |
| 777 | | .phc => return PhcFormatHasher.create(password, options.params, out, salt), |
| 778 | | .crypt => return CryptFormatHasher.create(password, options.params, out, salt), |
| 800 | .phc => return PhcFormatHasher.create(password, options.params, out, io), |
| 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 | 859 | var verify_options: VerifyOptions = .{ .silently_truncate_password = false }; |
| 822 | 860 | |
| 823 | 861 | var buf: [hash_length]u8 = undefined; |
| 824 | | const s = s: { |
| 825 | | var salt: [salt_length]u8 = undefined; |
| 826 | | io.random(&salt); |
| 827 | | break :s try strHash("password", hash_options, &buf, &salt); |
| 828 | | }; |
| 862 | const s = try strHash("password", hash_options, &buf, io); |
| 829 | 863 | |
| 830 | 864 | try testing.expect(mem.startsWith(u8, s, crypt_format.prefix)); |
| 831 | 865 | try strVerify(s, "password", verify_options); |
| ... | ... | @@ -835,11 +869,7 @@ test "bcrypt crypt format" { |
| 835 | 869 | ); |
| 836 | 870 | |
| 837 | 871 | var long_buf: [hash_length]u8 = undefined; |
| 838 | | var long_s = s: { |
| 839 | | var salt: [salt_length]u8 = undefined; |
| 840 | | io.random(&salt); |
| 841 | | break :s try strHash("password" ** 100, hash_options, &long_buf, &salt); |
| 842 | | }; |
| 872 | var long_s = try strHash("password" ** 100, hash_options, &long_buf, io); |
| 843 | 873 | |
| 844 | 874 | try testing.expect(mem.startsWith(u8, long_s, crypt_format.prefix)); |
| 845 | 875 | try strVerify(long_s, "password" ** 100, verify_options); |
| ... | ... | @@ -850,11 +880,7 @@ test "bcrypt crypt format" { |
| 850 | 880 | |
| 851 | 881 | hash_options.params.silently_truncate_password = true; |
| 852 | 882 | verify_options.silently_truncate_password = true; |
| 853 | | long_s = s: { |
| 854 | | var salt: [salt_length]u8 = undefined; |
| 855 | | io.random(&salt); |
| 856 | | break :s try strHash("password" ** 100, hash_options, &long_buf, &salt); |
| 857 | | }; |
| 883 | long_s = try strHash("password" ** 100, hash_options, &long_buf, io); |
| 858 | 884 | try strVerify(long_s, "password" ** 101, verify_options); |
| 859 | 885 | |
| 860 | 886 | try strVerify( |
| ... | ... | @@ -874,11 +900,7 @@ test "bcrypt phc format" { |
| 874 | 900 | const prefix = "$bcrypt$"; |
| 875 | 901 | |
| 876 | 902 | var buf: [hash_length * 2]u8 = undefined; |
| 877 | | const s = s: { |
| 878 | | var salt: [salt_length]u8 = undefined; |
| 879 | | io.random(&salt); |
| 880 | | break :s try strHash("password", hash_options, &buf, &salt); |
| 881 | | }; |
| 903 | const s = try strHash("password", hash_options, &buf, io); |
| 882 | 904 | |
| 883 | 905 | try testing.expect(mem.startsWith(u8, s, prefix)); |
| 884 | 906 | try strVerify(s, "password", verify_options); |
| ... | ... | @@ -888,11 +910,7 @@ test "bcrypt phc format" { |
| 888 | 910 | ); |
| 889 | 911 | |
| 890 | 912 | var long_buf: [hash_length * 2]u8 = undefined; |
| 891 | | var long_s = s: { |
| 892 | | var salt: [salt_length]u8 = undefined; |
| 893 | | io.random(&salt); |
| 894 | | break :s try strHash("password" ** 100, hash_options, &long_buf, &salt); |
| 895 | | }; |
| 913 | var long_s = try strHash("password" ** 100, hash_options, &long_buf, io); |
| 896 | 914 | |
| 897 | 915 | try testing.expect(mem.startsWith(u8, long_s, prefix)); |
| 898 | 916 | try strVerify(long_s, "password" ** 100, verify_options); |
| ... | ... | @@ -903,11 +921,7 @@ test "bcrypt phc format" { |
| 903 | 921 | |
| 904 | 922 | hash_options.params.silently_truncate_password = true; |
| 905 | 923 | verify_options.silently_truncate_password = true; |
| 906 | | long_s = s: { |
| 907 | | var salt: [salt_length]u8 = undefined; |
| 908 | | io.random(&salt); |
| 909 | | break :s try strHash("password" ** 100, hash_options, &long_buf, &salt); |
| 910 | | }; |
| 924 | long_s = try strHash("password" ** 100, hash_options, &long_buf, io); |
| 911 | 925 | try strVerify(long_s, "password" ** 101, verify_options); |
| 912 | 926 | |
| 913 | 927 | try strVerify( |
| ... | ... | @@ -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 | 953 | test "openssh kdf" { |
| 921 | 954 | var key: [100]u8 = undefined; |
| 922 | 955 | const pass = "password"; |