|
// https://datatracker.ietf.org/doc/rfc9106 // https://github.com/golang/crypto/tree/master/argon2 // https://github.com/P-H-C/phc-winner-argon2 |
ModeArgon2d is faster and uses data-depending memory access, which makes it highly resistant against GPU cracking attacks and suitable for applications with no threats from side-channel timing attacks (eg. cryptocurrencies). |
const std = @import("std"); const builtin = @import("builtin"); |
ParamsArgon2i instead uses data-independent memory access, which is preferred for password hashing and password-based key derivation, but it is slower as it makes more passes over the memory to protect from tradeoff attacks. |
const blake2 = crypto.hash.blake2; const crypto = std.crypto; const math = std.math; const mem = std.mem; const phc_format = pwhash.phc_format; const pwhash = crypto.pwhash; |
interactive_2iArgon2id is a hybrid of Argon2i and Argon2d, using a combination of data-depending and data-independent memory accesses, which gives some of Argon2i's resistance to side-channel cache timing attacks and much of Argon2d's resistance to GPU cracking attacks. |
const Thread = std.Thread; const Blake2b512 = blake2.Blake2b512; const Blocks = std.ArrayListAligned([block_length]u64, .@"16"); const H0 = [Blake2b512.digest_length + 8]u8; |
moderate_2iArgon2 parameters |
const EncodingError = crypto.errors.EncodingError; const KdfError = pwhash.KdfError; const HasherError = pwhash.HasherError; const Error = pwhash.Error; |
sensitive_2iA [t]ime cost, which defines the amount of computation realized and therefore the execution time, given in number of iterations. |
const version = 0x13; const block_length = 128; const sync_points = 4; const max_int = 0xffff_ffff; |
interactive_2idA [m]emory cost, which defines the memory usage, given in kibibytes. |
const default_salt_len = 32; const default_hash_len = 32; const max_salt_len = 64; const max_hash_len = 64; |
moderate_2idA [p]arallelism degree, which defines the number of parallel threads. |
/// Argon2 type pub const Mode = enum { /// Argon2d is faster and uses data-depending memory access, which makes it highly resistant /// against GPU cracking attacks and suitable for applications with no threats from side-channel /// timing attacks (eg. cryptocurrencies). argon2d, |
sensitive_2idThe [secret] parameter, which is used for keyed hashing. This allows a secret key to be input at hashing time (from some external location) and be folded into the value of the hash. This means that even if your salts and hashes are compromised, an attacker cannot brute-force to find the password without the key. |
/// Argon2i instead uses data-independent memory access, which is preferred for password /// hashing and password-based key derivation, but it is slower as it makes more passes over /// the memory to protect from tradeoff attacks. argon2i, |
owasp_2idThe [ad] parameter, which is used to fold any additional data into the hash value. Functionally, this behaves almost exactly like the secret or salt parameters; the ad parameter is folding into the value of the hash. However, this parameter is used for different data. The salt should be a random string stored alongside your password. The secret should be a random key only usable at hashing time. The ad is for any other data. |
/// Argon2id is a hybrid of Argon2i and Argon2d, using a combination of data-depending and /// data-independent memory accesses, which gives some of Argon2i's resistance to side-channel /// cache timing attacks and much of Argon2d's resistance to GPU cracking attacks. argon2id, }; |
fromLimits()Baseline parameters for interactive logins using argon2i type |
/// Argon2 parameters pub const Params = struct { const Self = @This(); |
kdf()Baseline parameters for normal usage using argon2i type |
/// A [t]ime cost, which defines the amount of computation realized and therefore the execution /// time, given in number of iterations. t: u32, |
create()Baseline parameters for offline usage using argon2i type |
/// A [m]emory cost, which defines the memory usage, given in kibibytes. m: u32, |
verify()Baseline parameters for interactive logins using argon2id type |
/// A [p]arallelism degree, which defines the number of parallel threads. p: u24, |
HashOptionsBaseline parameters for normal usage using argon2id type |
/// The [secret] parameter, which is used for keyed hashing. This allows a secret key to be input /// at hashing time (from some external location) and be folded into the value of the hash. This /// means that even if your salts and hashes are compromised, an attacker cannot brute-force to /// find the password without the key. secret: ?[]const u8 = null, |
strHash()Baseline parameters for offline usage using argon2id type |
/// The [ad] parameter, which is used to fold any additional data into the hash value. Functionally, /// this behaves almost exactly like the secret or salt parameters; the ad parameter is folding /// into the value of the hash. However, this parameter is used for different data. The salt /// should be a random string stored alongside your password. The secret should be a random key /// only usable at hashing time. The ad is for any other data. ad: ?[]const u8 = null, |
VerifyOptionsRecommended parameters for argon2id type according to the [OWASP cheat sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html). |
/// Baseline parameters for interactive logins using argon2i type pub const interactive_2i = Self.fromLimits(4, 33554432); /// Baseline parameters for normal usage using argon2i type pub const moderate_2i = Self.fromLimits(6, 134217728); /// Baseline parameters for offline usage using argon2i type pub const sensitive_2i = Self.fromLimits(8, 536870912); |
strVerify()Create parameters from ops and mem limits, where mem_limit given in bytes |
/// Baseline parameters for interactive logins using argon2id type pub const interactive_2id = Self.fromLimits(2, 67108864); /// Baseline parameters for normal usage using argon2id type pub const moderate_2id = Self.fromLimits(3, 268435456); /// Baseline parameters for offline usage using argon2id type pub const sensitive_2id = Self.fromLimits(4, 1073741824); |
Test:argon2dDerives a key from the password, salt, and argon2 parameters. Derived key has to be at least 4 bytes length. Salt has to be at least 8 bytes length. |
/// Recommended parameters for argon2id type according to the /// [OWASP cheat sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html). pub const owasp_2id = Self{ .t = 2, .m = 19 * 1024, .p = 1 }; |
Test:argon2iOptions for hashing a password. Allocator is required for argon2. Only phc encoding is supported. |
/// Create parameters from ops and mem limits, where mem_limit given in bytes pub fn fromLimits(ops_limit: u32, mem_limit: usize) Self { const m = mem_limit / 1024; std.debug.assert(m <= max_int); return .{ .t = ops_limit, .m = @as(u32, @intCast(m)), .p = 1 }; } }; |
Test:argon2idCompute a hash of a password using the argon2 key derivation function. The function returns a string that includes all the parameters required for verification. |
fn initHash( password: []const u8, salt: []const u8, params: Params, dk_len: usize, mode: Mode, ) H0 { var h0: H0 = undefined; var parameters: [24]u8 = undefined; var tmp: [4]u8 = undefined; var b2 = Blake2b512.init(.{}); mem.writeInt(u32, parameters[0..4], params.p, .little); mem.writeInt(u32, parameters[4..8], @as(u32, @intCast(dk_len)), .little); mem.writeInt(u32, parameters[8..12], params.m, .little); mem.writeInt(u32, parameters[12..16], params.t, .little); mem.writeInt(u32, parameters[16..20], version, .little); mem.writeInt(u32, parameters[20..24], @intFromEnum(mode), .little); b2.update(¶meters); mem.writeInt(u32, &tmp, @as(u32, @intCast(password.len)), .little); b2.update(&tmp); b2.update(password); mem.writeInt(u32, &tmp, @as(u32, @intCast(salt.len)), .little); b2.update(&tmp); b2.update(salt); const secret = params.secret orelse ""; std.debug.assert(secret.len <= max_int); mem.writeInt(u32, &tmp, @as(u32, @intCast(secret.len)), .little); b2.update(&tmp); b2.update(secret); const ad = params.ad orelse ""; std.debug.assert(ad.len <= max_int); mem.writeInt(u32, &tmp, @as(u32, @intCast(ad.len)), .little); b2.update(&tmp); b2.update(ad); b2.final(h0[0..Blake2b512.digest_length]); return h0; } |
Test:kdfOptions for hash verification. Allocator is required for argon2. |
fn blake2bLong(out: []u8, in: []const u8) void { const H = Blake2b512; var outlen_bytes: [4]u8 = undefined; mem.writeInt(u32, &outlen_bytes, @as(u32, @intCast(out.len)), .little); |
Test:phc format hasherVerify that a previously computed hash is valid for a given password. |
var out_buf: [H.digest_length]u8 = undefined; |
Test:password hash and password verify |
if (out.len <= H.digest_length) { var h = H.init(.{ .expected_out_bits = out.len * 8 }); h.update(&outlen_bytes); h.update(in); h.final(&out_buf); @memcpy(out, out_buf[0..out.len]); return; } |
Test:kdf derived key length |
var h = H.init(.{}); h.update(&outlen_bytes); h.update(in); h.final(&out_buf); var out_slice = out; out_slice[0 .. H.digest_length / 2].* = out_buf[0 .. H.digest_length / 2].*; out_slice = out_slice[H.digest_length / 2 ..]; var in_buf: [H.digest_length]u8 = undefined; while (out_slice.len > H.digest_length) { in_buf = out_buf; H.hash(&in_buf, &out_buf, .{}); out_slice[0 .. H.digest_length / 2].* = out_buf[0 .. H.digest_length / 2].*; out_slice = out_slice[H.digest_length / 2 ..]; } in_buf = out_buf; H.hash(&in_buf, &out_buf, .{ .expected_out_bits = out_slice.len * 8 }); @memcpy(out_slice, out_buf[0..out_slice.len]); } fn initBlocks( blocks: *Blocks, h0: *H0, memory: u32, threads: u24, ) void { var block0: [1024]u8 = undefined; var lane: u24 = 0; while (lane < threads) : (lane += 1) { const j = lane * (memory / threads); mem.writeInt(u32, h0[Blake2b512.digest_length + 4 ..][0..4], lane, .little); mem.writeInt(u32, h0[Blake2b512.digest_length..][0..4], 0, .little); blake2bLong(&block0, h0); for (&blocks.items[j + 0], 0..) |*v, i| { v.* = mem.readInt(u64, block0[i * 8 ..][0..8], .little); } mem.writeInt(u32, h0[Blake2b512.digest_length..][0..4], 1, .little); blake2bLong(&block0, h0); for (&blocks.items[j + 1], 0..) |*v, i| { v.* = mem.readInt(u64, block0[i * 8 ..][0..8], .little); } } } fn processBlocks( allocator: mem.Allocator, blocks: *Blocks, time: u32, memory: u32, threads: u24, mode: Mode, ) KdfError!void { const lanes = memory / threads; const segments = lanes / sync_points; if (builtin.single_threaded or threads == 1) { processBlocksSt(blocks, time, memory, threads, mode, lanes, segments); } else { try processBlocksMt(allocator, blocks, time, memory, threads, mode, lanes, segments); } } fn processBlocksSt( blocks: *Blocks, time: u32, memory: u32, threads: u24, mode: Mode, lanes: u32, segments: u32, ) void { var n: u32 = 0; while (n < time) : (n += 1) { var slice: u32 = 0; while (slice < sync_points) : (slice += 1) { var lane: u24 = 0; while (lane < threads) : (lane += 1) { processSegment(blocks, time, memory, threads, mode, lanes, segments, n, slice, lane); } } } } fn processBlocksMt( allocator: mem.Allocator, blocks: *Blocks, time: u32, memory: u32, threads: u24, mode: Mode, lanes: u32, segments: u32, ) KdfError!void { var threads_list = try std.ArrayList(Thread).initCapacity(allocator, threads); defer threads_list.deinit(); var n: u32 = 0; while (n < time) : (n += 1) { var slice: u32 = 0; while (slice < sync_points) : (slice += 1) { var lane: u24 = 0; while (lane < threads) : (lane += 1) { const thread = try Thread.spawn(.{}, processSegment, .{ blocks, time, memory, threads, mode, lanes, segments, n, slice, lane, }); threads_list.appendAssumeCapacity(thread); } lane = 0; while (lane < threads) : (lane += 1) { threads_list.items[lane].join(); } threads_list.clearRetainingCapacity(); } } } fn processSegment( blocks: *Blocks, passes: u32, memory: u32, threads: u24, mode: Mode, lanes: u32, segments: u32, n: u32, slice: u32, lane: u24, ) void { var addresses align(16) = [_]u64{0} ** block_length; var in align(16) = [_]u64{0} ** block_length; const zero align(16) = [_]u64{0} ** block_length; if (mode == .argon2i or (mode == .argon2id and n == 0 and slice < sync_points / 2)) { in[0] = n; in[1] = lane; in[2] = slice; in[3] = memory; in[4] = passes; in[5] = @intFromEnum(mode); } var index: u32 = 0; if (n == 0 and slice == 0) { index = 2; if (mode == .argon2i or mode == .argon2id) { in[6] += 1; processBlock(&addresses, &in, &zero); processBlock(&addresses, &addresses, &zero); } } var offset = lane * lanes + slice * segments + index; var random: u64 = 0; while (index < segments) : ({ index += 1; offset += 1; }) { var prev = offset -% 1; if (index == 0 and slice == 0) { prev +%= lanes; } if (mode == .argon2i or (mode == .argon2id and n == 0 and slice < sync_points / 2)) { if (index % block_length == 0) { in[6] += 1; processBlock(&addresses, &in, &zero); processBlock(&addresses, &addresses, &zero); } random = addresses[index % block_length]; } else { random = blocks.items[prev][0]; } const new_offset = indexAlpha(random, lanes, segments, threads, n, slice, lane, index); processBlockXor(&blocks.items[offset], &blocks.items[prev], &blocks.items[new_offset]); } } fn processBlock( out: *align(16) [block_length]u64, in1: *align(16) const [block_length]u64, in2: *align(16) const [block_length]u64, ) void { processBlockGeneric(out, in1, in2, false); } fn processBlockXor( out: *[block_length]u64, in1: *const [block_length]u64, in2: *const [block_length]u64, ) void { processBlockGeneric(out, in1, in2, true); } fn processBlockGeneric( out: *[block_length]u64, in1: *const [block_length]u64, in2: *const [block_length]u64, comptime xor: bool, ) void { var t: [block_length]u64 = undefined; for (&t, 0..) |*v, i| { v.* = in1[i] ^ in2[i]; } var i: usize = 0; while (i < block_length) : (i += 16) { blamkaGeneric(t[i..][0..16]); } i = 0; var buffer: [16]u64 = undefined; while (i < block_length / 8) : (i += 2) { var j: usize = 0; while (j < block_length / 8) : (j += 2) { buffer[j] = t[j * 8 + i]; buffer[j + 1] = t[j * 8 + i + 1]; } blamkaGeneric(&buffer); j = 0; while (j < block_length / 8) : (j += 2) { t[j * 8 + i] = buffer[j]; t[j * 8 + i + 1] = buffer[j + 1]; } } if (xor) { for (t, 0..) |v, j| { out[j] ^= in1[j] ^ in2[j] ^ v; } } else { for (t, 0..) |v, j| { out[j] = in1[j] ^ in2[j] ^ v; } } } const QuarterRound = struct { a: usize, b: usize, c: usize, d: usize }; fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound { return .{ .a = a, .b = b, .c = c, .d = d }; } fn fBlaMka(x: u64, y: u64) u64 { const xy = @as(u64, @as(u32, @truncate(x))) * @as(u64, @as(u32, @truncate(y))); return x +% y +% 2 *% xy; } fn blamkaGeneric(x: *[16]u64) void { const rounds = comptime [_]QuarterRound{ Rp(0, 4, 8, 12), Rp(1, 5, 9, 13), Rp(2, 6, 10, 14), Rp(3, 7, 11, 15), Rp(0, 5, 10, 15), Rp(1, 6, 11, 12), Rp(2, 7, 8, 13), Rp(3, 4, 9, 14), }; inline for (rounds) |r| { x[r.a] = fBlaMka(x[r.a], x[r.b]); x[r.d] = math.rotr(u64, x[r.d] ^ x[r.a], 32); x[r.c] = fBlaMka(x[r.c], x[r.d]); x[r.b] = math.rotr(u64, x[r.b] ^ x[r.c], 24); x[r.a] = fBlaMka(x[r.a], x[r.b]); x[r.d] = math.rotr(u64, x[r.d] ^ x[r.a], 16); x[r.c] = fBlaMka(x[r.c], x[r.d]); x[r.b] = math.rotr(u64, x[r.b] ^ x[r.c], 63); } } fn finalize( blocks: *Blocks, memory: u32, threads: u24, out: []u8, ) void { const lanes = memory / threads; var lane: u24 = 0; while (lane < threads - 1) : (lane += 1) { for (blocks.items[(lane * lanes) + lanes - 1], 0..) |v, i| { blocks.items[memory - 1][i] ^= v; } } var block: [1024]u8 = undefined; for (blocks.items[memory - 1], 0..) |v, i| { mem.writeInt(u64, block[i * 8 ..][0..8], v, .little); } blake2bLong(out, &block); } fn indexAlpha( rand: u64, lanes: u32, segments: u32, threads: u24, n: u32, slice: u32, lane: u24, index: u32, ) u32 { var ref_lane = @as(u32, @intCast(rand >> 32)) % threads; if (n == 0 and slice == 0) { ref_lane = lane; } var m = 3 * segments; var s = ((slice + 1) % sync_points) * segments; if (lane == ref_lane) { m += index; } if (n == 0) { m = slice * segments; s = 0; if (slice == 0 or lane == ref_lane) { m += index; } } if (index == 0 or lane == ref_lane) { m -= 1; } var p = @as(u64, @as(u32, @truncate(rand))); p = (p * p) >> 32; p = (p * m) >> 32; return ref_lane * lanes + @as(u32, @intCast(((s + m - (p + 1)) % lanes))); } /// Derives a key from the password, salt, and argon2 parameters. /// /// Derived key has to be at least 4 bytes length. /// /// Salt has to be at least 8 bytes length. pub fn kdf( allocator: mem.Allocator, derived_key: []u8, password: []const u8, salt: []const u8, params: Params, mode: Mode, ) KdfError!void { if (derived_key.len < 4) return KdfError.WeakParameters; if (derived_key.len > max_int) return KdfError.OutputTooLong; if (password.len > max_int) return KdfError.WeakParameters; if (salt.len < 8 or salt.len > max_int) return KdfError.WeakParameters; if (params.t < 1 or params.p < 1) return KdfError.WeakParameters; if (params.m / 8 < params.p) return KdfError.WeakParameters; var h0 = initHash(password, salt, params, derived_key.len, mode); const memory = @max( params.m / (sync_points * params.p) * (sync_points * params.p), 2 * sync_points * params.p, ); var blocks = try Blocks.initCapacity(allocator, memory); defer blocks.deinit(); blocks.appendNTimesAssumeCapacity([_]u64{0} ** block_length, memory); initBlocks(&blocks, &h0, memory, params.p); try processBlocks(allocator, &blocks, params.t, memory, params.p, mode); finalize(&blocks, memory, params.p, derived_key); } const PhcFormatHasher = struct { const BinValue = phc_format.BinValue; const HashResult = struct { alg_id: []const u8, alg_version: ?u32, m: u32, t: u32, p: u24, salt: BinValue(max_salt_len), hash: BinValue(max_hash_len), }; pub fn create( allocator: mem.Allocator, password: []const u8, params: Params, mode: Mode, buf: []u8, ) HasherError![]const u8 { if (params.secret != null or params.ad != null) return HasherError.InvalidEncoding; var salt: [default_salt_len]u8 = undefined; crypto.random.bytes(&salt); var hash: [default_hash_len]u8 = undefined; try kdf(allocator, &hash, password, &salt, params, mode); return phc_format.serialize(HashResult{ .alg_id = @tagName(mode), .alg_version = version, .m = params.m, .t = params.t, .p = params.p, .salt = try BinValue(max_salt_len).fromSlice(&salt), .hash = try BinValue(max_hash_len).fromSlice(&hash), }, buf); } pub fn verify( allocator: mem.Allocator, str: []const u8, password: []const u8, ) HasherError!void { const hash_result = try phc_format.deserialize(HashResult, str); const mode = std.meta.stringToEnum(Mode, hash_result.alg_id) orelse return HasherError.PasswordVerificationFailed; if (hash_result.alg_version) |v| { if (v != version) return HasherError.InvalidEncoding; } const params = Params{ .t = hash_result.t, .m = hash_result.m, .p = hash_result.p }; const expected_hash = hash_result.hash.constSlice(); var hash_buf: [max_hash_len]u8 = undefined; if (expected_hash.len > hash_buf.len) return HasherError.InvalidEncoding; const hash = hash_buf[0..expected_hash.len]; try kdf(allocator, hash, password, hash_result.salt.constSlice(), params, mode); if (!mem.eql(u8, hash, expected_hash)) return HasherError.PasswordVerificationFailed; } }; /// Options for hashing a password. /// /// Allocator is required for argon2. /// /// Only phc encoding is supported. pub const HashOptions = struct { allocator: ?mem.Allocator, params: Params, mode: Mode = .argon2id, encoding: pwhash.Encoding = .phc, }; /// Compute a hash of a password using the argon2 key derivation function. /// The function returns a string that includes all the parameters required for verification. pub fn strHash( password: []const u8, options: HashOptions, out: []u8, ) Error![]const u8 { const allocator = options.allocator orelse return Error.AllocatorRequired; switch (options.encoding) { .phc => return PhcFormatHasher.create( allocator, password, options.params, options.mode, out, ), .crypt => return Error.InvalidEncoding, } } /// Options for hash verification. /// /// Allocator is required for argon2. pub const VerifyOptions = struct { allocator: ?mem.Allocator, }; /// Verify that a previously computed hash is valid for a given password. pub fn strVerify( str: []const u8, password: []const u8, options: VerifyOptions, ) Error!void { const allocator = options.allocator orelse return Error.AllocatorRequired; return PhcFormatHasher.verify(allocator, str, password); } test "argon2d" { const password = [_]u8{0x01} ** 32; const salt = [_]u8{0x02} ** 16; const secret = [_]u8{0x03} ** 8; const ad = [_]u8{0x04} ** 12; var dk: [32]u8 = undefined; try kdf( std.testing.allocator, &dk, &password, &salt, .{ .t = 3, .m = 32, .p = 4, .secret = &secret, .ad = &ad }, .argon2d, ); const want = [_]u8{ 0x51, 0x2b, 0x39, 0x1b, 0x6f, 0x11, 0x62, 0x97, 0x53, 0x71, 0xd3, 0x09, 0x19, 0x73, 0x42, 0x94, 0xf8, 0x68, 0xe3, 0xbe, 0x39, 0x84, 0xf3, 0xc1, 0xa1, 0x3a, 0x4d, 0xb9, 0xfa, 0xbe, 0x4a, 0xcb, }; try std.testing.expectEqualSlices(u8, &dk, &want); } test "argon2i" { const password = [_]u8{0x01} ** 32; const salt = [_]u8{0x02} ** 16; const secret = [_]u8{0x03} ** 8; const ad = [_]u8{0x04} ** 12; var dk: [32]u8 = undefined; try kdf( std.testing.allocator, &dk, &password, &salt, .{ .t = 3, .m = 32, .p = 4, .secret = &secret, .ad = &ad }, .argon2i, ); const want = [_]u8{ 0xc8, 0x14, 0xd9, 0xd1, 0xdc, 0x7f, 0x37, 0xaa, 0x13, 0xf0, 0xd7, 0x7f, 0x24, 0x94, 0xbd, 0xa1, 0xc8, 0xde, 0x6b, 0x01, 0x6d, 0xd3, 0x88, 0xd2, 0x99, 0x52, 0xa4, 0xc4, 0x67, 0x2b, 0x6c, 0xe8, }; try std.testing.expectEqualSlices(u8, &dk, &want); } test "argon2id" { const password = [_]u8{0x01} ** 32; const salt = [_]u8{0x02} ** 16; const secret = [_]u8{0x03} ** 8; const ad = [_]u8{0x04} ** 12; var dk: [32]u8 = undefined; try kdf( std.testing.allocator, &dk, &password, &salt, .{ .t = 3, .m = 32, .p = 4, .secret = &secret, .ad = &ad }, .argon2id, ); const want = [_]u8{ 0x0d, 0x64, 0x0d, 0xf5, 0x8d, 0x78, 0x76, 0x6c, 0x08, 0xc0, 0x37, 0xa3, 0x4a, 0x8b, 0x53, 0xc9, 0xd0, 0x1e, 0xf0, 0x45, 0x2d, 0x75, 0xb6, 0x5e, 0xb5, 0x25, 0x20, 0xe9, 0x6b, 0x01, 0xe6, 0x59, }; try std.testing.expectEqualSlices(u8, &dk, &want); } test "kdf" { const password = "password"; const salt = "somesalt"; const TestVector = struct { mode: Mode, time: u32, memory: u32, threads: u8, hash: []const u8, }; const test_vectors = [_]TestVector{ .{ .mode = .argon2i, .time = 1, .memory = 64, .threads = 1, .hash = "b9c401d1844a67d50eae3967dc28870b22e508092e861a37", }, .{ .mode = .argon2d, .time = 1, .memory = 64, .threads = 1, .hash = "8727405fd07c32c78d64f547f24150d3f2e703a89f981a19", }, .{ .mode = .argon2id, .time = 1, .memory = 64, .threads = 1, .hash = "655ad15eac652dc59f7170a7332bf49b8469be1fdb9c28bb", }, .{ .mode = .argon2i, .time = 2, .memory = 64, .threads = 1, .hash = "8cf3d8f76a6617afe35fac48eb0b7433a9a670ca4a07ed64", }, .{ .mode = .argon2d, .time = 2, .memory = 64, .threads = 1, .hash = "3be9ec79a69b75d3752acb59a1fbb8b295a46529c48fbb75", }, .{ .mode = .argon2id, .time = 2, .memory = 64, .threads = 1, .hash = "068d62b26455936aa6ebe60060b0a65870dbfa3ddf8d41f7", }, .{ .mode = .argon2i, .time = 2, .memory = 64, .threads = 2, .hash = "2089f3e78a799720f80af806553128f29b132cafe40d059f", }, .{ .mode = .argon2d, .time = 2, .memory = 64, .threads = 2, .hash = "68e2462c98b8bc6bb60ec68db418ae2c9ed24fc6748a40e9", }, .{ .mode = .argon2id, .time = 2, .memory = 64, .threads = 2, .hash = "350ac37222f436ccb5c0972f1ebd3bf6b958bf2071841362", }, .{ .mode = .argon2i, .time = 3, .memory = 256, .threads = 2, .hash = "f5bbf5d4c3836af13193053155b73ec7476a6a2eb93fd5e6", }, .{ .mode = .argon2d, .time = 3, .memory = 256, .threads = 2, .hash = "f4f0669218eaf3641f39cc97efb915721102f4b128211ef2", }, .{ .mode = .argon2id, .time = 3, .memory = 256, .threads = 2, .hash = "4668d30ac4187e6878eedeacf0fd83c5a0a30db2cc16ef0b", }, .{ .mode = .argon2i, .time = 4, .memory = 4096, .threads = 4, .hash = "a11f7b7f3f93f02ad4bddb59ab62d121e278369288a0d0e7", }, .{ .mode = .argon2d, .time = 4, .memory = 4096, .threads = 4, .hash = "935598181aa8dc2b720914aa6435ac8d3e3a4210c5b0fb2d", }, .{ .mode = .argon2id, .time = 4, .memory = 4096, .threads = 4, .hash = "145db9733a9f4ee43edf33c509be96b934d505a4efb33c5a", }, .{ .mode = .argon2i, .time = 4, .memory = 1024, .threads = 8, .hash = "0cdd3956aa35e6b475a7b0c63488822f774f15b43f6e6e17", }, .{ .mode = .argon2d, .time = 4, .memory = 1024, .threads = 8, .hash = "83604fc2ad0589b9d055578f4d3cc55bc616df3578a896e9", }, .{ .mode = .argon2id, .time = 4, .memory = 1024, .threads = 8, .hash = "8dafa8e004f8ea96bf7c0f93eecf67a6047476143d15577f", }, .{ .mode = .argon2i, .time = 2, .memory = 64, .threads = 3, .hash = "5cab452fe6b8479c8661def8cd703b611a3905a6d5477fe6", }, .{ .mode = .argon2d, .time = 2, .memory = 64, .threads = 3, .hash = "22474a423bda2ccd36ec9afd5119e5c8949798cadf659f51", }, .{ .mode = .argon2id, .time = 2, .memory = 64, .threads = 3, .hash = "4a15b31aec7c2590b87d1f520be7d96f56658172deaa3079", }, .{ .mode = .argon2i, .time = 3, .memory = 1024, .threads = 6, .hash = "d236b29c2b2a09babee842b0dec6aa1e83ccbdea8023dced", }, .{ .mode = .argon2d, .time = 3, .memory = 1024, .threads = 6, .hash = "a3351b0319a53229152023d9206902f4ef59661cdca89481", }, .{ .mode = .argon2id, .time = 3, .memory = 1024, .threads = 6, .hash = "1640b932f4b60e272f5d2207b9a9c626ffa1bd88d2349016", }, }; for (test_vectors) |v| { var want: [24]u8 = undefined; _ = try std.fmt.hexToBytes(&want, v.hash); var dk: [24]u8 = undefined; try kdf( std.testing.allocator, &dk, password, salt, .{ .t = v.time, .m = v.memory, .p = v.threads }, v.mode, ); try std.testing.expectEqualSlices(u8, &dk, &want); } } test "phc format hasher" { const allocator = std.testing.allocator; const password = "testpass"; var buf: [128]u8 = undefined; const hash = try PhcFormatHasher.create( allocator, password, .{ .t = 3, .m = 32, .p = 4 }, .argon2id, &buf, ); try PhcFormatHasher.verify(allocator, hash, password); } test "password hash and password verify" { const allocator = std.testing.allocator; const password = "testpass"; var buf: [128]u8 = undefined; const hash = try strHash( password, .{ .allocator = allocator, .params = .{ .t = 3, .m = 32, .p = 4 } }, &buf, ); try strVerify(hash, password, .{ .allocator = allocator }); } test "kdf derived key length" { const allocator = std.testing.allocator; const password = "testpass"; const salt = "saltsalt"; const params = Params{ .t = 3, .m = 32, .p = 4 }; const mode = Mode.argon2id; var dk1: [11]u8 = undefined; try kdf(allocator, &dk1, password, salt, params, mode); var dk2: [77]u8 = undefined; try kdf(allocator, &dk2, password, salt, params, mode); var dk3: [111]u8 = undefined; try kdf(allocator, &dk3, password, salt, params, mode); } |
Generated by zstd-live on 2025-08-10 02:45:58 UTC. |