authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2026-01-07 23:14:21+01:00
committergravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2026-01-07 22:44:37+01:00
log3ee092536ec9739794e40f879febd5edadc7b388
tree9cede242bf707a62c4af1976ca772e4bcc9b6611
parent4a29a6e43265f37ac504e4f0a9c78f4917fcab65

Allow the salt to be passed as a parameter in bcrypt


1 files changed, 72 insertions(+), 39 deletions(-)

lib/std/crypto/bcrypt.zig+72-39
......@@ -662,15 +662,27 @@ const PhcFormatHasher = struct {
662662 password: []const u8,
663663 params: Params,
664664 buf: []u8,
665 /// Filled with cryptographically secure entropy.
666 salt: *const [salt_length]u8,
665 io: std.Io,
667666 ) 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);
669681
670682 return phc_format.serialize(HashResult{
671683 .alg_id = alg_id,
672684 .r = params.rounds_log,
673 .salt = try BinValue(salt_length).fromSlice(salt),
685 .salt = try BinValue(salt_length).fromSlice(&salt),
674686 .hash = try BinValue(dk_length).fromSlice(&hash),
675687 }, buf);
676688 }
......@@ -708,7 +720,19 @@ const CryptFormatHasher = struct {
708720 password: []const u8,
709721 params: Params,
710722 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,
712736 salt: *const [salt_length]u8,
713737 ) HasherError![]const u8 {
714738 if (buf.len < pwhash_str_length) return HasherError.NoSpaceLeft;
......@@ -770,12 +794,26 @@ pub fn strHash(
770794 password: []const u8,
771795 options: HashOptions,
772796 out: []u8,
773 /// Filled with cryptographically secure entropy.
774 salt: *const [salt_length]u8,
797 io: std.Io,
775798) Error![]const u8 {
776799 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.
808pub 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),
779817 }
780818}
781819
......@@ -821,11 +859,7 @@ test "bcrypt crypt format" {
821859 var verify_options: VerifyOptions = .{ .silently_truncate_password = false };
822860
823861 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);
829863
830864 try testing.expect(mem.startsWith(u8, s, crypt_format.prefix));
831865 try strVerify(s, "password", verify_options);
......@@ -835,11 +869,7 @@ test "bcrypt crypt format" {
835869 );
836870
837871 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);
843873
844874 try testing.expect(mem.startsWith(u8, long_s, crypt_format.prefix));
845875 try strVerify(long_s, "password" ** 100, verify_options);
......@@ -850,11 +880,7 @@ test "bcrypt crypt format" {
850880
851881 hash_options.params.silently_truncate_password = true;
852882 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);
858884 try strVerify(long_s, "password" ** 101, verify_options);
859885
860886 try strVerify(
......@@ -874,11 +900,7 @@ test "bcrypt phc format" {
874900 const prefix = "$bcrypt$";
875901
876902 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);
882904
883905 try testing.expect(mem.startsWith(u8, s, prefix));
884906 try strVerify(s, "password", verify_options);
......@@ -888,11 +910,7 @@ test "bcrypt phc format" {
888910 );
889911
890912 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);
896914
897915 try testing.expect(mem.startsWith(u8, long_s, prefix));
898916 try strVerify(long_s, "password" ** 100, verify_options);
......@@ -903,11 +921,7 @@ test "bcrypt phc format" {
903921
904922 hash_options.params.silently_truncate_password = true;
905923 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);
911925 try strVerify(long_s, "password" ** 101, verify_options);
912926
913927 try strVerify(
......@@ -917,6 +931,25 @@ test "bcrypt phc format" {
917931 );
918932}
919933
934test "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
920953test "openssh kdf" {
921954 var key: [100]u8 = undefined;
922955 const pass = "password";