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 builtin = @import("builtin");
6
7const std = @import("std");
8const blake2 = crypto.hash.blake2;
9const crypto = std.crypto;
10const Io = std.Io;
11const math = std.math;
12const mem = std.mem;
13const phc_format = pwhash.phc_format;
14const pwhash = crypto.pwhash;
15const Blake2b512 = blake2.Blake2b512;
16const Blocks = std.array_list.AlignedManaged([block_length]u64, .@"16");
17const H0 = [Blake2b512.digest_length + 8]u8;
18
19const EncodingError = crypto.errors.EncodingError;
20const KdfError = pwhash.KdfError;
21const HasherError = pwhash.HasherError;
22const Error = pwhash.Error;
23
24const version = 0x13;
25const block_length = 128;
26const sync_points = 4;
27const max_int = 0xffff_ffff;
28
29const default_salt_len = 32;
30const default_hash_len = 32;
31const max_salt_len = 64;
32const max_hash_len = 64;
33
34/// Argon2 type
35pub const Mode = enum {
36 /// Argon2d is faster and uses data-depending memory access, which makes it highly resistant
37 /// against GPU cracking attacks and suitable for applications with no threats from side-channel
38 /// timing attacks (eg. cryptocurrencies).
39 argon2d,
40
41 /// Argon2i instead uses data-independent memory access, which is preferred for password
42 /// hashing and password-based key derivation, but it is slower as it makes more passes over
43 /// the memory to protect from tradeoff attacks.
44 argon2i,
45
46 /// Argon2id is a hybrid of Argon2i and Argon2d, using a combination of data-depending and
47 /// data-independent memory accesses, which gives some of Argon2i's resistance to side-channel
48 /// cache timing attacks and much of Argon2d's resistance to GPU cracking attacks.
49 argon2id,
50};
51
52/// Argon2 parameters
53pub const Params = struct {
54 const Self = @This();
55
56 /// Time cost, which defines the amount of computation realized and therefore the execution
57 /// time, given in number of iterations.
58 t: u32,
59
60 /// Memory cost, which defines the memory usage, given in kibibytes.
61 m: u32,
62
63 /// Parallelism degree, which defines the number of independent tasks,
64 /// to be multiplexed onto threads when possible.
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], @backingInt(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 blocks: *Blocks,
208 time: u32,
209 memory: u32,
210 threads: u24,
211 mode: Mode,
212 io: Io,
213) Io.Cancelable!void {
214 const lanes = memory / threads;
215 const segments = lanes / sync_points;
216
217 if (builtin.single_threaded or threads == 1) {
218 processBlocksSync(blocks, time, memory, threads, mode, lanes, segments);
219 } else {
220 try processBlocksAsync(blocks, time, memory, threads, mode, lanes, segments, io);
221 }
222}
223
224fn processBlocksSync(
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 processBlocksAsync(
246 blocks: *Blocks,
247 time: u32,
248 memory: u32,
249 threads: u24,
250 mode: Mode,
251 lanes: u32,
252 segments: u32,
253 io: Io,
254) Io.Cancelable!void {
255 var n: u32 = 0;
256 while (n < time) : (n += 1) {
257 var slice: u32 = 0;
258 while (slice < sync_points) : (slice += 1) {
259 var group: Io.Group = .init;
260 defer group.cancel(io);
261 var lane: u24 = 0;
262 while (lane < threads) : (lane += 1) {
263 group.async(io, processSegment, .{
264 blocks, time, memory, threads, mode, lanes, segments, n, slice, lane,
265 });
266 }
267 try group.await(io);
268 }
269 }
270}
271
272fn processSegment(
273 blocks: *Blocks,
274 passes: u32,
275 memory: u32,
276 threads: u24,
277 mode: Mode,
278 lanes: u32,
279 segments: u32,
280 n: u32,
281 slice: u32,
282 lane: u24,
283) void {
284 var addresses: [block_length]u64 align(16) = @splat(0);
285 var in: [block_length]u64 align(16) = @splat(0);
286 const zero: [block_length]u64 align(16) = @splat(0);
287 if (mode == .argon2i or (mode == .argon2id and n == 0 and slice < sync_points / 2)) {
288 in[0] = n;
289 in[1] = lane;
290 in[2] = slice;
291 in[3] = memory;
292 in[4] = passes;
293 in[5] = @backingInt(mode);
294 }
295 var index: u32 = 0;
296 if (n == 0 and slice == 0) {
297 index = 2;
298 if (mode == .argon2i or mode == .argon2id) {
299 in[6] += 1;
300 processBlock(&addresses, &in, &zero);
301 processBlock(&addresses, &addresses, &zero);
302 }
303 }
304 var offset = lane * lanes + slice * segments + index;
305 var random: u64 = 0;
306 while (index < segments) : ({
307 index += 1;
308 offset += 1;
309 }) {
310 var prev = offset -% 1;
311 if (index == 0 and slice == 0) {
312 prev +%= lanes;
313 }
314 if (mode == .argon2i or (mode == .argon2id and n == 0 and slice < sync_points / 2)) {
315 if (index % block_length == 0) {
316 in[6] += 1;
317 processBlock(&addresses, &in, &zero);
318 processBlock(&addresses, &addresses, &zero);
319 }
320 random = addresses[index % block_length];
321 } else {
322 random = blocks.items[prev][0];
323 }
324 const new_offset = indexAlpha(random, lanes, segments, threads, n, slice, lane, index);
325 processBlockXor(&blocks.items[offset], &blocks.items[prev], &blocks.items[new_offset]);
326 }
327}
328
329fn processBlock(
330 out: *align(16) [block_length]u64,
331 in1: *align(16) const [block_length]u64,
332 in2: *align(16) const [block_length]u64,
333) void {
334 processBlockGeneric(out, in1, in2, false);
335}
336
337fn processBlockXor(
338 out: *[block_length]u64,
339 in1: *const [block_length]u64,
340 in2: *const [block_length]u64,
341) void {
342 processBlockGeneric(out, in1, in2, true);
343}
344
345fn processBlockGeneric(
346 out: *[block_length]u64,
347 in1: *const [block_length]u64,
348 in2: *const [block_length]u64,
349 comptime xor: bool,
350) void {
351 var t: [block_length]u64 = undefined;
352 for (&t, 0..) |*v, i| {
353 v.* = in1[i] ^ in2[i];
354 }
355 var i: usize = 0;
356 while (i < block_length) : (i += 16) {
357 blamkaGeneric(t[i..][0..16]);
358 }
359 i = 0;
360 var buffer: [16]u64 = undefined;
361 while (i < block_length / 8) : (i += 2) {
362 var j: usize = 0;
363 while (j < block_length / 8) : (j += 2) {
364 buffer[j] = t[j * 8 + i];
365 buffer[j + 1] = t[j * 8 + i + 1];
366 }
367 blamkaGeneric(&buffer);
368 j = 0;
369 while (j < block_length / 8) : (j += 2) {
370 t[j * 8 + i] = buffer[j];
371 t[j * 8 + i + 1] = buffer[j + 1];
372 }
373 }
374 if (xor) {
375 for (t, 0..) |v, j| {
376 out[j] ^= in1[j] ^ in2[j] ^ v;
377 }
378 } else {
379 for (t, 0..) |v, j| {
380 out[j] = in1[j] ^ in2[j] ^ v;
381 }
382 }
383}
384
385const QuarterRound = struct { a: usize, b: usize, c: usize, d: usize };
386
387fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound {
388 return .{ .a = a, .b = b, .c = c, .d = d };
389}
390
391fn fBlaMka(x: u64, y: u64) u64 {
392 const xy = @as(u64, @as(u32, @truncate(x))) * @as(u64, @as(u32, @truncate(y)));
393 return x +% y +% 2 *% xy;
394}
395
396fn blamkaGeneric(x: *[16]u64) void {
397 const rounds = comptime [_]QuarterRound{
398 Rp(0, 4, 8, 12),
399 Rp(1, 5, 9, 13),
400 Rp(2, 6, 10, 14),
401 Rp(3, 7, 11, 15),
402 Rp(0, 5, 10, 15),
403 Rp(1, 6, 11, 12),
404 Rp(2, 7, 8, 13),
405 Rp(3, 4, 9, 14),
406 };
407 inline for (rounds) |r| {
408 x[r.a] = fBlaMka(x[r.a], x[r.b]);
409 x[r.d] = math.rotr(u64, x[r.d] ^ x[r.a], 32);
410 x[r.c] = fBlaMka(x[r.c], x[r.d]);
411 x[r.b] = math.rotr(u64, x[r.b] ^ x[r.c], 24);
412 x[r.a] = fBlaMka(x[r.a], x[r.b]);
413 x[r.d] = math.rotr(u64, x[r.d] ^ x[r.a], 16);
414 x[r.c] = fBlaMka(x[r.c], x[r.d]);
415 x[r.b] = math.rotr(u64, x[r.b] ^ x[r.c], 63);
416 }
417}
418
419fn finalize(
420 blocks: *Blocks,
421 memory: u32,
422 threads: u24,
423 out: []u8,
424) void {
425 const lanes = memory / threads;
426 var lane: u24 = 0;
427 while (lane < threads - 1) : (lane += 1) {
428 for (blocks.items[(lane * lanes) + lanes - 1], 0..) |v, i| {
429 blocks.items[memory - 1][i] ^= v;
430 }
431 }
432 var block: [1024]u8 = undefined;
433 for (blocks.items[memory - 1], 0..) |v, i| {
434 mem.writeInt(u64, block[i * 8 ..][0..8], v, .little);
435 }
436 blake2bLong(out, &block);
437}
438
439fn indexAlpha(
440 rand: u64,
441 lanes: u32,
442 segments: u32,
443 threads: u24,
444 n: u32,
445 slice: u32,
446 lane: u24,
447 index: u32,
448) u32 {
449 var ref_lane = @as(u32, @intCast(rand >> 32)) % threads;
450 if (n == 0 and slice == 0) {
451 ref_lane = lane;
452 }
453 var m = 3 * segments;
454 var s = ((slice + 1) % sync_points) * segments;
455 if (lane == ref_lane) {
456 m += index;
457 }
458 if (n == 0) {
459 m = slice * segments;
460 s = 0;
461 if (slice == 0 or lane == ref_lane) {
462 m += index;
463 }
464 }
465 if (index == 0 or lane == ref_lane) {
466 m -= 1;
467 }
468 var p = @as(u64, @as(u32, @truncate(rand)));
469 p = (p * p) >> 32;
470 p = (p * m) >> 32;
471 return ref_lane * lanes + @as(u32, @intCast(((s + m - (p + 1)) % lanes)));
472}
473
474fn blockCount(params: Params) u32 {
475 return @max(
476 params.m / (sync_points * params.p) * (sync_points * params.p),
477 2 * sync_points * params.p,
478 );
479}
480
481/// Compute the number of bytes of scratch memory `kdf` needs for `params`.
482pub fn calcSize(params: Params) usize {
483 return blockCount(params) * @sizeOf([block_length]u64);
484}
485
486/// Derives a key from the password, salt, and argon2 parameters.
487///
488/// Derived key has to be at least 4 bytes length.
489///
490/// Salt has to be at least 8 bytes length.
491pub fn kdf(
492 allocator: mem.Allocator,
493 derived_key: []u8,
494 password: []const u8,
495 salt: []const u8,
496 params: Params,
497 mode: Mode,
498 io: Io,
499) KdfError!void {
500 if (derived_key.len < 4) return KdfError.WeakParameters;
501 if (derived_key.len > max_int) return KdfError.OutputTooLong;
502
503 if (password.len > max_int) return KdfError.WeakParameters;
504 if (salt.len < 8 or salt.len > max_int) return KdfError.WeakParameters;
505 if (params.t < 1 or params.p < 1) return KdfError.WeakParameters;
506 if (params.m / 8 < params.p) return KdfError.WeakParameters;
507
508 var h0 = initHash(password, salt, params, derived_key.len, mode);
509 const memory = blockCount(params);
510
511 var blocks = try Blocks.initCapacity(allocator, memory);
512 defer blocks.deinit();
513
514 blocks.appendNTimesAssumeCapacity(@splat(0), memory);
515
516 initBlocks(&blocks, &h0, memory, params.p);
517 try processBlocks(&blocks, params.t, memory, params.p, mode, io);
518 finalize(&blocks, memory, params.p, derived_key);
519}
520
521const PhcFormatHasher = struct {
522 const BinValue = phc_format.BinValue;
523
524 const HashResult = struct {
525 alg_id: []const u8,
526 alg_version: ?u32,
527 m: u32,
528 t: u32,
529 p: u24,
530 salt: BinValue(max_salt_len),
531 hash: BinValue(max_hash_len),
532 };
533
534 pub fn create(
535 allocator: mem.Allocator,
536 password: []const u8,
537 params: Params,
538 mode: Mode,
539 buf: []u8,
540 io: Io,
541 ) HasherError![]const u8 {
542 if (params.secret != null or params.ad != null) return HasherError.InvalidEncoding;
543
544 var salt: [default_salt_len]u8 = undefined;
545 io.random(&salt);
546
547 var hash: [default_hash_len]u8 = undefined;
548 try kdf(allocator, &hash, password, &salt, params, mode, io);
549
550 return phc_format.serialize(HashResult{
551 .alg_id = @tagName(mode),
552 .alg_version = version,
553 .m = params.m,
554 .t = params.t,
555 .p = params.p,
556 .salt = try BinValue(max_salt_len).fromSlice(&salt),
557 .hash = try BinValue(max_hash_len).fromSlice(&hash),
558 }, buf);
559 }
560
561 pub fn verify(
562 allocator: mem.Allocator,
563 str: []const u8,
564 password: []const u8,
565 io: Io,
566 ) HasherError!void {
567 const hash_result = try phc_format.deserialize(HashResult, str);
568
569 const mode = std.meta.stringToEnum(Mode, hash_result.alg_id) orelse
570 return HasherError.PasswordVerificationFailed;
571 if (hash_result.alg_version) |v| {
572 if (v != version) return HasherError.InvalidEncoding;
573 }
574 const params = Params{ .t = hash_result.t, .m = hash_result.m, .p = hash_result.p };
575
576 const expected_hash = hash_result.hash.constSlice();
577 var hash_buf: [max_hash_len]u8 = undefined;
578 if (expected_hash.len > hash_buf.len) return HasherError.InvalidEncoding;
579 const hash = hash_buf[0..expected_hash.len];
580
581 try kdf(allocator, hash, password, hash_result.salt.constSlice(), params, mode, io);
582 if (!mem.eql(u8, hash, expected_hash)) return HasherError.PasswordVerificationFailed;
583 }
584};
585
586/// Options for hashing a password.
587///
588/// Allocator is required for argon2.
589///
590/// Only phc encoding is supported.
591pub const HashOptions = struct {
592 allocator: ?mem.Allocator,
593 params: Params,
594 mode: Mode = .argon2id,
595 encoding: pwhash.Encoding = .phc,
596};
597
598/// Compute a hash of a password using the argon2 key derivation function.
599/// The function returns a string that includes all the parameters required for verification.
600pub fn strHash(
601 password: []const u8,
602 options: HashOptions,
603 out: []u8,
604 io: Io,
605) Error![]const u8 {
606 const allocator = options.allocator orelse return Error.AllocatorRequired;
607 switch (options.encoding) {
608 .phc => return PhcFormatHasher.create(
609 allocator,
610 password,
611 options.params,
612 options.mode,
613 out,
614 io,
615 ),
616 .crypt => return Error.InvalidEncoding,
617 }
618}
619
620/// Options for hash verification.
621pub const VerifyOptions = struct {
622 allocator: mem.Allocator,
623};
624
625/// Verify that a previously computed hash is valid for a given password.
626pub fn strVerify(
627 str: []const u8,
628 password: []const u8,
629 options: VerifyOptions,
630 io: Io,
631) Error!void {
632 return PhcFormatHasher.verify(options.allocator, str, password, io);
633}
634
635test "argon2d" {
636 if (true) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/30074
637
638 const password: [32]u8 = @splat(0x01);
639 const salt: [16]u8 = @splat(0x02);
640 const secret: [8]u8 = @splat(0x03);
641 const ad: [12]u8 = @splat(0x04);
642
643 var dk: [32]u8 = undefined;
644 try kdf(
645 std.testing.allocator,
646 &dk,
647 &password,
648 &salt,
649 .{ .t = 3, .m = 32, .p = 4, .secret = &secret, .ad = &ad },
650 .argon2d,
651 std.testing.io,
652 );
653
654 const want = [_]u8{
655 0x51, 0x2b, 0x39, 0x1b, 0x6f, 0x11, 0x62, 0x97,
656 0x53, 0x71, 0xd3, 0x09, 0x19, 0x73, 0x42, 0x94,
657 0xf8, 0x68, 0xe3, 0xbe, 0x39, 0x84, 0xf3, 0xc1,
658 0xa1, 0x3a, 0x4d, 0xb9, 0xfa, 0xbe, 0x4a, 0xcb,
659 };
660 try std.testing.expectEqualSlices(u8, &dk, &want);
661}
662
663test "argon2i" {
664 const password: [32]u8 = @splat(0x01);
665 const salt: [16]u8 = @splat(0x02);
666 const secret: [8]u8 = @splat(0x03);
667 const ad: [12]u8 = @splat(0x04);
668
669 var dk: [32]u8 = undefined;
670 try kdf(
671 std.testing.allocator,
672 &dk,
673 &password,
674 &salt,
675 .{ .t = 3, .m = 32, .p = 4, .secret = &secret, .ad = &ad },
676 .argon2i,
677 std.testing.io,
678 );
679
680 const want = [_]u8{
681 0xc8, 0x14, 0xd9, 0xd1, 0xdc, 0x7f, 0x37, 0xaa,
682 0x13, 0xf0, 0xd7, 0x7f, 0x24, 0x94, 0xbd, 0xa1,
683 0xc8, 0xde, 0x6b, 0x01, 0x6d, 0xd3, 0x88, 0xd2,
684 0x99, 0x52, 0xa4, 0xc4, 0x67, 0x2b, 0x6c, 0xe8,
685 };
686 try std.testing.expectEqualSlices(u8, &dk, &want);
687}
688
689test "argon2id" {
690 const password: [32]u8 = @splat(0x01);
691 const salt: [16]u8 = @splat(0x02);
692 const secret: [8]u8 = @splat(0x03);
693 const ad: [12]u8 = @splat(0x04);
694
695 var dk: [32]u8 = undefined;
696 try kdf(
697 std.testing.allocator,
698 &dk,
699 &password,
700 &salt,
701 .{ .t = 3, .m = 32, .p = 4, .secret = &secret, .ad = &ad },
702 .argon2id,
703 std.testing.io,
704 );
705
706 const want = [_]u8{
707 0x0d, 0x64, 0x0d, 0xf5, 0x8d, 0x78, 0x76, 0x6c,
708 0x08, 0xc0, 0x37, 0xa3, 0x4a, 0x8b, 0x53, 0xc9,
709 0xd0, 0x1e, 0xf0, 0x45, 0x2d, 0x75, 0xb6, 0x5e,
710 0xb5, 0x25, 0x20, 0xe9, 0x6b, 0x01, 0xe6, 0x59,
711 };
712 try std.testing.expectEqualSlices(u8, &dk, &want);
713}
714
715test "kdf" {
716 if (true) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/31402
717
718 const password = "password";
719 const salt = "somesalt";
720
721 const TestVector = struct {
722 mode: Mode,
723 time: u32,
724 memory: u32,
725 threads: u8,
726 hash: []const u8,
727 };
728 const test_vectors = [_]TestVector{
729 .{
730 .mode = .argon2i,
731 .time = 1,
732 .memory = 64,
733 .threads = 1,
734 .hash = "b9c401d1844a67d50eae3967dc28870b22e508092e861a37",
735 },
736 .{
737 .mode = .argon2d,
738 .time = 1,
739 .memory = 64,
740 .threads = 1,
741 .hash = "8727405fd07c32c78d64f547f24150d3f2e703a89f981a19",
742 },
743 .{
744 .mode = .argon2id,
745 .time = 1,
746 .memory = 64,
747 .threads = 1,
748 .hash = "655ad15eac652dc59f7170a7332bf49b8469be1fdb9c28bb",
749 },
750 .{
751 .mode = .argon2i,
752 .time = 2,
753 .memory = 64,
754 .threads = 1,
755 .hash = "8cf3d8f76a6617afe35fac48eb0b7433a9a670ca4a07ed64",
756 },
757 .{
758 .mode = .argon2d,
759 .time = 2,
760 .memory = 64,
761 .threads = 1,
762 .hash = "3be9ec79a69b75d3752acb59a1fbb8b295a46529c48fbb75",
763 },
764 .{
765 .mode = .argon2id,
766 .time = 2,
767 .memory = 64,
768 .threads = 1,
769 .hash = "068d62b26455936aa6ebe60060b0a65870dbfa3ddf8d41f7",
770 },
771 .{
772 .mode = .argon2i,
773 .time = 2,
774 .memory = 64,
775 .threads = 2,
776 .hash = "2089f3e78a799720f80af806553128f29b132cafe40d059f",
777 },
778 .{
779 .mode = .argon2d,
780 .time = 2,
781 .memory = 64,
782 .threads = 2,
783 .hash = "68e2462c98b8bc6bb60ec68db418ae2c9ed24fc6748a40e9",
784 },
785 .{
786 .mode = .argon2id,
787 .time = 2,
788 .memory = 64,
789 .threads = 2,
790 .hash = "350ac37222f436ccb5c0972f1ebd3bf6b958bf2071841362",
791 },
792 .{
793 .mode = .argon2i,
794 .time = 3,
795 .memory = 256,
796 .threads = 2,
797 .hash = "f5bbf5d4c3836af13193053155b73ec7476a6a2eb93fd5e6",
798 },
799 .{
800 .mode = .argon2d,
801 .time = 3,
802 .memory = 256,
803 .threads = 2,
804 .hash = "f4f0669218eaf3641f39cc97efb915721102f4b128211ef2",
805 },
806 .{
807 .mode = .argon2id,
808 .time = 3,
809 .memory = 256,
810 .threads = 2,
811 .hash = "4668d30ac4187e6878eedeacf0fd83c5a0a30db2cc16ef0b",
812 },
813 .{
814 .mode = .argon2i,
815 .time = 4,
816 .memory = 256,
817 .threads = 4,
818 .hash = "f7dbbacbf16999e3700817a7e06f65a8db2e9fa9504ede4c",
819 },
820 .{
821 .mode = .argon2d,
822 .time = 4,
823 .memory = 256,
824 .threads = 4,
825 .hash = "ea2970501cf49faa5ba1d2e6370204e9b57ca90a8fea937b",
826 },
827 .{
828 .mode = .argon2id,
829 .time = 4,
830 .memory = 256,
831 .threads = 4,
832 .hash = "fbd40d5a8cb92f88c20bda4b3cdb1f9d5af1efa937032410",
833 },
834 .{
835 .mode = .argon2i,
836 .time = 4,
837 .memory = 256,
838 .threads = 8,
839 .hash = "15d3c398364e53f68fd12d19baf3f21432d964254fe27467",
840 },
841 .{
842 .mode = .argon2d,
843 .time = 4,
844 .memory = 256,
845 .threads = 8,
846 .hash = "23c9adc06f06e21e4612c1466a1be02627690932b02c0df0",
847 },
848 .{
849 .mode = .argon2id,
850 .time = 4,
851 .memory = 256,
852 .threads = 8,
853 .hash = "f22802f8ca47be93f9954e4ce20c1e944e938fbd4a125d9d",
854 },
855 .{
856 .mode = .argon2i,
857 .time = 2,
858 .memory = 64,
859 .threads = 3,
860 .hash = "5cab452fe6b8479c8661def8cd703b611a3905a6d5477fe6",
861 },
862 .{
863 .mode = .argon2d,
864 .time = 2,
865 .memory = 64,
866 .threads = 3,
867 .hash = "22474a423bda2ccd36ec9afd5119e5c8949798cadf659f51",
868 },
869 .{
870 .mode = .argon2id,
871 .time = 2,
872 .memory = 64,
873 .threads = 3,
874 .hash = "4a15b31aec7c2590b87d1f520be7d96f56658172deaa3079",
875 },
876 .{
877 .mode = .argon2i,
878 .time = 3,
879 .memory = 256,
880 .threads = 6,
881 .hash = "ebc8f91964abd8ceab49a12963b0a9e57d635bfa2aad2884",
882 },
883 .{
884 .mode = .argon2d,
885 .time = 3,
886 .memory = 256,
887 .threads = 6,
888 .hash = "1dd7202fd68da6675f769f4034b7a1db30d8785331954117",
889 },
890 .{
891 .mode = .argon2id,
892 .time = 3,
893 .memory = 256,
894 .threads = 6,
895 .hash = "424436b6ee22a66b04b9d0cf78f190305c5c166bae8baa09",
896 },
897 };
898 for (test_vectors) |v| {
899 var want: [24]u8 = undefined;
900 _ = try std.fmt.hexToBytes(&want, v.hash);
901
902 var dk: [24]u8 = undefined;
903 try kdf(
904 std.testing.allocator,
905 &dk,
906 password,
907 salt,
908 .{ .t = v.time, .m = v.memory, .p = v.threads },
909 v.mode,
910 std.testing.io,
911 );
912
913 try std.testing.expectEqualSlices(u8, &dk, &want);
914 }
915}
916
917test "phc format hasher" {
918 const allocator = std.testing.allocator;
919 const password = "testpass";
920 const io = std.testing.io;
921
922 var buf: [128]u8 = undefined;
923 const hash = try PhcFormatHasher.create(
924 allocator,
925 password,
926 .{ .t = 3, .m = 32, .p = 4 },
927 .argon2id,
928 &buf,
929 io,
930 );
931 try PhcFormatHasher.verify(allocator, hash, password, io);
932}
933
934test "password hash and password verify" {
935 const allocator = std.testing.allocator;
936 const password = "testpass";
937 const io = std.testing.io;
938
939 var buf: [128]u8 = undefined;
940 const hash = try strHash(
941 password,
942 .{ .allocator = allocator, .params = .{ .t = 3, .m = 32, .p = 4 } },
943 &buf,
944 io,
945 );
946 try strVerify(hash, password, .{ .allocator = allocator }, io);
947}
948
949test "kdf derived key length" {
950 if (true) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/31504
951
952 const allocator = std.testing.allocator;
953 const io = std.testing.io;
954
955 const password = "testpass";
956 const salt = "saltsalt";
957 const params = Params{ .t = 3, .m = 32, .p = 4 };
958 const mode = Mode.argon2id;
959
960 var dk1: [11]u8 = undefined;
961 try kdf(allocator, &dk1, password, salt, params, mode, io);
962
963 var dk2: [77]u8 = undefined;
964 try kdf(allocator, &dk2, password, salt, params, mode, io);
965
966 var dk3: [111]u8 = undefined;
967 try kdf(allocator, &dk3, password, salt, params, mode, io);
968}