Add scrypt.createWithSalt

This commit is contained in:
Frank Denis 2026-01-07 23:14:37 +01:00
parent 2153759a22
commit 4a29a6e432

View file

@ -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 });
}