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