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;
23pub const 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.findScalar(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 io: std.Io,
421 ) HasherError![]const u8 {
422 var salt: [default_salt_len]u8 = undefined;
423 io.random(&salt);
424 return createWithSalt(allocator, password, params, buf, &salt);
425 }
426
427 /// Return a deterministic hash of the password encoded as a PHC-format string.
428 /// Uses the provided salt instead of generating one randomly.
429 pub fn createWithSalt(
430 allocator: mem.Allocator,
431 password: []const u8,
432 params: Params,
433 buf: []u8,
434 salt: *const [default_salt_len]u8,
435 ) HasherError![]const u8 {
436 var hash: [default_hash_len]u8 = undefined;
437 try kdf(allocator, &hash, password, salt, params);
438
439 return phc_format.serialize(HashResult{
440 .alg_id = alg_id,
441 .ln = params.ln,
442 .r = params.r,
443 .p = params.p,
444 .salt = try BinValue(max_salt_len).fromSlice(salt),
445 .hash = try BinValue(max_hash_len).fromSlice(&hash),
446 }, buf);
447 }
448
449 /// Verify a password against a PHC-format encoded string
450 pub fn verify(
451 allocator: mem.Allocator,
452 str: []const u8,
453 password: []const u8,
454 ) HasherError!void {
455 const hash_result = try phc_format.deserialize(HashResult, str);
456 if (!mem.eql(u8, hash_result.alg_id, alg_id)) return HasherError.PasswordVerificationFailed;
457 const params = Params{ .ln = hash_result.ln, .r = hash_result.r, .p = hash_result.p };
458 const expected_hash = hash_result.hash.constSlice();
459 var hash_buf: [max_hash_len]u8 = undefined;
460 if (expected_hash.len > hash_buf.len) return HasherError.InvalidEncoding;
461 const hash = hash_buf[0..expected_hash.len];
462 try kdf(allocator, hash, password, hash_result.salt.constSlice(), params);
463 if (!mem.eql(u8, hash, expected_hash)) return HasherError.PasswordVerificationFailed;
464 }
465};
466
467/// Hash and verify passwords using the modular crypt format.
468const CryptFormatHasher = struct {
469 const BinValue = crypt_format.BinValue;
470 const HashResult = crypt_format.HashResult(max_hash_len);
471
472 /// Length of a string returned by the create() function
473 pub const pwhash_str_length: usize = 101;
474
475 /// Return a non-deterministic hash of the password encoded into the modular crypt format
476 pub fn create(
477 allocator: mem.Allocator,
478 password: []const u8,
479 params: Params,
480 buf: []u8,
481 io: std.Io,
482 ) HasherError![]const u8 {
483 var salt_bin: [default_salt_len]u8 = undefined;
484 io.random(&salt_bin);
485 return createWithSalt(allocator, password, params, buf, &salt_bin);
486 }
487
488 /// Return a deterministic hash of the password encoded into the modular crypt format.
489 /// Uses the provided salt instead of generating one randomly.
490 pub fn createWithSalt(
491 allocator: mem.Allocator,
492 password: []const u8,
493 params: Params,
494 buf: []u8,
495 salt_bin: *const [default_salt_len]u8,
496 ) HasherError![]const u8 {
497 const salt = crypt_format.saltFromBin(salt_bin.len, salt_bin.*);
498
499 var hash: [default_hash_len]u8 = undefined;
500 try kdf(allocator, &hash, password, &salt, params);
501
502 return crypt_format.serialize(HashResult{
503 .ln = params.ln,
504 .r = params.r,
505 .p = params.p,
506 .salt = &salt,
507 .hash = try BinValue(max_hash_len).fromSlice(&hash),
508 }, buf);
509 }
510
511 /// Verify a password against a string in modular crypt format
512 pub fn verify(
513 allocator: mem.Allocator,
514 str: []const u8,
515 password: []const u8,
516 ) HasherError!void {
517 const hash_result = try crypt_format.deserialize(HashResult, str);
518 const params = Params{ .ln = hash_result.ln, .r = hash_result.r, .p = hash_result.p };
519 const expected_hash = hash_result.hash.constSlice();
520 var hash_buf: [max_hash_len]u8 = undefined;
521 if (expected_hash.len > hash_buf.len) return HasherError.InvalidEncoding;
522 const hash = hash_buf[0..expected_hash.len];
523 try kdf(allocator, hash, password, hash_result.salt, params);
524 if (!mem.eql(u8, hash, expected_hash)) return HasherError.PasswordVerificationFailed;
525 }
526};
527
528/// Options for hashing a password.
529///
530/// Allocator is required for scrypt.
531pub const HashOptions = struct {
532 allocator: ?mem.Allocator,
533 params: Params,
534 encoding: pwhash.Encoding,
535};
536
537/// Compute a hash of a password using the scrypt key derivation function.
538/// The function returns a string that includes all the parameters required for verification.
539pub fn strHash(
540 password: []const u8,
541 options: HashOptions,
542 out: []u8,
543 io: std.Io,
544) Error![]const u8 {
545 const allocator = options.allocator orelse return Error.AllocatorRequired;
546 switch (options.encoding) {
547 .phc => return PhcFormatHasher.create(allocator, password, options.params, out, io),
548 .crypt => return CryptFormatHasher.create(allocator, password, options.params, out, io),
549 }
550}
551
552/// Compute a deterministic hash of a password using the scrypt key derivation function.
553/// The function returns a string that includes all the parameters required for verification.
554/// Uses the provided salt instead of generating one randomly.
555pub fn strHashWithSalt(
556 password: []const u8,
557 options: HashOptions,
558 out: []u8,
559 salt: *const [default_salt_len]u8,
560) Error![]const u8 {
561 const allocator = options.allocator orelse return Error.AllocatorRequired;
562 switch (options.encoding) {
563 .phc => return PhcFormatHasher.createWithSalt(allocator, password, options.params, out, salt),
564 .crypt => return CryptFormatHasher.createWithSalt(allocator, password, options.params, out, salt),
565 }
566}
567
568/// Options for hash verification.
569pub const VerifyOptions = struct {
570 allocator: mem.Allocator,
571};
572
573/// Verify that a previously computed hash is valid for a given password.
574pub fn strVerify(
575 str: []const u8,
576 password: []const u8,
577 options: VerifyOptions,
578) Error!void {
579 if (mem.startsWith(u8, str, crypt_format.prefix)) {
580 return CryptFormatHasher.verify(options.allocator, str, password);
581 } else {
582 return PhcFormatHasher.verify(options.allocator, str, password);
583 }
584}
585
586// These tests take way too long to run, so I have disabled them.
587const run_long_tests = false;
588
589test "kdf" {
590 if (!run_long_tests) return error.SkipZigTest;
591
592 const password = "testpass";
593 const salt = "saltsalt";
594
595 var dk: [32]u8 = undefined;
596 try kdf(std.testing.allocator, &dk, password, salt, .{ .ln = 15, .r = 8, .p = 1 });
597
598 const hex = "1e0f97c3f6609024022fbe698da29c2fe53ef1087a8e396dc6d5d2a041e886de";
599 var bytes: [hex.len / 2]u8 = undefined;
600 _ = try fmt.hexToBytes(&bytes, hex);
601
602 try std.testing.expectEqualSlices(u8, &bytes, &dk);
603}
604
605test "kdf rfc 1" {
606 if (!run_long_tests) return error.SkipZigTest;
607
608 const password = "";
609 const salt = "";
610
611 var dk: [64]u8 = undefined;
612 try kdf(std.testing.allocator, &dk, password, salt, .{ .ln = 4, .r = 1, .p = 1 });
613
614 const hex = "77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906";
615 var bytes: [hex.len / 2]u8 = undefined;
616 _ = try fmt.hexToBytes(&bytes, hex);
617
618 try std.testing.expectEqualSlices(u8, &bytes, &dk);
619}
620
621test "kdf rfc 2" {
622 if (!run_long_tests) return error.SkipZigTest;
623
624 const password = "password";
625 const salt = "NaCl";
626
627 var dk: [64]u8 = undefined;
628 try kdf(std.testing.allocator, &dk, password, salt, .{ .ln = 10, .r = 8, .p = 16 });
629
630 const hex = "fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640";
631 var bytes: [hex.len / 2]u8 = undefined;
632 _ = try fmt.hexToBytes(&bytes, hex);
633
634 try std.testing.expectEqualSlices(u8, &bytes, &dk);
635}
636
637test "kdf rfc 3" {
638 if (!run_long_tests) return error.SkipZigTest;
639
640 const password = "pleaseletmein";
641 const salt = "SodiumChloride";
642
643 var dk: [64]u8 = undefined;
644 try kdf(std.testing.allocator, &dk, password, salt, .{ .ln = 14, .r = 8, .p = 1 });
645
646 const hex = "7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887";
647 var bytes: [hex.len / 2]u8 = undefined;
648 _ = try fmt.hexToBytes(&bytes, hex);
649
650 try std.testing.expectEqualSlices(u8, &bytes, &dk);
651}
652
653test "kdf rfc 4" {
654 if (!run_long_tests) return error.SkipZigTest;
655
656 const password = "pleaseletmein";
657 const salt = "SodiumChloride";
658
659 var dk: [64]u8 = undefined;
660 try kdf(std.testing.allocator, &dk, password, salt, .{ .ln = 20, .r = 8, .p = 1 });
661
662 const hex = "2101cb9b6a511aaeaddbbe09cf70f881ec568d574a2ffd4dabe5ee9820adaa478e56fd8f4ba5d09ffa1c6d927c40f4c337304049e8a952fbcbf45c6fa77a41a4";
663 var bytes: [hex.len / 2]u8 = undefined;
664 _ = try fmt.hexToBytes(&bytes, hex);
665
666 try std.testing.expectEqualSlices(u8, &bytes, &dk);
667}
668
669test "password hashing (crypt format)" {
670 if (!run_long_tests) return error.SkipZigTest;
671
672 const alloc = std.testing.allocator;
673 const io = std.testing.io;
674
675 const str = "$7$A6....1....TrXs5Zk6s8sWHpQgWDIXTR8kUU3s6Jc3s.DtdS8M2i4$a4ik5hGDN7foMuHOW.cp.CtX01UyCeO0.JAG.AHPpx5";
676 const password = "Y0!?iQa9M%5ekffW(`";
677 try CryptFormatHasher.verify(alloc, str, password);
678
679 const params = Params.interactive;
680 var buf: [CryptFormatHasher.pwhash_str_length]u8 = undefined;
681 const str2 = try CryptFormatHasher.create(alloc, password, params, &buf, io);
682 try CryptFormatHasher.verify(alloc, str2, password);
683}
684
685test "strHash and strVerify" {
686 if (!run_long_tests) return error.SkipZigTest;
687
688 const alloc = std.testing.allocator;
689 const io = std.testing.io;
690
691 const password = "testpass";
692 const params = Params.interactive;
693 const verify_options: VerifyOptions = .{ .allocator = alloc };
694 var buf: [128]u8 = undefined;
695
696 {
697 const str = try strHash(
698 password,
699 .{ .allocator = alloc, .params = params, .encoding = .crypt },
700 &buf,
701 io,
702 );
703 try strVerify(str, password, verify_options);
704 }
705 {
706 const str = try strHash(
707 password,
708 .{ .allocator = alloc, .params = params, .encoding = .phc },
709 &buf,
710 io,
711 );
712 try strVerify(str, password, verify_options);
713 }
714}
715
716test "unix-scrypt" {
717 if (!run_long_tests) return error.SkipZigTest;
718
719 const alloc = std.testing.allocator;
720
721 // https://gitlab.com/jas/scrypt-unix-crypt/blob/master/unix-scrypt.txt
722 {
723 const str = "$7$C6..../....SodiumChloride$kBGj9fHznVYFQMEn/qDCfrDevf9YDtcDdKvEqHJLV8D";
724 const password = "pleaseletmein";
725 try strVerify(str, password, .{ .allocator = alloc });
726 }
727 // one of the libsodium test vectors
728 {
729 const str = "$7$B6....1....75gBMAGwfFWZqBdyF3WdTQnWdUsuTiWjG1fF9c1jiSD$tc8RoB3.Em3/zNgMLWo2u00oGIoTyJv4fl3Fl8Tix72";
730 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)";
731 try strVerify(str, password, .{ .allocator = alloc });
732 }
733}
734
735test "crypt format" {
736 const str = "$7$C6..../....SodiumChloride$kBGj9fHznVYFQMEn/qDCfrDevf9YDtcDdKvEqHJLV8D";
737 const params = try crypt_format.deserialize(crypt_format.HashResult(32), str);
738 var buf: [str.len]u8 = undefined;
739 const s1 = try crypt_format.serialize(params, &buf);
740 try std.testing.expectEqualStrings(s1, str);
741}
742
743test "kdf fast" {
744 const TestVector = struct {
745 password: []const u8,
746 salt: []const u8,
747 params: Params,
748 want: []const u8,
749 };
750 const test_vectors = [_]TestVector{
751 .{
752 .password = "p",
753 .salt = "s",
754 .params = .{ .ln = 1, .r = 1, .p = 1 },
755 .want = &([_]u8{
756 0x48, 0xb0, 0xd2, 0xa8, 0xa3, 0x27, 0x26, 0x11,
757 0x98, 0x4c, 0x50, 0xeb, 0xd6, 0x30, 0xaf, 0x52,
758 }),
759 },
760 };
761 inline for (test_vectors) |v| {
762 var dk: [v.want.len]u8 = undefined;
763 try kdf(std.testing.allocator, &dk, v.password, v.salt, v.params);
764 try std.testing.expectEqualSlices(u8, &dk, v.want);
765 }
766}
767
768test "strHashWithSalt deterministic" {
769 const alloc = std.testing.allocator;
770 const password = "testpass";
771 const salt: [default_salt_len]u8 = "0123456789abcdef0123456789abcdef".*;
772 const params: Params = .{ .ln = 1, .r = 1, .p = 1 };
773
774 var buf1: [128]u8 = undefined;
775 var buf2: [128]u8 = undefined;
776
777 const str1 = try strHashWithSalt(password, .{ .allocator = alloc, .params = params, .encoding = .phc }, &buf1, &salt);
778 const str2 = try strHashWithSalt(password, .{ .allocator = alloc, .params = params, .encoding = .phc }, &buf2, &salt);
779 try std.testing.expectEqualStrings(str1, str2);
780 try strVerify(str1, password, .{ .allocator = alloc });
781
782 const str3 = try strHashWithSalt(password, .{ .allocator = alloc, .params = params, .encoding = .crypt }, &buf1, &salt);
783 const str4 = try strHashWithSalt(password, .{ .allocator = alloc, .params = params, .encoding = .crypt }, &buf2, &salt);
784 try std.testing.expectEqualStrings(str3, str4);
785 try strVerify(str3, password, .{ .allocator = alloc });
786}