authorgravatar for 53379023+x13a@users.noreply.github.comlucky <53379023+x13a@users.noreply.github.com> 2021-11-15 06:47:57+03:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-11-15 04:47:57+01:00
logc61fbe77c8689709aac4adfa78b27ed7ea53ee5f
tree7b85582e0a2d11f89c5fdb63692de948870577d0
parent09c17acf024ba43252cc516b13a4cf8c19fda057
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

add argon2 kdf (#9756)


3 files changed, 957 insertions(+), 7 deletions(-)

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