diff --git a/lib/std/crypto/argon2.zig b/lib/std/crypto/argon2.zig index 77c878e779d6..8b7b2d27082d 100644 --- a/lib/std/crypto/argon2.zig +++ b/lib/std/crypto/argon2.zig @@ -578,51 +578,30 @@ const PhcFormatHasher = struct { }; /// 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, + strhash_max_bytes: usize = 128, }; /// 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( + allocator: mem.Allocator, 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, - } + comptime options: HashOptions, +) Error![]u8 { + var buf: [options.strhash_max_bytes]u8 = undefined; + const out = try PhcFormatHasher.create(allocator, password, options.params, options.mode, &buf); + return allocator.dupe(u8, out); } -/// 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( + allocator: mem.Allocator, str: []const u8, password: []const u8, - options: VerifyOptions, ) Error!void { - const allocator = options.allocator orelse return Error.AllocatorRequired; return PhcFormatHasher.verify(allocator, str, password); } @@ -919,13 +898,14 @@ test "password hash and password verify" { const allocator = std.testing.allocator; const password = "testpass"; - var buf: [128]u8 = undefined; const hash = try strHash( + allocator, password, - .{ .allocator = allocator, .params = .{ .t = 3, .m = 32, .p = 4 } }, - &buf, + .{ .params = .{ .t = 3, .m = 32, .p = 4 } }, ); - try strVerify(hash, password, .{ .allocator = allocator }); + defer allocator.free(hash); + + try strVerify(allocator, hash, password); } test "kdf derived key length" { diff --git a/lib/std/crypto/bcrypt.zig b/lib/std/crypto/bcrypt.zig index 62fc4fd4ccc4..9fa7616af410 100644 --- a/lib/std/crypto/bcrypt.zig +++ b/lib/std/crypto/bcrypt.zig @@ -748,12 +748,17 @@ const CryptFormatHasher = struct { /// Options for hashing a password. pub const HashOptions = struct { - /// For `bcrypt`, that can be left to `null`. - allocator: ?mem.Allocator = null, /// Internal bcrypt parameters. params: Params, /// Encoding to use for the output of the hash function. encoding: pwhash.Encoding, + /// The maximum length in bytes for a hashed password. If not + /// set by the developer, this varies based on encoding. + /// `crypt` encoding uses 60 bytes, and `phc` encoding uses + /// 120 bytes. + /// + /// The returned hash may be smaller than the maximum length. + strhash_max_bytes: ?usize = null, }; /// Compute a hash of a password using 2^rounds_log rounds of the bcrypt key stretching function. @@ -764,20 +769,25 @@ pub const HashOptions = struct { /// IMPORTANT: by design, bcrypt silently truncates passwords to 72 bytes. /// If this is an issue for your application, set the `silently_truncate_password` option to `false`. pub fn strHash( + allocator: mem.Allocator, password: []const u8, - options: HashOptions, - out: []u8, -) Error![]const u8 { - switch (options.encoding) { - .phc => return PhcFormatHasher.create(password, options.params, out), - .crypt => return CryptFormatHasher.create(password, options.params, out), - } + comptime options: HashOptions, +) Error![]u8 { + const buf_len = comptime if (options.strhash_max_bytes) |len| len else switch (options.encoding) { + .crypt => hash_length, + .phc => hash_length * 2, + }; + var buf: [buf_len]u8 = undefined; + + const out = switch (options.encoding) { + .phc => try PhcFormatHasher.create(password, options.params, &buf), + .crypt => try CryptFormatHasher.create(password, options.params, &buf), + }; + return allocator.dupe(u8, out); } /// Options for hash verification. pub const VerifyOptions = struct { - /// For `bcrypt`, that can be left to `null`. - allocator: ?mem.Allocator = null, /// Whether to silently truncate the password to 72 bytes, or pre-hash the password when it is longer. silently_truncate_password: bool, }; @@ -806,14 +816,15 @@ test "bcrypt codec" { } test "bcrypt crypt format" { - var hash_options = HashOptions{ + const allocator = std.testing.allocator; + const hash_options = HashOptions{ .params = .{ .rounds_log = 5, .silently_truncate_password = false }, .encoding = .crypt, }; - var verify_options = VerifyOptions{ .silently_truncate_password = false }; + const verify_options = VerifyOptions{ .silently_truncate_password = false }; - var buf: [hash_length]u8 = undefined; - const s = try strHash("password", hash_options, &buf); + const s = try strHash(allocator, "password", hash_options); + defer allocator.free(s); try testing.expect(mem.startsWith(u8, s, crypt_format.prefix)); try strVerify(s, "password", verify_options); @@ -822,8 +833,7 @@ test "bcrypt crypt format" { strVerify(s, "invalid password", verify_options), ); - var long_buf: [hash_length]u8 = undefined; - var long_s = try strHash("password" ** 100, hash_options, &long_buf); + var long_s = try strHash(allocator, "password" ** 100, hash_options); try testing.expect(mem.startsWith(u8, long_s, crypt_format.prefix)); try strVerify(long_s, "password" ** 100, verify_options); @@ -832,28 +842,33 @@ test "bcrypt crypt format" { strVerify(long_s, "password" ** 101, verify_options), ); - hash_options.params.silently_truncate_password = true; - verify_options.silently_truncate_password = true; - long_s = try strHash("password" ** 100, hash_options, &long_buf); - try strVerify(long_s, "password" ** 101, verify_options); + allocator.free(long_s); + + long_s = try strHash(allocator, "password" ** 100, .{ + .params = .{ .rounds_log = 5, .silently_truncate_password = true }, + .encoding = .crypt, + }); + defer allocator.free(long_s); + try strVerify(long_s, "password" ** 101, .{ .silently_truncate_password = true }); try strVerify( "$2b$08$WUQKyBCaKpziCwUXHiMVvu40dYVjkTxtWJlftl0PpjY2BxWSvFIEe", "The devil himself", - verify_options, + .{ .silently_truncate_password = true }, ); } test "bcrypt phc format" { - var hash_options = HashOptions{ + const allocator = std.testing.allocator; + const hash_options = HashOptions{ .params = .{ .rounds_log = 5, .silently_truncate_password = false }, .encoding = .phc, }; - var verify_options = VerifyOptions{ .silently_truncate_password = false }; + const verify_options = VerifyOptions{ .silently_truncate_password = false }; const prefix = "$bcrypt$"; - var buf: [hash_length * 2]u8 = undefined; - const s = try strHash("password", hash_options, &buf); + const s = try strHash(allocator, "password", hash_options); + defer allocator.free(s); try testing.expect(mem.startsWith(u8, s, prefix)); try strVerify(s, "password", verify_options); @@ -862,8 +877,7 @@ test "bcrypt phc format" { strVerify(s, "invalid password", verify_options), ); - var long_buf: [hash_length * 2]u8 = undefined; - var long_s = try strHash("password" ** 100, hash_options, &long_buf); + var long_s = try strHash(allocator, "password" ** 100, hash_options); try testing.expect(mem.startsWith(u8, long_s, prefix)); try strVerify(long_s, "password" ** 100, verify_options); @@ -872,10 +886,14 @@ test "bcrypt phc format" { strVerify(long_s, "password" ** 101, verify_options), ); - hash_options.params.silently_truncate_password = true; - verify_options.silently_truncate_password = true; - long_s = try strHash("password" ** 100, hash_options, &long_buf); - try strVerify(long_s, "password" ** 101, verify_options); + allocator.free(long_s); + + long_s = try strHash(allocator, "password" ** 100, .{ + .params = .{ .rounds_log = 5, .silently_truncate_password = true }, + .encoding = .crypt, + }); + defer allocator.free(long_s); + try strVerify(long_s, "password" ** 101, .{ .silently_truncate_password = true }); try strVerify( "$bcrypt$r=5$2NopntlgE2lX3cTwr4qz8A$r3T7iKYQNnY4hAhGjk9RmuyvgrYJZwc", diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig index ee8809a55f26..460aa2cbb39c 100644 --- a/lib/std/crypto/benchmark.zig +++ b/lib/std/crypto/benchmark.zig @@ -387,50 +387,95 @@ pub fn benchmarkAes8(comptime Aes: anytype, comptime count: comptime_int) !u64 { } const CryptoPwhash = struct { - ty: type, - params: *const anyopaque, name: []const u8, + benchmark: fn (mem.Allocator, comptime comptime_int) anyerror!f64, }; const bcrypt_params = crypto.pwhash.bcrypt.Params{ .rounds_log = 8, .silently_truncate_password = true }; const pwhashes = [_]CryptoPwhash{ .{ - .ty = crypto.pwhash.bcrypt, - .params = &bcrypt_params, .name = "bcrypt", + .benchmark = benchmarkBcryptPwhash, }, .{ - .ty = crypto.pwhash.scrypt, - .params = &crypto.pwhash.scrypt.Params.interactive, .name = "scrypt", + .benchmark = benchmarkScryptPwhash, }, .{ - .ty = crypto.pwhash.argon2, - .params = &crypto.pwhash.argon2.Params.interactive_2id, .name = "argon2", + .benchmark = benchmarkArgon2Pwhash, }, }; -fn benchmarkPwhash( +fn benchmarkBcryptPwhash( + allocator: mem.Allocator, + comptime count: comptime_int, +) !f64 { + const password = "testpass" ** 2; + const opts = crypto.pwhash.bcrypt.HashOptions{ + .params = bcrypt_params, + .encoding = .phc, + .strhash_max_bytes = 256, + }; + + var timer = try Timer.start(); + const start = timer.lap(); + { + var i: usize = 0; + while (i < count) : (i += 1) { + _ = try crypto.pwhash.bcrypt.strHash(allocator, password, opts); + } + } + const end = timer.read(); + + const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s; + const throughput = elapsed_s / count; + + return throughput; +} + +fn benchmarkScryptPwhash( allocator: mem.Allocator, - comptime ty: anytype, - comptime params: *const anyopaque, comptime count: comptime_int, ) !f64 { const password = "testpass" ** 2; - const opts = ty.HashOptions{ - .allocator = allocator, - .params = @as(*const ty.Params, @ptrCast(@alignCast(params))).*, + const opts = crypto.pwhash.scrypt.HashOptions{ + .params = crypto.pwhash.scrypt.Params.interactive, .encoding = .phc, + .strhash_max_bytes = 256, + }; + + var timer = try Timer.start(); + const start = timer.lap(); + { + var i: usize = 0; + while (i < count) : (i += 1) { + _ = try crypto.pwhash.scrypt.strHash(allocator, password, opts); + } + } + const end = timer.read(); + + const elapsed_s = @as(f64, @floatFromInt(end - start)) / time.ns_per_s; + const throughput = elapsed_s / count; + + return throughput; +} + +fn benchmarkArgon2Pwhash( + allocator: mem.Allocator, + comptime count: comptime_int, +) !f64 { + const password = "testpass" ** 2; + const opts = crypto.pwhash.argon2.HashOptions{ + .params = crypto.pwhash.argon2.Params.interactive_2id, + .strhash_max_bytes = 256, }; - var buf: [256]u8 = undefined; var timer = try Timer.start(); const start = timer.lap(); { var i: usize = 0; while (i < count) : (i += 1) { - _ = try ty.strHash(password, opts, &buf); - mem.doNotOptimizeAway(&buf); + _ = try crypto.pwhash.argon2.strHash(allocator, password, opts); } } const end = timer.read(); @@ -563,7 +608,7 @@ pub fn main() !void { inline for (pwhashes) |H| { if (filter == null or std.mem.indexOf(u8, H.name, filter.?) != null) { - const throughput = try benchmarkPwhash(arena_allocator, H.ty, H.params, mode(64)); + const throughput = try H.benchmark(arena_allocator, mode(64)); try stdout.print("{s:>17}: {d:10.3} s/ops\n", .{ H.name, throughput }); } } diff --git a/lib/std/crypto/scrypt.zig b/lib/std/crypto/scrypt.zig index 23202e793747..7e962bc8085a 100644 --- a/lib/std/crypto/scrypt.zig +++ b/lib/std/crypto/scrypt.zig @@ -498,42 +498,35 @@ const CryptFormatHasher = struct { }; /// Options for hashing a password. -/// -/// Allocator is required for scrypt. pub const HashOptions = struct { - allocator: ?mem.Allocator, params: Params, encoding: pwhash.Encoding, + strhash_max_bytes: usize = 128, }; /// Compute a hash of a password using the scrypt key derivation function. /// The function returns a string that includes all the parameters required for verification. pub fn strHash( + allocator: mem.Allocator, 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, out), - .crypt => return CryptFormatHasher.create(allocator, password, options.params, out), - } -} + comptime options: HashOptions, +) Error![]u8 { + var buf: [options.strhash_max_bytes]u8 = undefined; + const hasher = comptime switch (options.encoding) { + .phc => &PhcFormatHasher.create, + .crypt => &CryptFormatHasher.create, + }; -/// Options for hash verification. -/// -/// Allocator is required for scrypt. -pub const VerifyOptions = struct { - allocator: ?mem.Allocator, -}; + const written = try hasher(allocator, password, options.params, &buf); + return allocator.dupe(u8, written); +} /// Verify that a previously computed hash is valid for a given password. pub fn strVerify( + allocator: mem.Allocator, str: []const u8, password: []const u8, - options: VerifyOptions, ) Error!void { - const allocator = options.allocator orelse return Error.AllocatorRequired; if (mem.startsWith(u8, str, crypt_format.prefix)) { return CryptFormatHasher.verify(allocator, str, password); } else { @@ -646,24 +639,26 @@ test "strHash and strVerify" { const password = "testpass"; const params = Params.interactive; - const verify_options = VerifyOptions{ .allocator = alloc }; - var buf: [128]u8 = undefined; { const str = try strHash( + alloc, password, - .{ .allocator = alloc, .params = params, .encoding = .crypt }, - &buf, + .{ .params = params, .encoding = .crypt }, ); - try strVerify(str, password, verify_options); + defer alloc.free(str); + + try strVerify(alloc, str, password); } { const str = try strHash( + alloc, password, - .{ .allocator = alloc, .params = params, .encoding = .phc }, - &buf, + .{ .params = params, .encoding = .phc }, ); - try strVerify(str, password, verify_options); + defer alloc.free(str); + + try strVerify(alloc, str, password); } } @@ -676,13 +671,13 @@ test "unix-scrypt" { { const str = "$7$C6..../....SodiumChloride$kBGj9fHznVYFQMEn/qDCfrDevf9YDtcDdKvEqHJLV8D"; const password = "pleaseletmein"; - try strVerify(str, password, .{ .allocator = alloc }); + try strVerify(alloc, str, password); } // one of the libsodium test vectors { const str = "$7$B6....1....75gBMAGwfFWZqBdyF3WdTQnWdUsuTiWjG1fF9c1jiSD$tc8RoB3.Em3/zNgMLWo2u00oGIoTyJv4fl3Fl8Tix72"; 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)"; - try strVerify(str, password, .{ .allocator = alloc }); + try strVerify(alloc, str, password); } }