commit 4a29a6e43265f37ac504e4f0a9c78f4917fcab65 (tree)
parent 2153759a22f584c5ad43e15fdc13b1afcb5449a7
Author: Frank Denis <github@pureftpd.org>
Date: Wed, 7 Jan 2026 23:14:37 +0100
Add scrypt.createWithSalt
Diffstat:
1 file changed, 62 insertions(+), 3 deletions(-)
diff --git 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 });
+}