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(¶meters);
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}