master
  1// https://datatracker.ietf.org/doc/rfc9106
  2// https://github.com/golang/crypto/tree/master/argon2
  3// https://github.com/P-H-C/phc-winner-argon2
  4
  5const std = @import("std");
  6const builtin = @import("builtin");
  7
  8const blake2 = crypto.hash.blake2;
  9const crypto = std.crypto;
 10const math = std.math;
 11const mem = std.mem;
 12const phc_format = pwhash.phc_format;
 13const pwhash = crypto.pwhash;
 14
 15const Thread = std.Thread;
 16const Blake2b512 = blake2.Blake2b512;
 17const Blocks = std.array_list.AlignedManaged([block_length]u64, .@"16");
 18const H0 = [Blake2b512.digest_length + 8]u8;
 19
 20const EncodingError = crypto.errors.EncodingError;
 21const KdfError = pwhash.KdfError;
 22const HasherError = pwhash.HasherError;
 23const Error = pwhash.Error;
 24
 25const version = 0x13;
 26const block_length = 128;
 27const sync_points = 4;
 28const max_int = 0xffff_ffff;
 29
 30const default_salt_len = 32;
 31const default_hash_len = 32;
 32const max_salt_len = 64;
 33const max_hash_len = 64;
 34
 35/// Argon2 type
 36pub const Mode = enum {
 37    /// Argon2d is faster and uses data-depending memory access, which makes it highly resistant
 38    /// against GPU cracking attacks and suitable for applications with no threats from side-channel
 39    /// timing attacks (eg. cryptocurrencies).
 40    argon2d,
 41
 42    /// Argon2i instead uses data-independent memory access, which is preferred for password
 43    /// hashing and password-based key derivation, but it is slower as it makes more passes over
 44    /// the memory to protect from tradeoff attacks.
 45    argon2i,
 46
 47    /// Argon2id is a hybrid of Argon2i and Argon2d, using a combination of data-depending and
 48    /// data-independent memory accesses, which gives some of Argon2i's resistance to side-channel
 49    /// cache timing attacks and much of Argon2d's resistance to GPU cracking attacks.
 50    argon2id,
 51};
 52
 53/// Argon2 parameters
 54pub const Params = struct {
 55    const Self = @This();
 56
 57    /// A [t]ime cost, which defines the amount of computation realized and therefore the execution
 58    /// time, given in number of iterations.
 59    t: u32,
 60
 61    /// A [m]emory cost, which defines the memory usage, given in kibibytes.
 62    m: u32,
 63
 64    /// A [p]arallelism degree, which defines the number of parallel threads.
 65    p: u24,
 66
 67    /// The [secret] parameter, which is used for keyed hashing. This allows a secret key to be input
 68    /// at hashing time (from some external location) and be folded into the value of the hash. This
 69    /// means that even if your salts and hashes are compromised, an attacker cannot brute-force to
 70    /// find the password without the key.
 71    secret: ?[]const u8 = null,
 72
 73    /// The [ad] parameter, which is used to fold any additional data into the hash value. Functionally,
 74    /// this behaves almost exactly like the secret or salt parameters; the ad parameter is folding
 75    /// into the value of the hash. However, this parameter is used for different data. The salt
 76    /// should be a random string stored alongside your password. The secret should be a random key
 77    /// only usable at hashing time. The ad is for any other data.
 78    ad: ?[]const u8 = null,
 79
 80    /// Baseline parameters for interactive logins using argon2i type
 81    pub const interactive_2i = Self.fromLimits(4, 33554432);
 82    /// Baseline parameters for normal usage using argon2i type
 83    pub const moderate_2i = Self.fromLimits(6, 134217728);
 84    /// Baseline parameters for offline usage using argon2i type
 85    pub const sensitive_2i = Self.fromLimits(8, 536870912);
 86
 87    /// Baseline parameters for interactive logins using argon2id type
 88    pub const interactive_2id = Self.fromLimits(2, 67108864);
 89    /// Baseline parameters for normal usage using argon2id type
 90    pub const moderate_2id = Self.fromLimits(3, 268435456);
 91    /// Baseline parameters for offline usage using argon2id type
 92    pub const sensitive_2id = Self.fromLimits(4, 1073741824);
 93
 94    /// Recommended parameters for argon2id type according to the
 95    /// [OWASP cheat sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html).
 96    pub const owasp_2id = Self{ .t = 2, .m = 19 * 1024, .p = 1 };
 97
 98    /// Create parameters from ops and mem limits, where mem_limit given in bytes
 99    pub fn fromLimits(ops_limit: u32, mem_limit: usize) Self {
100        const m = mem_limit / 1024;
101        std.debug.assert(m <= max_int);
102        return .{ .t = ops_limit, .m = @as(u32, @intCast(m)), .p = 1 };
103    }
104};
105
106fn initHash(
107    password: []const u8,
108    salt: []const u8,
109    params: Params,
110    dk_len: usize,
111    mode: Mode,
112) H0 {
113    var h0: H0 = undefined;
114    var parameters: [24]u8 = undefined;
115    var tmp: [4]u8 = undefined;
116    var b2 = Blake2b512.init(.{});
117    mem.writeInt(u32, parameters[0..4], params.p, .little);
118    mem.writeInt(u32, parameters[4..8], @as(u32, @intCast(dk_len)), .little);
119    mem.writeInt(u32, parameters[8..12], params.m, .little);
120    mem.writeInt(u32, parameters[12..16], params.t, .little);
121    mem.writeInt(u32, parameters[16..20], version, .little);
122    mem.writeInt(u32, parameters[20..24], @intFromEnum(mode), .little);
123    b2.update(&parameters);
124    mem.writeInt(u32, &tmp, @as(u32, @intCast(password.len)), .little);
125    b2.update(&tmp);
126    b2.update(password);
127    mem.writeInt(u32, &tmp, @as(u32, @intCast(salt.len)), .little);
128    b2.update(&tmp);
129    b2.update(salt);
130    const secret = params.secret orelse "";
131    std.debug.assert(secret.len <= max_int);
132    mem.writeInt(u32, &tmp, @as(u32, @intCast(secret.len)), .little);
133    b2.update(&tmp);
134    b2.update(secret);
135    const ad = params.ad orelse "";
136    std.debug.assert(ad.len <= max_int);
137    mem.writeInt(u32, &tmp, @as(u32, @intCast(ad.len)), .little);
138    b2.update(&tmp);
139    b2.update(ad);
140    b2.final(h0[0..Blake2b512.digest_length]);
141    return h0;
142}
143
144fn blake2bLong(out: []u8, in: []const u8) void {
145    const H = Blake2b512;
146    var outlen_bytes: [4]u8 = undefined;
147    mem.writeInt(u32, &outlen_bytes, @as(u32, @intCast(out.len)), .little);
148
149    var out_buf: [H.digest_length]u8 = undefined;
150
151    if (out.len <= H.digest_length) {
152        var h = H.init(.{ .expected_out_bits = out.len * 8 });
153        h.update(&outlen_bytes);
154        h.update(in);
155        h.final(&out_buf);
156        @memcpy(out, out_buf[0..out.len]);
157        return;
158    }
159
160    var h = H.init(.{});
161    h.update(&outlen_bytes);
162    h.update(in);
163    h.final(&out_buf);
164    var out_slice = out;
165    out_slice[0 .. H.digest_length / 2].* = out_buf[0 .. H.digest_length / 2].*;
166    out_slice = out_slice[H.digest_length / 2 ..];
167
168    var in_buf: [H.digest_length]u8 = undefined;
169    while (out_slice.len > H.digest_length) {
170        in_buf = out_buf;
171        H.hash(&in_buf, &out_buf, .{});
172        out_slice[0 .. H.digest_length / 2].* = out_buf[0 .. H.digest_length / 2].*;
173        out_slice = out_slice[H.digest_length / 2 ..];
174    }
175    in_buf = out_buf;
176    H.hash(&in_buf, &out_buf, .{ .expected_out_bits = out_slice.len * 8 });
177    @memcpy(out_slice, out_buf[0..out_slice.len]);
178}
179
180fn initBlocks(
181    blocks: *Blocks,
182    h0: *H0,
183    memory: u32,
184    threads: u24,
185) void {
186    var block0: [1024]u8 = undefined;
187    var lane: u24 = 0;
188    while (lane < threads) : (lane += 1) {
189        const j = lane * (memory / threads);
190        mem.writeInt(u32, h0[Blake2b512.digest_length + 4 ..][0..4], lane, .little);
191
192        mem.writeInt(u32, h0[Blake2b512.digest_length..][0..4], 0, .little);
193        blake2bLong(&block0, h0);
194        for (&blocks.items[j + 0], 0..) |*v, i| {
195            v.* = mem.readInt(u64, block0[i * 8 ..][0..8], .little);
196        }
197
198        mem.writeInt(u32, h0[Blake2b512.digest_length..][0..4], 1, .little);
199        blake2bLong(&block0, h0);
200        for (&blocks.items[j + 1], 0..) |*v, i| {
201            v.* = mem.readInt(u64, block0[i * 8 ..][0..8], .little);
202        }
203    }
204}
205
206fn processBlocks(
207    allocator: mem.Allocator,
208    blocks: *Blocks,
209    time: u32,
210    memory: u32,
211    threads: u24,
212    mode: Mode,
213) KdfError!void {
214    const lanes = memory / threads;
215    const segments = lanes / sync_points;
216
217    if (builtin.single_threaded or threads == 1) {
218        processBlocksSt(blocks, time, memory, threads, mode, lanes, segments);
219    } else {
220        try processBlocksMt(allocator, blocks, time, memory, threads, mode, lanes, segments);
221    }
222}
223
224fn processBlocksSt(
225    blocks: *Blocks,
226    time: u32,
227    memory: u32,
228    threads: u24,
229    mode: Mode,
230    lanes: u32,
231    segments: u32,
232) void {
233    var n: u32 = 0;
234    while (n < time) : (n += 1) {
235        var slice: u32 = 0;
236        while (slice < sync_points) : (slice += 1) {
237            var lane: u24 = 0;
238            while (lane < threads) : (lane += 1) {
239                processSegment(blocks, time, memory, threads, mode, lanes, segments, n, slice, lane);
240            }
241        }
242    }
243}
244
245fn processBlocksMt(
246    allocator: mem.Allocator,
247    blocks: *Blocks,
248    time: u32,
249    memory: u32,
250    threads: u24,
251    mode: Mode,
252    lanes: u32,
253    segments: u32,
254) KdfError!void {
255    var threads_list = try std.array_list.Managed(Thread).initCapacity(allocator, threads);
256    defer threads_list.deinit();
257
258    var n: u32 = 0;
259    while (n < time) : (n += 1) {
260        var slice: u32 = 0;
261        while (slice < sync_points) : (slice += 1) {
262            var lane: u24 = 0;
263            while (lane < threads) : (lane += 1) {
264                const thread = try Thread.spawn(.{}, processSegment, .{
265                    blocks, time, memory, threads, mode, lanes, segments, n, slice, lane,
266                });
267                threads_list.appendAssumeCapacity(thread);
268            }
269            lane = 0;
270            while (lane < threads) : (lane += 1) {
271                threads_list.items[lane].join();
272            }
273            threads_list.clearRetainingCapacity();
274        }
275    }
276}
277
278fn processSegment(
279    blocks: *Blocks,
280    passes: u32,
281    memory: u32,
282    threads: u24,
283    mode: Mode,
284    lanes: u32,
285    segments: u32,
286    n: u32,
287    slice: u32,
288    lane: u24,
289) void {
290    var addresses align(16) = [_]u64{0} ** block_length;
291    var in align(16) = [_]u64{0} ** block_length;
292    const zero align(16) = [_]u64{0} ** block_length;
293    if (mode == .argon2i or (mode == .argon2id and n == 0 and slice < sync_points / 2)) {
294        in[0] = n;
295        in[1] = lane;
296        in[2] = slice;
297        in[3] = memory;
298        in[4] = passes;
299        in[5] = @intFromEnum(mode);
300    }
301    var index: u32 = 0;
302    if (n == 0 and slice == 0) {
303        index = 2;
304        if (mode == .argon2i or mode == .argon2id) {
305            in[6] += 1;
306            processBlock(&addresses, &in, &zero);
307            processBlock(&addresses, &addresses, &zero);
308        }
309    }
310    var offset = lane * lanes + slice * segments + index;
311    var random: u64 = 0;
312    while (index < segments) : ({
313        index += 1;
314        offset += 1;
315    }) {
316        var prev = offset -% 1;
317        if (index == 0 and slice == 0) {
318            prev +%= lanes;
319        }
320        if (mode == .argon2i or (mode == .argon2id and n == 0 and slice < sync_points / 2)) {
321            if (index % block_length == 0) {
322                in[6] += 1;
323                processBlock(&addresses, &in, &zero);
324                processBlock(&addresses, &addresses, &zero);
325            }
326            random = addresses[index % block_length];
327        } else {
328            random = blocks.items[prev][0];
329        }
330        const new_offset = indexAlpha(random, lanes, segments, threads, n, slice, lane, index);
331        processBlockXor(&blocks.items[offset], &blocks.items[prev], &blocks.items[new_offset]);
332    }
333}
334
335fn processBlock(
336    out: *align(16) [block_length]u64,
337    in1: *align(16) const [block_length]u64,
338    in2: *align(16) const [block_length]u64,
339) void {
340    processBlockGeneric(out, in1, in2, false);
341}
342
343fn processBlockXor(
344    out: *[block_length]u64,
345    in1: *const [block_length]u64,
346    in2: *const [block_length]u64,
347) void {
348    processBlockGeneric(out, in1, in2, true);
349}
350
351fn processBlockGeneric(
352    out: *[block_length]u64,
353    in1: *const [block_length]u64,
354    in2: *const [block_length]u64,
355    comptime xor: bool,
356) void {
357    var t: [block_length]u64 = undefined;
358    for (&t, 0..) |*v, i| {
359        v.* = in1[i] ^ in2[i];
360    }
361    var i: usize = 0;
362    while (i < block_length) : (i += 16) {
363        blamkaGeneric(t[i..][0..16]);
364    }
365    i = 0;
366    var buffer: [16]u64 = undefined;
367    while (i < block_length / 8) : (i += 2) {
368        var j: usize = 0;
369        while (j < block_length / 8) : (j += 2) {
370            buffer[j] = t[j * 8 + i];
371            buffer[j + 1] = t[j * 8 + i + 1];
372        }
373        blamkaGeneric(&buffer);
374        j = 0;
375        while (j < block_length / 8) : (j += 2) {
376            t[j * 8 + i] = buffer[j];
377            t[j * 8 + i + 1] = buffer[j + 1];
378        }
379    }
380    if (xor) {
381        for (t, 0..) |v, j| {
382            out[j] ^= in1[j] ^ in2[j] ^ v;
383        }
384    } else {
385        for (t, 0..) |v, j| {
386            out[j] = in1[j] ^ in2[j] ^ v;
387        }
388    }
389}
390
391const QuarterRound = struct { a: usize, b: usize, c: usize, d: usize };
392
393fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound {
394    return .{ .a = a, .b = b, .c = c, .d = d };
395}
396
397fn fBlaMka(x: u64, y: u64) u64 {
398    const xy = @as(u64, @as(u32, @truncate(x))) * @as(u64, @as(u32, @truncate(y)));
399    return x +% y +% 2 *% xy;
400}
401
402fn blamkaGeneric(x: *[16]u64) void {
403    const rounds = comptime [_]QuarterRound{
404        Rp(0, 4, 8, 12),
405        Rp(1, 5, 9, 13),
406        Rp(2, 6, 10, 14),
407        Rp(3, 7, 11, 15),
408        Rp(0, 5, 10, 15),
409        Rp(1, 6, 11, 12),
410        Rp(2, 7, 8, 13),
411        Rp(3, 4, 9, 14),
412    };
413    inline for (rounds) |r| {
414        x[r.a] = fBlaMka(x[r.a], x[r.b]);
415        x[r.d] = math.rotr(u64, x[r.d] ^ x[r.a], 32);
416        x[r.c] = fBlaMka(x[r.c], x[r.d]);
417        x[r.b] = math.rotr(u64, x[r.b] ^ x[r.c], 24);
418        x[r.a] = fBlaMka(x[r.a], x[r.b]);
419        x[r.d] = math.rotr(u64, x[r.d] ^ x[r.a], 16);
420        x[r.c] = fBlaMka(x[r.c], x[r.d]);
421        x[r.b] = math.rotr(u64, x[r.b] ^ x[r.c], 63);
422    }
423}
424
425fn finalize(
426    blocks: *Blocks,
427    memory: u32,
428    threads: u24,
429    out: []u8,
430) void {
431    const lanes = memory / threads;
432    var lane: u24 = 0;
433    while (lane < threads - 1) : (lane += 1) {
434        for (blocks.items[(lane * lanes) + lanes - 1], 0..) |v, i| {
435            blocks.items[memory - 1][i] ^= v;
436        }
437    }
438    var block: [1024]u8 = undefined;
439    for (blocks.items[memory - 1], 0..) |v, i| {
440        mem.writeInt(u64, block[i * 8 ..][0..8], v, .little);
441    }
442    blake2bLong(out, &block);
443}
444
445fn indexAlpha(
446    rand: u64,
447    lanes: u32,
448    segments: u32,
449    threads: u24,
450    n: u32,
451    slice: u32,
452    lane: u24,
453    index: u32,
454) u32 {
455    var ref_lane = @as(u32, @intCast(rand >> 32)) % threads;
456    if (n == 0 and slice == 0) {
457        ref_lane = lane;
458    }
459    var m = 3 * segments;
460    var s = ((slice + 1) % sync_points) * segments;
461    if (lane == ref_lane) {
462        m += index;
463    }
464    if (n == 0) {
465        m = slice * segments;
466        s = 0;
467        if (slice == 0 or lane == ref_lane) {
468            m += index;
469        }
470    }
471    if (index == 0 or lane == ref_lane) {
472        m -= 1;
473    }
474    var p = @as(u64, @as(u32, @truncate(rand)));
475    p = (p * p) >> 32;
476    p = (p * m) >> 32;
477    return ref_lane * lanes + @as(u32, @intCast(((s + m - (p + 1)) % lanes)));
478}
479
480/// Derives a key from the password, salt, and argon2 parameters.
481///
482/// Derived key has to be at least 4 bytes length.
483///
484/// Salt has to be at least 8 bytes length.
485pub fn kdf(
486    allocator: mem.Allocator,
487    derived_key: []u8,
488    password: []const u8,
489    salt: []const u8,
490    params: Params,
491    mode: Mode,
492) KdfError!void {
493    if (derived_key.len < 4) return KdfError.WeakParameters;
494    if (derived_key.len > max_int) return KdfError.OutputTooLong;
495
496    if (password.len > max_int) return KdfError.WeakParameters;
497    if (salt.len < 8 or salt.len > max_int) return KdfError.WeakParameters;
498    if (params.t < 1 or params.p < 1) return KdfError.WeakParameters;
499    if (params.m / 8 < params.p) return KdfError.WeakParameters;
500
501    var h0 = initHash(password, salt, params, derived_key.len, mode);
502    const memory = @max(
503        params.m / (sync_points * params.p) * (sync_points * params.p),
504        2 * sync_points * params.p,
505    );
506
507    var blocks = try Blocks.initCapacity(allocator, memory);
508    defer blocks.deinit();
509
510    blocks.appendNTimesAssumeCapacity(@splat(0), memory);
511
512    initBlocks(&blocks, &h0, memory, params.p);
513    try processBlocks(allocator, &blocks, params.t, memory, params.p, mode);
514    finalize(&blocks, memory, params.p, derived_key);
515}
516
517const PhcFormatHasher = struct {
518    const BinValue = phc_format.BinValue;
519
520    const HashResult = struct {
521        alg_id: []const u8,
522        alg_version: ?u32,
523        m: u32,
524        t: u32,
525        p: u24,
526        salt: BinValue(max_salt_len),
527        hash: BinValue(max_hash_len),
528    };
529
530    pub fn create(
531        allocator: mem.Allocator,
532        password: []const u8,
533        params: Params,
534        mode: Mode,
535        buf: []u8,
536    ) HasherError![]const u8 {
537        if (params.secret != null or params.ad != null) return HasherError.InvalidEncoding;
538
539        var salt: [default_salt_len]u8 = undefined;
540        crypto.random.bytes(&salt);
541
542        var hash: [default_hash_len]u8 = undefined;
543        try kdf(allocator, &hash, password, &salt, params, mode);
544
545        return phc_format.serialize(HashResult{
546            .alg_id = @tagName(mode),
547            .alg_version = version,
548            .m = params.m,
549            .t = params.t,
550            .p = params.p,
551            .salt = try BinValue(max_salt_len).fromSlice(&salt),
552            .hash = try BinValue(max_hash_len).fromSlice(&hash),
553        }, buf);
554    }
555
556    pub fn verify(
557        allocator: mem.Allocator,
558        str: []const u8,
559        password: []const u8,
560    ) HasherError!void {
561        const hash_result = try phc_format.deserialize(HashResult, str);
562
563        const mode = std.meta.stringToEnum(Mode, hash_result.alg_id) orelse
564            return HasherError.PasswordVerificationFailed;
565        if (hash_result.alg_version) |v| {
566            if (v != version) return HasherError.InvalidEncoding;
567        }
568        const params = Params{ .t = hash_result.t, .m = hash_result.m, .p = hash_result.p };
569
570        const expected_hash = hash_result.hash.constSlice();
571        var hash_buf: [max_hash_len]u8 = undefined;
572        if (expected_hash.len > hash_buf.len) return HasherError.InvalidEncoding;
573        const hash = hash_buf[0..expected_hash.len];
574
575        try kdf(allocator, hash, password, hash_result.salt.constSlice(), params, mode);
576        if (!mem.eql(u8, hash, expected_hash)) return HasherError.PasswordVerificationFailed;
577    }
578};
579
580/// Options for hashing a password.
581///
582/// Allocator is required for argon2.
583///
584/// Only phc encoding is supported.
585pub const HashOptions = struct {
586    allocator: ?mem.Allocator,
587    params: Params,
588    mode: Mode = .argon2id,
589    encoding: pwhash.Encoding = .phc,
590};
591
592/// Compute a hash of a password using the argon2 key derivation function.
593/// The function returns a string that includes all the parameters required for verification.
594pub fn strHash(
595    password: []const u8,
596    options: HashOptions,
597    out: []u8,
598) Error![]const u8 {
599    const allocator = options.allocator orelse return Error.AllocatorRequired;
600    switch (options.encoding) {
601        .phc => return PhcFormatHasher.create(
602            allocator,
603            password,
604            options.params,
605            options.mode,
606            out,
607        ),
608        .crypt => return Error.InvalidEncoding,
609    }
610}
611
612/// Options for hash verification.
613///
614/// Allocator is required for argon2.
615pub const VerifyOptions = struct {
616    allocator: ?mem.Allocator,
617};
618
619/// Verify that a previously computed hash is valid for a given password.
620pub fn strVerify(
621    str: []const u8,
622    password: []const u8,
623    options: VerifyOptions,
624) Error!void {
625    const allocator = options.allocator orelse return Error.AllocatorRequired;
626    return PhcFormatHasher.verify(allocator, str, password);
627}
628
629test "argon2d" {
630    const password = [_]u8{0x01} ** 32;
631    const salt = [_]u8{0x02} ** 16;
632    const secret = [_]u8{0x03} ** 8;
633    const ad = [_]u8{0x04} ** 12;
634
635    var dk: [32]u8 = undefined;
636    try kdf(
637        std.testing.allocator,
638        &dk,
639        &password,
640        &salt,
641        .{ .t = 3, .m = 32, .p = 4, .secret = &secret, .ad = &ad },
642        .argon2d,
643    );
644
645    const want = [_]u8{
646        0x51, 0x2b, 0x39, 0x1b, 0x6f, 0x11, 0x62, 0x97,
647        0x53, 0x71, 0xd3, 0x09, 0x19, 0x73, 0x42, 0x94,
648        0xf8, 0x68, 0xe3, 0xbe, 0x39, 0x84, 0xf3, 0xc1,
649        0xa1, 0x3a, 0x4d, 0xb9, 0xfa, 0xbe, 0x4a, 0xcb,
650    };
651    try std.testing.expectEqualSlices(u8, &dk, &want);
652}
653
654test "argon2i" {
655    const password = [_]u8{0x01} ** 32;
656    const salt = [_]u8{0x02} ** 16;
657    const secret = [_]u8{0x03} ** 8;
658    const ad = [_]u8{0x04} ** 12;
659
660    var dk: [32]u8 = undefined;
661    try kdf(
662        std.testing.allocator,
663        &dk,
664        &password,
665        &salt,
666        .{ .t = 3, .m = 32, .p = 4, .secret = &secret, .ad = &ad },
667        .argon2i,
668    );
669
670    const want = [_]u8{
671        0xc8, 0x14, 0xd9, 0xd1, 0xdc, 0x7f, 0x37, 0xaa,
672        0x13, 0xf0, 0xd7, 0x7f, 0x24, 0x94, 0xbd, 0xa1,
673        0xc8, 0xde, 0x6b, 0x01, 0x6d, 0xd3, 0x88, 0xd2,
674        0x99, 0x52, 0xa4, 0xc4, 0x67, 0x2b, 0x6c, 0xe8,
675    };
676    try std.testing.expectEqualSlices(u8, &dk, &want);
677}
678
679test "argon2id" {
680    const password = [_]u8{0x01} ** 32;
681    const salt = [_]u8{0x02} ** 16;
682    const secret = [_]u8{0x03} ** 8;
683    const ad = [_]u8{0x04} ** 12;
684
685    var dk: [32]u8 = undefined;
686    try kdf(
687        std.testing.allocator,
688        &dk,
689        &password,
690        &salt,
691        .{ .t = 3, .m = 32, .p = 4, .secret = &secret, .ad = &ad },
692        .argon2id,
693    );
694
695    const want = [_]u8{
696        0x0d, 0x64, 0x0d, 0xf5, 0x8d, 0x78, 0x76, 0x6c,
697        0x08, 0xc0, 0x37, 0xa3, 0x4a, 0x8b, 0x53, 0xc9,
698        0xd0, 0x1e, 0xf0, 0x45, 0x2d, 0x75, 0xb6, 0x5e,
699        0xb5, 0x25, 0x20, 0xe9, 0x6b, 0x01, 0xe6, 0x59,
700    };
701    try std.testing.expectEqualSlices(u8, &dk, &want);
702}
703
704test "kdf" {
705    const password = "password";
706    const salt = "somesalt";
707
708    const TestVector = struct {
709        mode: Mode,
710        time: u32,
711        memory: u32,
712        threads: u8,
713        hash: []const u8,
714    };
715    const test_vectors = [_]TestVector{
716        .{
717            .mode = .argon2i,
718            .time = 1,
719            .memory = 64,
720            .threads = 1,
721            .hash = "b9c401d1844a67d50eae3967dc28870b22e508092e861a37",
722        },
723        .{
724            .mode = .argon2d,
725            .time = 1,
726            .memory = 64,
727            .threads = 1,
728            .hash = "8727405fd07c32c78d64f547f24150d3f2e703a89f981a19",
729        },
730        .{
731            .mode = .argon2id,
732            .time = 1,
733            .memory = 64,
734            .threads = 1,
735            .hash = "655ad15eac652dc59f7170a7332bf49b8469be1fdb9c28bb",
736        },
737        .{
738            .mode = .argon2i,
739            .time = 2,
740            .memory = 64,
741            .threads = 1,
742            .hash = "8cf3d8f76a6617afe35fac48eb0b7433a9a670ca4a07ed64",
743        },
744        .{
745            .mode = .argon2d,
746            .time = 2,
747            .memory = 64,
748            .threads = 1,
749            .hash = "3be9ec79a69b75d3752acb59a1fbb8b295a46529c48fbb75",
750        },
751        .{
752            .mode = .argon2id,
753            .time = 2,
754            .memory = 64,
755            .threads = 1,
756            .hash = "068d62b26455936aa6ebe60060b0a65870dbfa3ddf8d41f7",
757        },
758        .{
759            .mode = .argon2i,
760            .time = 2,
761            .memory = 64,
762            .threads = 2,
763            .hash = "2089f3e78a799720f80af806553128f29b132cafe40d059f",
764        },
765        .{
766            .mode = .argon2d,
767            .time = 2,
768            .memory = 64,
769            .threads = 2,
770            .hash = "68e2462c98b8bc6bb60ec68db418ae2c9ed24fc6748a40e9",
771        },
772        .{
773            .mode = .argon2id,
774            .time = 2,
775            .memory = 64,
776            .threads = 2,
777            .hash = "350ac37222f436ccb5c0972f1ebd3bf6b958bf2071841362",
778        },
779        .{
780            .mode = .argon2i,
781            .time = 3,
782            .memory = 256,
783            .threads = 2,
784            .hash = "f5bbf5d4c3836af13193053155b73ec7476a6a2eb93fd5e6",
785        },
786        .{
787            .mode = .argon2d,
788            .time = 3,
789            .memory = 256,
790            .threads = 2,
791            .hash = "f4f0669218eaf3641f39cc97efb915721102f4b128211ef2",
792        },
793        .{
794            .mode = .argon2id,
795            .time = 3,
796            .memory = 256,
797            .threads = 2,
798            .hash = "4668d30ac4187e6878eedeacf0fd83c5a0a30db2cc16ef0b",
799        },
800        .{
801            .mode = .argon2i,
802            .time = 4,
803            .memory = 4096,
804            .threads = 4,
805            .hash = "a11f7b7f3f93f02ad4bddb59ab62d121e278369288a0d0e7",
806        },
807        .{
808            .mode = .argon2d,
809            .time = 4,
810            .memory = 4096,
811            .threads = 4,
812            .hash = "935598181aa8dc2b720914aa6435ac8d3e3a4210c5b0fb2d",
813        },
814        .{
815            .mode = .argon2id,
816            .time = 4,
817            .memory = 4096,
818            .threads = 4,
819            .hash = "145db9733a9f4ee43edf33c509be96b934d505a4efb33c5a",
820        },
821        .{
822            .mode = .argon2i,
823            .time = 4,
824            .memory = 1024,
825            .threads = 8,
826            .hash = "0cdd3956aa35e6b475a7b0c63488822f774f15b43f6e6e17",
827        },
828        .{
829            .mode = .argon2d,
830            .time = 4,
831            .memory = 1024,
832            .threads = 8,
833            .hash = "83604fc2ad0589b9d055578f4d3cc55bc616df3578a896e9",
834        },
835        .{
836            .mode = .argon2id,
837            .time = 4,
838            .memory = 1024,
839            .threads = 8,
840            .hash = "8dafa8e004f8ea96bf7c0f93eecf67a6047476143d15577f",
841        },
842        .{
843            .mode = .argon2i,
844            .time = 2,
845            .memory = 64,
846            .threads = 3,
847            .hash = "5cab452fe6b8479c8661def8cd703b611a3905a6d5477fe6",
848        },
849        .{
850            .mode = .argon2d,
851            .time = 2,
852            .memory = 64,
853            .threads = 3,
854            .hash = "22474a423bda2ccd36ec9afd5119e5c8949798cadf659f51",
855        },
856        .{
857            .mode = .argon2id,
858            .time = 2,
859            .memory = 64,
860            .threads = 3,
861            .hash = "4a15b31aec7c2590b87d1f520be7d96f56658172deaa3079",
862        },
863        .{
864            .mode = .argon2i,
865            .time = 3,
866            .memory = 1024,
867            .threads = 6,
868            .hash = "d236b29c2b2a09babee842b0dec6aa1e83ccbdea8023dced",
869        },
870        .{
871            .mode = .argon2d,
872            .time = 3,
873            .memory = 1024,
874            .threads = 6,
875            .hash = "a3351b0319a53229152023d9206902f4ef59661cdca89481",
876        },
877        .{
878            .mode = .argon2id,
879            .time = 3,
880            .memory = 1024,
881            .threads = 6,
882            .hash = "1640b932f4b60e272f5d2207b9a9c626ffa1bd88d2349016",
883        },
884    };
885    for (test_vectors) |v| {
886        var want: [24]u8 = undefined;
887        _ = try std.fmt.hexToBytes(&want, v.hash);
888
889        var dk: [24]u8 = undefined;
890        try kdf(
891            std.testing.allocator,
892            &dk,
893            password,
894            salt,
895            .{ .t = v.time, .m = v.memory, .p = v.threads },
896            v.mode,
897        );
898
899        try std.testing.expectEqualSlices(u8, &dk, &want);
900    }
901}
902
903test "phc format hasher" {
904    const allocator = std.testing.allocator;
905    const password = "testpass";
906
907    var buf: [128]u8 = undefined;
908    const hash = try PhcFormatHasher.create(
909        allocator,
910        password,
911        .{ .t = 3, .m = 32, .p = 4 },
912        .argon2id,
913        &buf,
914    );
915    try PhcFormatHasher.verify(allocator, hash, password);
916}
917
918test "password hash and password verify" {
919    const allocator = std.testing.allocator;
920    const password = "testpass";
921
922    var buf: [128]u8 = undefined;
923    const hash = try strHash(
924        password,
925        .{ .allocator = allocator, .params = .{ .t = 3, .m = 32, .p = 4 } },
926        &buf,
927    );
928    try strVerify(hash, password, .{ .allocator = allocator });
929}
930
931test "kdf derived key length" {
932    const allocator = std.testing.allocator;
933
934    const password = "testpass";
935    const salt = "saltsalt";
936    const params = Params{ .t = 3, .m = 32, .p = 4 };
937    const mode = Mode.argon2id;
938
939    var dk1: [11]u8 = undefined;
940    try kdf(allocator, &dk1, password, salt, params, mode);
941
942    var dk2: [77]u8 = undefined;
943    try kdf(allocator, &dk2, password, salt, params, mode);
944
945    var dk3: [111]u8 = undefined;
946    try kdf(allocator, &dk3, password, salt, params, mode);
947}