| ... | @@ -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 | |
| 5 | const std = @import("std"); |
| 6 | const builtin = @import("builtin"); |
| 7 | |
| 8 | const blake2 = crypto.hash.blake2; |
| 9 | const crypto = std.crypto; |
| 10 | const math = std.math; |
| 11 | const mem = std.mem; |
| 12 | const phc_format = pwhash.phc_format; |
| 13 | const pwhash = crypto.pwhash; |
| 14 | |
| 15 | const Thread = std.Thread; |
| 16 | const Blake2b512 = blake2.Blake2b512; |
| 17 | const Blocks = std.ArrayListAligned([block_length]u64, 16); |
| 18 | const H0 = [Blake2b512.digest_length + 8]u8; |
| 19 | |
| 20 | const EncodingError = crypto.errors.EncodingError; |
| 21 | const KdfError = pwhash.KdfError; |
| 22 | const HasherError = pwhash.HasherError; |
| 23 | const Error = pwhash.Error; |
| 24 | |
| 25 | const version = 0x13; |
| 26 | const block_length = 128; |
| 27 | const sync_points = 4; |
| 28 | const max_int = 0xffff_ffff; |
| 29 | |
| 30 | const default_salt_len = 32; |
| 31 | const default_hash_len = 32; |
| 32 | const max_salt_len = 64; |
| 33 | const max_hash_len = 64; |
| 34 | |
| 35 | /// Argon2 type |
| 36 | pub 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 |
| 54 | pub 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 | |
| 102 | fn 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 | |
| 140 | fn 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 | |
| 177 | fn 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 | |
| 203 | fn 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 | |
| 221 | fn 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 | |
| 242 | fn 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 | |
| 275 | fn 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 | |
| 332 | fn 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 | |
| 340 | fn 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 | |
| 348 | fn 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 | |
| 388 | const QuarterRound = struct { a: usize, b: usize, c: usize, d: usize }; |
| 389 | |
| 390 | fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound { |
| 391 | return .{ .a = a, .b = b, .c = c, .d = d }; |
| 392 | } |
| 393 | |
| 394 | fn 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 | |
| 399 | fn 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 | |
| 422 | fn 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 | |
| 442 | fn 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. |
| 482 | pub 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 | |
| 513 | const 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. |
| 581 | pub 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. |
| 590 | pub 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. |
| 611 | pub const VerifyOptions = struct { |
| 612 | allocator: ?*mem.Allocator, |
| 613 | }; |
| 614 | |
| 615 | /// Verify that a previously computed hash is valid for a given password. |
| 616 | pub 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 | |
| 625 | test "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 | |
| 650 | test "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 | |
| 675 | test "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 | |
| 700 | test "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 | |
| 899 | test "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 | |
| 914 | test "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 | |
| 927 | test "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 | } |