Skip to content

Commit 838d754

Browse files
committed
pwhash: Make strHash functions allocate
Greatly simplifies the API for most use cases.
1 parent 8908354 commit 838d754

File tree

4 files changed

+80
-60
lines changed

4 files changed

+80
-60
lines changed

lib/std/crypto/argon2.zig

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -581,23 +581,19 @@ const PhcFormatHasher = struct {
581581
pub const HashOptions = struct {
582582
params: Params,
583583
mode: Mode = .argon2id,
584+
strhash_max_bytes: usize = 128,
584585
};
585586

586587
/// Compute a hash of a password using the argon2 key derivation function.
587588
/// The function returns a string that includes all the parameters required for verification.
588589
pub fn strHash(
589590
allocator: mem.Allocator,
590591
password: []const u8,
591-
options: HashOptions,
592-
out: []u8,
593-
) Error![]const u8 {
594-
return PhcFormatHasher.create(
595-
allocator,
596-
password,
597-
options.params,
598-
options.mode,
599-
out,
600-
);
592+
comptime options: HashOptions,
593+
) Error![]u8 {
594+
var buf: [options.strhash_max_bytes]u8 = undefined;
595+
const out = try PhcFormatHasher.create(allocator, password, options.params, options.mode, &buf);
596+
return allocator.dupe(u8, out);
601597
}
602598

603599
/// Verify that a previously computed hash is valid for a given password.
@@ -902,13 +898,13 @@ test "password hash and password verify" {
902898
const allocator = std.testing.allocator;
903899
const password = "testpass";
904900

905-
var buf: [128]u8 = undefined;
906901
const hash = try strHash(
907902
allocator,
908903
password,
909904
.{ .params = .{ .t = 3, .m = 32, .p = 4 } },
910-
&buf,
911905
);
906+
defer allocator.free(hash);
907+
912908
try strVerify(allocator, hash, password);
913909
}
914910

lib/std/crypto/bcrypt.zig

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,13 @@ pub const HashOptions = struct {
752752
params: Params,
753753
/// Encoding to use for the output of the hash function.
754754
encoding: pwhash.Encoding,
755+
/// The maximum length in bytes for a hashed password. If not
756+
/// set by the developer, this varies based on encoding.
757+
/// `crypt` encoding uses 60 bytes, and `phc` encoding uses
758+
/// 120 bytes.
759+
///
760+
/// The returned hash may be smaller than the maximum length.
761+
strhash_max_bytes: ?usize = null,
755762
};
756763

757764
/// Compute a hash of a password using 2^rounds_log rounds of the bcrypt key stretching function.
@@ -762,14 +769,21 @@ pub const HashOptions = struct {
762769
/// IMPORTANT: by design, bcrypt silently truncates passwords to 72 bytes.
763770
/// If this is an issue for your application, set the `silently_truncate_password` option to `false`.
764771
pub fn strHash(
772+
allocator: mem.Allocator,
765773
password: []const u8,
766-
options: HashOptions,
767-
out: []u8,
768-
) Error![]const u8 {
769-
switch (options.encoding) {
770-
.phc => return PhcFormatHasher.create(password, options.params, out),
771-
.crypt => return CryptFormatHasher.create(password, options.params, out),
772-
}
774+
comptime options: HashOptions,
775+
) Error![]u8 {
776+
const buf_len = comptime if (options.strhash_max_bytes) |len| len else switch (options.encoding) {
777+
.crypt => hash_length,
778+
.phc => hash_length * 2,
779+
};
780+
var buf: [buf_len]u8 = undefined;
781+
782+
const out = switch (options.encoding) {
783+
.phc => try PhcFormatHasher.create(password, options.params, &buf),
784+
.crypt => try CryptFormatHasher.create(password, options.params, &buf),
785+
};
786+
return allocator.dupe(u8, out);
773787
}
774788

775789
/// Options for hash verification.
@@ -802,14 +816,15 @@ test "bcrypt codec" {
802816
}
803817

804818
test "bcrypt crypt format" {
805-
var hash_options = HashOptions{
819+
const allocator = std.testing.allocator;
820+
const hash_options = HashOptions{
806821
.params = .{ .rounds_log = 5, .silently_truncate_password = false },
807822
.encoding = .crypt,
808823
};
809-
var verify_options = VerifyOptions{ .silently_truncate_password = false };
824+
const verify_options = VerifyOptions{ .silently_truncate_password = false };
810825

811-
var buf: [hash_length]u8 = undefined;
812-
const s = try strHash("password", hash_options, &buf);
826+
const s = try strHash(allocator, "password", hash_options);
827+
defer allocator.free(s);
813828

814829
try testing.expect(mem.startsWith(u8, s, crypt_format.prefix));
815830
try strVerify(s, "password", verify_options);
@@ -818,8 +833,7 @@ test "bcrypt crypt format" {
818833
strVerify(s, "invalid password", verify_options),
819834
);
820835

821-
var long_buf: [hash_length]u8 = undefined;
822-
var long_s = try strHash("password" ** 100, hash_options, &long_buf);
836+
var long_s = try strHash(allocator, "password" ** 100, hash_options);
823837

824838
try testing.expect(mem.startsWith(u8, long_s, crypt_format.prefix));
825839
try strVerify(long_s, "password" ** 100, verify_options);
@@ -828,28 +842,33 @@ test "bcrypt crypt format" {
828842
strVerify(long_s, "password" ** 101, verify_options),
829843
);
830844

831-
hash_options.params.silently_truncate_password = true;
832-
verify_options.silently_truncate_password = true;
833-
long_s = try strHash("password" ** 100, hash_options, &long_buf);
834-
try strVerify(long_s, "password" ** 101, verify_options);
845+
allocator.free(long_s);
846+
847+
long_s = try strHash(allocator, "password" ** 100, .{
848+
.params = .{ .rounds_log = 5, .silently_truncate_password = true },
849+
.encoding = .crypt,
850+
});
851+
defer allocator.free(long_s);
852+
try strVerify(long_s, "password" ** 101, .{ .silently_truncate_password = true });
835853

836854
try strVerify(
837855
"$2b$08$WUQKyBCaKpziCwUXHiMVvu40dYVjkTxtWJlftl0PpjY2BxWSvFIEe",
838856
"The devil himself",
839-
verify_options,
857+
.{ .silently_truncate_password = true },
840858
);
841859
}
842860

843861
test "bcrypt phc format" {
844-
var hash_options = HashOptions{
862+
const allocator = std.testing.allocator;
863+
const hash_options = HashOptions{
845864
.params = .{ .rounds_log = 5, .silently_truncate_password = false },
846865
.encoding = .phc,
847866
};
848-
var verify_options = VerifyOptions{ .silently_truncate_password = false };
867+
const verify_options = VerifyOptions{ .silently_truncate_password = false };
849868
const prefix = "$bcrypt$";
850869

851-
var buf: [hash_length * 2]u8 = undefined;
852-
const s = try strHash("password", hash_options, &buf);
870+
const s = try strHash(allocator, "password", hash_options);
871+
defer allocator.free(s);
853872

854873
try testing.expect(mem.startsWith(u8, s, prefix));
855874
try strVerify(s, "password", verify_options);
@@ -858,8 +877,7 @@ test "bcrypt phc format" {
858877
strVerify(s, "invalid password", verify_options),
859878
);
860879

861-
var long_buf: [hash_length * 2]u8 = undefined;
862-
var long_s = try strHash("password" ** 100, hash_options, &long_buf);
880+
var long_s = try strHash(allocator, "password" ** 100, hash_options);
863881

864882
try testing.expect(mem.startsWith(u8, long_s, prefix));
865883
try strVerify(long_s, "password" ** 100, verify_options);
@@ -868,10 +886,14 @@ test "bcrypt phc format" {
868886
strVerify(long_s, "password" ** 101, verify_options),
869887
);
870888

871-
hash_options.params.silently_truncate_password = true;
872-
verify_options.silently_truncate_password = true;
873-
long_s = try strHash("password" ** 100, hash_options, &long_buf);
874-
try strVerify(long_s, "password" ** 101, verify_options);
889+
allocator.free(long_s);
890+
891+
long_s = try strHash(allocator, "password" ** 100, .{
892+
.params = .{ .rounds_log = 5, .silently_truncate_password = true },
893+
.encoding = .crypt,
894+
});
895+
defer allocator.free(long_s);
896+
try strVerify(long_s, "password" ** 101, .{ .silently_truncate_password = true });
875897

876898
try strVerify(
877899
"$bcrypt$r=5$2NopntlgE2lX3cTwr4qz8A$r3T7iKYQNnY4hAhGjk9RmuyvgrYJZwc",

lib/std/crypto/benchmark.zig

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -407,23 +407,22 @@ const pwhashes = [_]CryptoPwhash{
407407
};
408408

409409
fn benchmarkBcryptPwhash(
410-
_: mem.Allocator,
410+
allocator: mem.Allocator,
411411
comptime count: comptime_int,
412412
) !f64 {
413413
const password = "testpass" ** 2;
414414
const opts = crypto.pwhash.bcrypt.HashOptions{
415415
.params = bcrypt_params,
416416
.encoding = .phc,
417+
.strhash_max_bytes = 256,
417418
};
418-
var buf: [256]u8 = undefined;
419419

420420
var timer = try Timer.start();
421421
const start = timer.lap();
422422
{
423423
var i: usize = 0;
424424
while (i < count) : (i += 1) {
425-
_ = try crypto.pwhash.bcrypt.strHash(password, opts, &buf);
426-
mem.doNotOptimizeAway(&buf);
425+
_ = try crypto.pwhash.bcrypt.strHash(allocator, password, opts);
427426
}
428427
}
429428
const end = timer.read();
@@ -442,16 +441,15 @@ fn benchmarkScryptPwhash(
442441
const opts = crypto.pwhash.scrypt.HashOptions{
443442
.params = crypto.pwhash.scrypt.Params.interactive,
444443
.encoding = .phc,
444+
.strhash_max_bytes = 256,
445445
};
446-
var buf: [256]u8 = undefined;
447446

448447
var timer = try Timer.start();
449448
const start = timer.lap();
450449
{
451450
var i: usize = 0;
452451
while (i < count) : (i += 1) {
453-
_ = try crypto.pwhash.scrypt.strHash(allocator, password, opts, &buf);
454-
mem.doNotOptimizeAway(&buf);
452+
_ = try crypto.pwhash.scrypt.strHash(allocator, password, opts);
455453
}
456454
}
457455
const end = timer.read();
@@ -469,16 +467,15 @@ fn benchmarkArgon2Pwhash(
469467
const password = "testpass" ** 2;
470468
const opts = crypto.pwhash.argon2.HashOptions{
471469
.params = crypto.pwhash.argon2.Params.interactive_2id,
470+
.strhash_max_bytes = 256,
472471
};
473-
var buf: [256]u8 = undefined;
474472

475473
var timer = try Timer.start();
476474
const start = timer.lap();
477475
{
478476
var i: usize = 0;
479477
while (i < count) : (i += 1) {
480-
_ = try crypto.pwhash.argon2.strHash(allocator, password, opts, &buf);
481-
mem.doNotOptimizeAway(&buf);
478+
_ = try crypto.pwhash.argon2.strHash(allocator, password, opts);
482479
}
483480
}
484481
const end = timer.read();

lib/std/crypto/scrypt.zig

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -501,20 +501,24 @@ const CryptFormatHasher = struct {
501501
pub const HashOptions = struct {
502502
params: Params,
503503
encoding: pwhash.Encoding,
504+
strhash_max_bytes: usize = 128,
504505
};
505506

506507
/// Compute a hash of a password using the scrypt key derivation function.
507508
/// The function returns a string that includes all the parameters required for verification.
508509
pub fn strHash(
509510
allocator: mem.Allocator,
510511
password: []const u8,
511-
options: HashOptions,
512-
out: []u8,
513-
) Error![]const u8 {
514-
switch (options.encoding) {
515-
.phc => return PhcFormatHasher.create(allocator, password, options.params, out),
516-
.crypt => return CryptFormatHasher.create(allocator, password, options.params, out),
517-
}
512+
comptime options: HashOptions,
513+
) Error![]u8 {
514+
var buf: [options.strhash_max_bytes]u8 = undefined;
515+
const hasher = comptime switch (options.encoding) {
516+
.phc => &PhcFormatHasher.create,
517+
.crypt => &CryptFormatHasher.create,
518+
};
519+
520+
const written = try hasher(allocator, password, options.params, &buf);
521+
return allocator.dupe(u8, written);
518522
}
519523

520524
/// Verify that a previously computed hash is valid for a given password.
@@ -635,24 +639,25 @@ test "strHash and strVerify" {
635639

636640
const password = "testpass";
637641
const params = Params.interactive;
638-
var buf: [128]u8 = undefined;
639642

640643
{
641644
const str = try strHash(
642645
alloc,
643646
password,
644647
.{ .params = params, .encoding = .crypt },
645-
&buf,
646648
);
649+
defer alloc.free(str);
650+
647651
try strVerify(alloc, str, password);
648652
}
649653
{
650654
const str = try strHash(
651655
alloc,
652656
password,
653657
.{ .params = params, .encoding = .phc },
654-
&buf,
655658
);
659+
defer alloc.free(str);
660+
656661
try strVerify(alloc, str, password);
657662
}
658663
}

0 commit comments

Comments
 (0)