master
  1// https://tools.ietf.org/html/rfc7914
  2// https://github.com/golang/crypto/blob/master/scrypt/scrypt.go
  3// https://github.com/Tarsnap/scrypt
  4
  5const std = @import("std");
  6const crypto = std.crypto;
  7const fmt = std.fmt;
  8const math = std.math;
  9const mem = std.mem;
 10const meta = std.meta;
 11const pwhash = crypto.pwhash;
 12
 13const phc_format = @import("phc_encoding.zig");
 14
 15const HmacSha256 = crypto.auth.hmac.sha2.HmacSha256;
 16const KdfError = pwhash.KdfError;
 17const HasherError = pwhash.HasherError;
 18const EncodingError = phc_format.Error;
 19const Error = pwhash.Error;
 20
 21const max_size = math.maxInt(usize);
 22const max_int = max_size >> 1;
 23const default_salt_len = 32;
 24const default_hash_len = 32;
 25const max_salt_len = 64;
 26const max_hash_len = 64;
 27
 28fn blockCopy(dst: []align(16) u32, src: []align(16) const u32, n: usize) void {
 29    @memcpy(dst[0 .. n * 16], src[0 .. n * 16]);
 30}
 31
 32fn blockXor(dst: []align(16) u32, src: []align(16) const u32, n: usize) void {
 33    for (src[0 .. n * 16], 0..) |v, i| {
 34        dst[i] ^= v;
 35    }
 36}
 37
 38const QuarterRound = struct { a: usize, b: usize, c: usize, d: u6 };
 39
 40fn Rp(a: usize, b: usize, c: usize, d: u6) QuarterRound {
 41    return QuarterRound{ .a = a, .b = b, .c = c, .d = d };
 42}
 43
 44fn salsa8core(b: *align(16) [16]u32) void {
 45    const arx_steps = comptime [_]QuarterRound{
 46        Rp(4, 0, 12, 7),   Rp(8, 4, 0, 9),    Rp(12, 8, 4, 13),   Rp(0, 12, 8, 18),
 47        Rp(9, 5, 1, 7),    Rp(13, 9, 5, 9),   Rp(1, 13, 9, 13),   Rp(5, 1, 13, 18),
 48        Rp(14, 10, 6, 7),  Rp(2, 14, 10, 9),  Rp(6, 2, 14, 13),   Rp(10, 6, 2, 18),
 49        Rp(3, 15, 11, 7),  Rp(7, 3, 15, 9),   Rp(11, 7, 3, 13),   Rp(15, 11, 7, 18),
 50        Rp(1, 0, 3, 7),    Rp(2, 1, 0, 9),    Rp(3, 2, 1, 13),    Rp(0, 3, 2, 18),
 51        Rp(6, 5, 4, 7),    Rp(7, 6, 5, 9),    Rp(4, 7, 6, 13),    Rp(5, 4, 7, 18),
 52        Rp(11, 10, 9, 7),  Rp(8, 11, 10, 9),  Rp(9, 8, 11, 13),   Rp(10, 9, 8, 18),
 53        Rp(12, 15, 14, 7), Rp(13, 12, 15, 9), Rp(14, 13, 12, 13), Rp(15, 14, 13, 18),
 54    };
 55    var x = b.*;
 56    var j: usize = 0;
 57    while (j < 8) : (j += 2) {
 58        inline for (arx_steps) |r| {
 59            x[r.a] ^= math.rotl(u32, x[r.b] +% x[r.c], r.d);
 60        }
 61    }
 62    j = 0;
 63    while (j < 16) : (j += 1) {
 64        b[j] +%= x[j];
 65    }
 66}
 67
 68fn salsaXor(tmp: *align(16) [16]u32, in: []align(16) const u32, out: []align(16) u32) void {
 69    blockXor(tmp, in, 1);
 70    salsa8core(tmp);
 71    blockCopy(out, tmp, 1);
 72}
 73
 74fn blockMix(tmp: *align(16) [16]u32, in: []align(16) const u32, out: []align(16) u32, r: u30) void {
 75    blockCopy(tmp, @alignCast(in[(2 * r - 1) * 16 ..]), 1);
 76    var i: usize = 0;
 77    while (i < 2 * r) : (i += 2) {
 78        salsaXor(tmp, @alignCast(in[i * 16 ..]), @alignCast(out[i * 8 ..]));
 79        salsaXor(tmp, @alignCast(in[i * 16 + 16 ..]), @alignCast(out[i * 8 + r * 16 ..]));
 80    }
 81}
 82
 83fn integerify(b: []align(16) const u32, r: u30) u64 {
 84    const j = (2 * r - 1) * 16;
 85    return @as(u64, b[j]) | @as(u64, b[j + 1]) << 32;
 86}
 87
 88fn smix(b: []align(16) u8, r: u30, n: usize, v: []align(16) u32, xy: []align(16) u32) void {
 89    const x: []align(16) u32 = @alignCast(xy[0 .. 32 * r]);
 90    const y: []align(16) u32 = @alignCast(xy[32 * r ..]);
 91
 92    for (x, 0..) |*v1, j| {
 93        v1.* = mem.readInt(u32, b[4 * j ..][0..4], .little);
 94    }
 95
 96    var tmp: [16]u32 align(16) = undefined;
 97    var i: usize = 0;
 98    while (i < n) : (i += 2) {
 99        blockCopy(@alignCast(v[i * (32 * r) ..]), x, 2 * r);
100        blockMix(&tmp, x, y, r);
101
102        blockCopy(@alignCast(v[(i + 1) * (32 * r) ..]), y, 2 * r);
103        blockMix(&tmp, y, x, r);
104    }
105
106    i = 0;
107    while (i < n) : (i += 2) {
108        var j = @as(usize, @intCast(integerify(x, r) & (n - 1)));
109        blockXor(x, @alignCast(v[j * (32 * r) ..]), 2 * r);
110        blockMix(&tmp, x, y, r);
111
112        j = @as(usize, @intCast(integerify(y, r) & (n - 1)));
113        blockXor(y, @alignCast(v[j * (32 * r) ..]), 2 * r);
114        blockMix(&tmp, y, x, r);
115    }
116
117    for (x, 0..) |v1, j| {
118        mem.writeInt(u32, b[4 * j ..][0..4], v1, .little);
119    }
120}
121
122/// Scrypt parameters
123pub const Params = struct {
124    const Self = @This();
125
126    /// The CPU/Memory cost parameter [ln] is log2(N).
127    ln: u6,
128
129    /// The [r]esource usage parameter specifies the block size.
130    r: u30,
131
132    /// The [p]arallelization parameter.
133    /// A large value of [p] can be used to increase the computational cost of scrypt without
134    /// increasing the memory usage.
135    p: u30,
136
137    /// Baseline parameters for interactive logins
138    pub const interactive = Self.fromLimits(524288, 16777216);
139
140    /// Baseline parameters for offline usage
141    pub const sensitive = Self.fromLimits(33554432, 1073741824);
142
143    /// Recommended parameters according to the
144    /// [OWASP cheat sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html).
145    pub const owasp = Self{ .ln = 17, .r = 8, .p = 1 };
146
147    /// Create parameters from ops and mem limits, where mem_limit given in bytes
148    pub fn fromLimits(ops_limit: u64, mem_limit: usize) Self {
149        const ops = @max(32768, ops_limit);
150        const r: u30 = 8;
151        if (ops < mem_limit / 32) {
152            const max_n = ops / (r * 4);
153            return Self{ .r = r, .p = 1, .ln = @as(u6, @intCast(math.log2(max_n))) };
154        } else {
155            const max_n = mem_limit / (@as(usize, @intCast(r)) * 128);
156            const ln = @as(u6, @intCast(math.log2(max_n)));
157            const max_rp = @min(0x3fffffff, (ops / 4) / (@as(u64, 1) << ln));
158            return Self{ .r = r, .p = @as(u30, @intCast(max_rp / @as(u64, r))), .ln = ln };
159        }
160    }
161};
162
163/// Apply scrypt to generate a key from a password.
164///
165/// scrypt is defined in RFC 7914.
166///
167/// allocator: mem.Allocator.
168///
169/// derived_key: Slice of appropriate size for generated key. Generally 16 or 32 bytes in length.
170///              May be uninitialized. All bytes will be overwritten.
171///              Maximum size is `derived_key.len / 32 == 0xffff_ffff`.
172///
173/// password: Arbitrary sequence of bytes of any length.
174///
175/// salt: Arbitrary sequence of bytes of any length.
176///
177/// params: Params.
178pub fn kdf(
179    allocator: mem.Allocator,
180    derived_key: []u8,
181    password: []const u8,
182    salt: []const u8,
183    params: Params,
184) KdfError!void {
185    if (derived_key.len == 0) return KdfError.WeakParameters;
186    if (derived_key.len / 32 > 0xffff_ffff) return KdfError.OutputTooLong;
187    if (params.ln == 0 or params.r == 0 or params.p == 0) return KdfError.WeakParameters;
188
189    const n64 = @as(u64, 1) << params.ln;
190    if (n64 > max_size) return KdfError.WeakParameters;
191    const n = @as(usize, @intCast(n64));
192    if (@as(u64, params.r) * @as(u64, params.p) >= 1 << 30 or
193        params.r > max_int / 128 / @as(u64, params.p) or
194        params.r > max_int / 256 or
195        n > max_int / 128 / @as(u64, params.r)) return KdfError.WeakParameters;
196
197    const xy = try allocator.alignedAlloc(u32, .@"16", 64 * params.r);
198    defer allocator.free(xy);
199    const v = try allocator.alignedAlloc(u32, .@"16", 32 * n * params.r);
200    defer allocator.free(v);
201    var dk = try allocator.alignedAlloc(u8, .@"16", params.p * 128 * params.r);
202    defer allocator.free(dk);
203
204    try pwhash.pbkdf2(dk, password, salt, 1, HmacSha256);
205    var i: u32 = 0;
206    while (i < params.p) : (i += 1) {
207        smix(@alignCast(dk[i * 128 * params.r ..]), params.r, n, v, xy);
208    }
209    try pwhash.pbkdf2(derived_key, password, dk, 1, HmacSha256);
210}
211
212const crypt_format = struct {
213    /// String prefix for scrypt
214    pub const prefix = "$7$";
215
216    /// Standard type for a set of scrypt parameters, with the salt and hash.
217    pub fn HashResult(comptime crypt_max_hash_len: usize) type {
218        return struct {
219            ln: u6,
220            r: u30,
221            p: u30,
222            salt: []const u8,
223            hash: BinValue(crypt_max_hash_len),
224        };
225    }
226
227    const Codec = CustomB64Codec("./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".*);
228
229    /// A wrapped binary value whose maximum size is `max_len`.
230    ///
231    /// This type must be used whenever a binary value is encoded in a PHC-formatted string.
232    /// This includes `salt`, `hash`, and any other binary parameters such as keys.
233    ///
234    /// Once initialized, the actual value can be read with the `constSlice()` function.
235    pub fn BinValue(comptime max_len: usize) type {
236        return struct {
237            const Self = @This();
238            const capacity = max_len;
239            const max_encoded_length = Codec.encodedLen(max_len);
240
241            buf: [max_len]u8 = undefined,
242            len: usize = 0,
243
244            /// Wrap an existing byte slice
245            pub fn fromSlice(slice: []const u8) EncodingError!Self {
246                if (slice.len > capacity) return EncodingError.NoSpaceLeft;
247                var bin_value: Self = undefined;
248                @memcpy(bin_value.buf[0..slice.len], slice);
249                bin_value.len = slice.len;
250                return bin_value;
251            }
252
253            /// Return the slice containing the actual value.
254            pub fn constSlice(self: *const Self) []const u8 {
255                return self.buf[0..self.len];
256            }
257
258            fn fromB64(self: *Self, str: []const u8) !void {
259                const len = Codec.decodedLen(str.len);
260                if (len > self.buf.len) return EncodingError.NoSpaceLeft;
261                try Codec.decode(self.buf[0..len], str);
262                self.len = len;
263            }
264
265            fn toB64(self: *const Self, buf: []u8) ![]const u8 {
266                const value = self.constSlice();
267                const len = Codec.encodedLen(value.len);
268                if (len > buf.len) return EncodingError.NoSpaceLeft;
269                const encoded = buf[0..len];
270                Codec.encode(encoded, value);
271                return encoded;
272            }
273        };
274    }
275
276    /// Expand binary data into a salt for the modular crypt format.
277    pub fn saltFromBin(comptime len: usize, salt: [len]u8) [Codec.encodedLen(len)]u8 {
278        var buf: [Codec.encodedLen(len)]u8 = undefined;
279        Codec.encode(&buf, &salt);
280        return buf;
281    }
282
283    /// Deserialize a string into a structure `T` (matching `HashResult`).
284    pub fn deserialize(comptime T: type, str: []const u8) EncodingError!T {
285        var out: T = undefined;
286
287        if (str.len < 16) return EncodingError.InvalidEncoding;
288        if (!mem.eql(u8, prefix, str[0..3])) return EncodingError.InvalidEncoding;
289        out.ln = try Codec.intDecode(u6, str[3..4]);
290        out.r = try Codec.intDecode(u30, str[4..9]);
291        out.p = try Codec.intDecode(u30, str[9..14]);
292
293        var it = mem.splitScalar(u8, str[14..], '$');
294
295        const salt = it.first();
296        if (@hasField(T, "salt")) out.salt = salt;
297
298        const hash_str = it.next() orelse return EncodingError.InvalidEncoding;
299        if (@hasField(T, "hash")) try out.hash.fromB64(hash_str);
300
301        return out;
302    }
303
304    /// Serialize parameters into a string in modular crypt format.
305    pub fn serialize(params: anytype, str: []u8) EncodingError![]const u8 {
306        var w: std.Io.Writer = .fixed(str);
307        serializeTo(params, &w) catch |err| switch (err) {
308            error.WriteFailed => return error.NoSpaceLeft,
309            else => |e| return e,
310        };
311        return w.buffered();
312    }
313
314    /// Compute the number of bytes required to serialize `params`
315    pub fn calcSize(params: anytype) usize {
316        var trash: [128]u8 = undefined;
317        var d: std.Io.Writer.Discarding = .init(&trash);
318        serializeTo(params, &d.writer) catch unreachable;
319        return @intCast(d.fullCount());
320    }
321
322    fn serializeTo(params: anytype, w: *std.Io.Writer) !void {
323        var header: [14]u8 = undefined;
324        header[0..3].* = prefix.*;
325        Codec.intEncode(header[3..4], params.ln);
326        Codec.intEncode(header[4..9], params.r);
327        Codec.intEncode(header[9..14], params.p);
328        try w.writeAll(&header);
329        try w.writeAll(params.salt);
330        try w.writeAll("$");
331        var buf: [@TypeOf(params.hash).max_encoded_length]u8 = undefined;
332        const hash_str = try params.hash.toB64(&buf);
333        try w.writeAll(hash_str);
334    }
335
336    /// Custom codec that maps 6 bits into 8 like regular Base64, but uses its own alphabet,
337    /// encodes bits in little-endian, and can also encode integers.
338    fn CustomB64Codec(comptime map: [64]u8) type {
339        return struct {
340            const map64 = map;
341
342            fn encodedLen(len: usize) usize {
343                return (len * 4 + 2) / 3;
344            }
345
346            fn decodedLen(len: usize) usize {
347                return len / 4 * 3 + (len % 4) * 3 / 4;
348            }
349
350            fn intEncode(dst: []u8, src: anytype) void {
351                var n = src;
352                for (dst) |*x| {
353                    x.* = map64[@as(u6, @truncate(n))];
354                    n = math.shr(@TypeOf(src), n, 6);
355                }
356            }
357
358            fn intDecode(comptime T: type, src: *const [(@bitSizeOf(T) + 5) / 6]u8) !T {
359                var v: T = 0;
360                for (src, 0..) |x, i| {
361                    const vi = mem.indexOfScalar(u8, &map64, x) orelse return EncodingError.InvalidEncoding;
362                    v |= @as(T, @intCast(vi)) << @as(math.Log2Int(T), @intCast(i * 6));
363                }
364                return v;
365            }
366
367            fn decode(dst: []u8, src: []const u8) !void {
368                std.debug.assert(dst.len == decodedLen(src.len));
369                var i: usize = 0;
370                while (i < src.len / 4) : (i += 1) {
371                    mem.writeInt(u24, dst[i * 3 ..][0..3], try intDecode(u24, src[i * 4 ..][0..4]), .little);
372                }
373                const leftover = src[i * 4 ..];
374                var v: u24 = 0;
375                for (leftover, 0..) |_, j| {
376                    v |= @as(u24, try intDecode(u6, leftover[j..][0..1])) << @as(u5, @intCast(j * 6));
377                }
378                for (dst[i * 3 ..], 0..) |*x, j| {
379                    x.* = @as(u8, @truncate(v >> @as(u5, @intCast(j * 8))));
380                }
381            }
382
383            fn encode(dst: []u8, src: []const u8) void {
384                std.debug.assert(dst.len == encodedLen(src.len));
385                var i: usize = 0;
386                while (i < src.len / 3) : (i += 1) {
387                    intEncode(dst[i * 4 ..][0..4], mem.readInt(u24, src[i * 3 ..][0..3], .little));
388                }
389                const leftover = src[i * 3 ..];
390                var v: u24 = 0;
391                for (leftover, 0..) |x, j| {
392                    v |= @as(u24, x) << @as(u5, @intCast(j * 8));
393                }
394                intEncode(dst[i * 4 ..], v);
395            }
396        };
397    }
398};
399
400/// Hash and verify passwords using the PHC format.
401const PhcFormatHasher = struct {
402    const alg_id = "scrypt";
403    const BinValue = phc_format.BinValue;
404
405    const HashResult = struct {
406        alg_id: []const u8,
407        ln: u6,
408        r: u30,
409        p: u30,
410        salt: BinValue(max_salt_len),
411        hash: BinValue(max_hash_len),
412    };
413
414    /// Return a non-deterministic hash of the password encoded as a PHC-format string
415    pub fn create(
416        allocator: mem.Allocator,
417        password: []const u8,
418        params: Params,
419        buf: []u8,
420    ) HasherError![]const u8 {
421        var salt: [default_salt_len]u8 = undefined;
422        crypto.random.bytes(&salt);
423
424        var hash: [default_hash_len]u8 = undefined;
425        try kdf(allocator, &hash, password, &salt, params);
426
427        return phc_format.serialize(HashResult{
428            .alg_id = alg_id,
429            .ln = params.ln,
430            .r = params.r,
431            .p = params.p,
432            .salt = try BinValue(max_salt_len).fromSlice(&salt),
433            .hash = try BinValue(max_hash_len).fromSlice(&hash),
434        }, buf);
435    }
436
437    /// Verify a password against a PHC-format encoded string
438    pub fn verify(
439        allocator: mem.Allocator,
440        str: []const u8,
441        password: []const u8,
442    ) HasherError!void {
443        const hash_result = try phc_format.deserialize(HashResult, str);
444        if (!mem.eql(u8, hash_result.alg_id, alg_id)) return HasherError.PasswordVerificationFailed;
445        const params = Params{ .ln = hash_result.ln, .r = hash_result.r, .p = hash_result.p };
446        const expected_hash = hash_result.hash.constSlice();
447        var hash_buf: [max_hash_len]u8 = undefined;
448        if (expected_hash.len > hash_buf.len) return HasherError.InvalidEncoding;
449        const hash = hash_buf[0..expected_hash.len];
450        try kdf(allocator, hash, password, hash_result.salt.constSlice(), params);
451        if (!mem.eql(u8, hash, expected_hash)) return HasherError.PasswordVerificationFailed;
452    }
453};
454
455/// Hash and verify passwords using the modular crypt format.
456const CryptFormatHasher = struct {
457    const BinValue = crypt_format.BinValue;
458    const HashResult = crypt_format.HashResult(max_hash_len);
459
460    /// Length of a string returned by the create() function
461    pub const pwhash_str_length: usize = 101;
462
463    /// Return a non-deterministic hash of the password encoded into the modular crypt format
464    pub fn create(
465        allocator: mem.Allocator,
466        password: []const u8,
467        params: Params,
468        buf: []u8,
469    ) HasherError![]const u8 {
470        var salt_bin: [default_salt_len]u8 = undefined;
471        crypto.random.bytes(&salt_bin);
472        const salt = crypt_format.saltFromBin(salt_bin.len, salt_bin);
473
474        var hash: [default_hash_len]u8 = undefined;
475        try kdf(allocator, &hash, password, &salt, params);
476
477        return crypt_format.serialize(HashResult{
478            .ln = params.ln,
479            .r = params.r,
480            .p = params.p,
481            .salt = &salt,
482            .hash = try BinValue(max_hash_len).fromSlice(&hash),
483        }, buf);
484    }
485
486    /// Verify a password against a string in modular crypt format
487    pub fn verify(
488        allocator: mem.Allocator,
489        str: []const u8,
490        password: []const u8,
491    ) HasherError!void {
492        const hash_result = try crypt_format.deserialize(HashResult, str);
493        const params = Params{ .ln = hash_result.ln, .r = hash_result.r, .p = hash_result.p };
494        const expected_hash = hash_result.hash.constSlice();
495        var hash_buf: [max_hash_len]u8 = undefined;
496        if (expected_hash.len > hash_buf.len) return HasherError.InvalidEncoding;
497        const hash = hash_buf[0..expected_hash.len];
498        try kdf(allocator, hash, password, hash_result.salt, params);
499        if (!mem.eql(u8, hash, expected_hash)) return HasherError.PasswordVerificationFailed;
500    }
501};
502
503/// Options for hashing a password.
504///
505/// Allocator is required for scrypt.
506pub const HashOptions = struct {
507    allocator: ?mem.Allocator,
508    params: Params,
509    encoding: pwhash.Encoding,
510};
511
512/// Compute a hash of a password using the scrypt key derivation function.
513/// The function returns a string that includes all the parameters required for verification.
514pub fn strHash(
515    password: []const u8,
516    options: HashOptions,
517    out: []u8,
518) Error![]const u8 {
519    const allocator = options.allocator orelse return Error.AllocatorRequired;
520    switch (options.encoding) {
521        .phc => return PhcFormatHasher.create(allocator, password, options.params, out),
522        .crypt => return CryptFormatHasher.create(allocator, password, options.params, out),
523    }
524}
525
526/// Options for hash verification.
527///
528/// Allocator is required for scrypt.
529pub const VerifyOptions = struct {
530    allocator: ?mem.Allocator,
531};
532
533/// Verify that a previously computed hash is valid for a given password.
534pub fn strVerify(
535    str: []const u8,
536    password: []const u8,
537    options: VerifyOptions,
538) Error!void {
539    const allocator = options.allocator orelse return Error.AllocatorRequired;
540    if (mem.startsWith(u8, str, crypt_format.prefix)) {
541        return CryptFormatHasher.verify(allocator, str, password);
542    } else {
543        return PhcFormatHasher.verify(allocator, str, password);
544    }
545}
546
547// These tests take way too long to run, so I have disabled them.
548const run_long_tests = false;
549
550test "kdf" {
551    if (!run_long_tests) return error.SkipZigTest;
552
553    const password = "testpass";
554    const salt = "saltsalt";
555
556    var dk: [32]u8 = undefined;
557    try kdf(std.testing.allocator, &dk, password, salt, .{ .ln = 15, .r = 8, .p = 1 });
558
559    const hex = "1e0f97c3f6609024022fbe698da29c2fe53ef1087a8e396dc6d5d2a041e886de";
560    var bytes: [hex.len / 2]u8 = undefined;
561    _ = try fmt.hexToBytes(&bytes, hex);
562
563    try std.testing.expectEqualSlices(u8, &bytes, &dk);
564}
565
566test "kdf rfc 1" {
567    if (!run_long_tests) return error.SkipZigTest;
568
569    const password = "";
570    const salt = "";
571
572    var dk: [64]u8 = undefined;
573    try kdf(std.testing.allocator, &dk, password, salt, .{ .ln = 4, .r = 1, .p = 1 });
574
575    const hex = "77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906";
576    var bytes: [hex.len / 2]u8 = undefined;
577    _ = try fmt.hexToBytes(&bytes, hex);
578
579    try std.testing.expectEqualSlices(u8, &bytes, &dk);
580}
581
582test "kdf rfc 2" {
583    if (!run_long_tests) return error.SkipZigTest;
584
585    const password = "password";
586    const salt = "NaCl";
587
588    var dk: [64]u8 = undefined;
589    try kdf(std.testing.allocator, &dk, password, salt, .{ .ln = 10, .r = 8, .p = 16 });
590
591    const hex = "fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640";
592    var bytes: [hex.len / 2]u8 = undefined;
593    _ = try fmt.hexToBytes(&bytes, hex);
594
595    try std.testing.expectEqualSlices(u8, &bytes, &dk);
596}
597
598test "kdf rfc 3" {
599    if (!run_long_tests) return error.SkipZigTest;
600
601    const password = "pleaseletmein";
602    const salt = "SodiumChloride";
603
604    var dk: [64]u8 = undefined;
605    try kdf(std.testing.allocator, &dk, password, salt, .{ .ln = 14, .r = 8, .p = 1 });
606
607    const hex = "7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887";
608    var bytes: [hex.len / 2]u8 = undefined;
609    _ = try fmt.hexToBytes(&bytes, hex);
610
611    try std.testing.expectEqualSlices(u8, &bytes, &dk);
612}
613
614test "kdf rfc 4" {
615    if (!run_long_tests) return error.SkipZigTest;
616
617    const password = "pleaseletmein";
618    const salt = "SodiumChloride";
619
620    var dk: [64]u8 = undefined;
621    try kdf(std.testing.allocator, &dk, password, salt, .{ .ln = 20, .r = 8, .p = 1 });
622
623    const hex = "2101cb9b6a511aaeaddbbe09cf70f881ec568d574a2ffd4dabe5ee9820adaa478e56fd8f4ba5d09ffa1c6d927c40f4c337304049e8a952fbcbf45c6fa77a41a4";
624    var bytes: [hex.len / 2]u8 = undefined;
625    _ = try fmt.hexToBytes(&bytes, hex);
626
627    try std.testing.expectEqualSlices(u8, &bytes, &dk);
628}
629
630test "password hashing (crypt format)" {
631    if (!run_long_tests) return error.SkipZigTest;
632
633    const alloc = std.testing.allocator;
634
635    const str = "$7$A6....1....TrXs5Zk6s8sWHpQgWDIXTR8kUU3s6Jc3s.DtdS8M2i4$a4ik5hGDN7foMuHOW.cp.CtX01UyCeO0.JAG.AHPpx5";
636    const password = "Y0!?iQa9M%5ekffW(`";
637    try CryptFormatHasher.verify(alloc, str, password);
638
639    const params = Params.interactive;
640    var buf: [CryptFormatHasher.pwhash_str_length]u8 = undefined;
641    const str2 = try CryptFormatHasher.create(alloc, password, params, &buf);
642    try CryptFormatHasher.verify(alloc, str2, password);
643}
644
645test "strHash and strVerify" {
646    if (!run_long_tests) return error.SkipZigTest;
647
648    const alloc = std.testing.allocator;
649
650    const password = "testpass";
651    const params = Params.interactive;
652    const verify_options = VerifyOptions{ .allocator = alloc };
653    var buf: [128]u8 = undefined;
654
655    {
656        const str = try strHash(
657            password,
658            .{ .allocator = alloc, .params = params, .encoding = .crypt },
659            &buf,
660        );
661        try strVerify(str, password, verify_options);
662    }
663    {
664        const str = try strHash(
665            password,
666            .{ .allocator = alloc, .params = params, .encoding = .phc },
667            &buf,
668        );
669        try strVerify(str, password, verify_options);
670    }
671}
672
673test "unix-scrypt" {
674    if (!run_long_tests) return error.SkipZigTest;
675
676    const alloc = std.testing.allocator;
677
678    // https://gitlab.com/jas/scrypt-unix-crypt/blob/master/unix-scrypt.txt
679    {
680        const str = "$7$C6..../....SodiumChloride$kBGj9fHznVYFQMEn/qDCfrDevf9YDtcDdKvEqHJLV8D";
681        const password = "pleaseletmein";
682        try strVerify(str, password, .{ .allocator = alloc });
683    }
684    // one of the libsodium test vectors
685    {
686        const str = "$7$B6....1....75gBMAGwfFWZqBdyF3WdTQnWdUsuTiWjG1fF9c1jiSD$tc8RoB3.Em3/zNgMLWo2u00oGIoTyJv4fl3Fl8Tix72";
687        const password = "^T5H$JYt39n%K*j:W]!1s?vg!:jGi]Ax?..l7[p0v:1jHTpla9;]bUN;?bWyCbtqg nrDFal+Jxl3,2`#^tFSu%v_+7iYse8-cCkNf!tD=KrW)";
688        try strVerify(str, password, .{ .allocator = alloc });
689    }
690}
691
692test "crypt format" {
693    const str = "$7$C6..../....SodiumChloride$kBGj9fHznVYFQMEn/qDCfrDevf9YDtcDdKvEqHJLV8D";
694    const params = try crypt_format.deserialize(crypt_format.HashResult(32), str);
695    var buf: [str.len]u8 = undefined;
696    const s1 = try crypt_format.serialize(params, &buf);
697    try std.testing.expectEqualStrings(s1, str);
698}
699
700test "kdf fast" {
701    const TestVector = struct {
702        password: []const u8,
703        salt: []const u8,
704        params: Params,
705        want: []const u8,
706    };
707    const test_vectors = [_]TestVector{
708        .{
709            .password = "p",
710            .salt = "s",
711            .params = .{ .ln = 1, .r = 1, .p = 1 },
712            .want = &([_]u8{
713                0x48, 0xb0, 0xd2, 0xa8, 0xa3, 0x27, 0x26, 0x11,
714                0x98, 0x4c, 0x50, 0xeb, 0xd6, 0x30, 0xaf, 0x52,
715            }),
716        },
717    };
718    inline for (test_vectors) |v| {
719        var dk: [v.want.len]u8 = undefined;
720        try kdf(std.testing.allocator, &dk, v.password, v.salt, v.params);
721        try std.testing.expectEqualSlices(u8, &dk, v.want);
722    }
723}