diff --git a/lib/std/crypto/scrypt.zig b/lib/std/crypto/scrypt.zig index 63a29c4e27..913feb4b38 100644 --- a/lib/std/crypto/scrypt.zig +++ b/lib/std/crypto/scrypt.zig @@ -421,16 +421,27 @@ const PhcFormatHasher = struct { ) HasherError![]const u8 { var salt: [default_salt_len]u8 = undefined; io.random(&salt); + return createWithSalt(allocator, password, params, buf, &salt); + } + /// Return a deterministic hash of the password encoded as a PHC-format string. + /// Uses the provided salt instead of generating one randomly. + pub fn createWithSalt( + allocator: mem.Allocator, + password: []const u8, + params: Params, + buf: []u8, + salt: *const [default_salt_len]u8, + ) HasherError![]const u8 { var hash: [default_hash_len]u8 = undefined; - try kdf(allocator, &hash, password, &salt, params); + try kdf(allocator, &hash, password, salt, params); return phc_format.serialize(HashResult{ .alg_id = alg_id, .ln = params.ln, .r = params.r, .p = params.p, - .salt = try BinValue(max_salt_len).fromSlice(&salt), + .salt = try BinValue(max_salt_len).fromSlice(salt), .hash = try BinValue(max_hash_len).fromSlice(&hash), }, buf); } @@ -471,7 +482,19 @@ const CryptFormatHasher = struct { ) HasherError![]const u8 { var salt_bin: [default_salt_len]u8 = undefined; io.random(&salt_bin); - const salt = crypt_format.saltFromBin(salt_bin.len, salt_bin); + return createWithSalt(allocator, password, params, buf, &salt_bin); + } + + /// Return a deterministic hash of the password encoded into the modular crypt format. + /// Uses the provided salt instead of generating one randomly. + pub fn createWithSalt( + allocator: mem.Allocator, + password: []const u8, + params: Params, + buf: []u8, + salt_bin: *const [default_salt_len]u8, + ) HasherError![]const u8 { + const salt = crypt_format.saltFromBin(salt_bin.len, salt_bin.*); var hash: [default_hash_len]u8 = undefined; try kdf(allocator, &hash, password, &salt, params); @@ -526,6 +549,22 @@ pub fn strHash( } } +/// Compute a deterministic hash of a password using the scrypt key derivation function. +/// The function returns a string that includes all the parameters required for verification. +/// Uses the provided salt instead of generating one randomly. +pub fn strHashWithSalt( + password: []const u8, + options: HashOptions, + out: []u8, + salt: *const [default_salt_len]u8, +) Error![]const u8 { + const allocator = options.allocator orelse return Error.AllocatorRequired; + switch (options.encoding) { + .phc => return PhcFormatHasher.createWithSalt(allocator, password, options.params, out, salt), + .crypt => return CryptFormatHasher.createWithSalt(allocator, password, options.params, out, salt), + } +} + /// Options for hash verification. /// /// Allocator is required for scrypt. @@ -728,3 +767,23 @@ test "kdf fast" { try std.testing.expectEqualSlices(u8, &dk, v.want); } } + +test "strHashWithSalt deterministic" { + const alloc = std.testing.allocator; + const password = "testpass"; + const salt: [default_salt_len]u8 = "0123456789abcdef0123456789abcdef".*; + const params: Params = .{ .ln = 1, .r = 1, .p = 1 }; + + var buf1: [128]u8 = undefined; + var buf2: [128]u8 = undefined; + + const str1 = try strHashWithSalt(password, .{ .allocator = alloc, .params = params, .encoding = .phc }, &buf1, &salt); + const str2 = try strHashWithSalt(password, .{ .allocator = alloc, .params = params, .encoding = .phc }, &buf2, &salt); + try std.testing.expectEqualStrings(str1, str2); + try strVerify(str1, password, .{ .allocator = alloc }); + + const str3 = try strHashWithSalt(password, .{ .allocator = alloc, .params = params, .encoding = .crypt }, &buf1, &salt); + const str4 = try strHashWithSalt(password, .{ .allocator = alloc, .params = params, .encoding = .crypt }, &buf2, &salt); + try std.testing.expectEqualStrings(str3, str4); + try strVerify(str3, password, .{ .allocator = alloc }); +}