| ... | ... | @@ -7,6 +7,7 @@ const math = std.math; |
| 7 | 7 | const mem = std.mem; |
| 8 | 8 | const pwhash = crypto.pwhash; |
| 9 | 9 | const testing = std.testing; |
| 10 | const HmacSha512 = crypto.auth.hmac.sha2.HmacSha512; |
| 10 | 11 | const Sha512 = crypto.hash.sha2.Sha512; |
| 11 | 12 | const utils = crypto.utils; |
| 12 | 13 | |
| ... | ... | @@ -405,10 +406,21 @@ pub const State = struct { |
| 405 | 406 | } |
| 406 | 407 | }; |
| 407 | 408 | |
| 409 | /// bcrypt parameters |
| 408 | 410 | pub const Params = struct { |
| 411 | /// log2 of the number of rounds |
| 409 | 412 | rounds_log: u6, |
| 410 | 413 | }; |
| 411 | 414 | |
| 415 | /// Compute a hash of a password using 2^rounds_log rounds of the bcrypt key stretching function. |
| 416 | /// bcrypt is a computationally expensive and cache-hard function, explicitly designed to slow down exhaustive searches. |
| 417 | /// |
| 418 | /// The function returns the hash as a `dk_length` byte array, that doesn't include anything besides the hash output. |
| 419 | /// |
| 420 | /// For a generic key-derivation function, use `bcrypt.pbkdf()` instead. |
| 421 | /// |
| 422 | /// IMPORTANT: by design, bcrypt silently truncates passwords to 72 bytes. |
| 423 | /// If this is an issue for your application, use `bcryptWithoutTruncation` instead. |
| 412 | 424 | pub fn bcrypt( |
| 413 | 425 | password: []const u8, |
| 414 | 426 | salt: [salt_length]u8, |
| ... | ... | @@ -443,6 +455,34 @@ pub fn bcrypt( |
| 443 | 455 | return ct[0..dk_length].*; |
| 444 | 456 | } |
| 445 | 457 | |
| 458 | /// Compute a hash of a password using 2^rounds_log rounds of the bcrypt key stretching function. |
| 459 | /// bcrypt is a computationally expensive and cache-hard function, explicitly designed to slow down exhaustive searches. |
| 460 | /// |
| 461 | /// The function returns the hash as a `dk_length` byte array, that doesn't include anything besides the hash output. |
| 462 | /// |
| 463 | /// For a generic key-derivation function, use `bcrypt.pbkdf()` instead. |
| 464 | /// |
| 465 | /// This function is identical to `bcrypt`, except that it doesn't silently truncate passwords. |
| 466 | /// Instead, passwords longer than 72 bytes are pre-hashed using HMAC-SHA512 before being passed to bcrypt. |
| 467 | pub fn bcryptWithoutTruncation( |
| 468 | password: []const u8, |
| 469 | salt: [salt_length]u8, |
| 470 | params: Params, |
| 471 | ) [dk_length]u8 { |
| 472 | if (password.len <= 72) { |
| 473 | return bcrypt(password, salt, params); |
| 474 | } |
| 475 | |
| 476 | var pre_hash: [HmacSha512.mac_length]u8 = undefined; |
| 477 | HmacSha512.create(&pre_hash, password, &salt); |
| 478 | |
| 479 | const Encoder = crypt_format.Codec.Encoder; |
| 480 | var pre_hash_b64: [Encoder.calcSize(pre_hash.len)]u8 = undefined; |
| 481 | _ = Encoder.encode(&pre_hash_b64, &pre_hash); |
| 482 | |
| 483 | return bcrypt(&pre_hash_b64, salt, params); |
| 484 | } |
| 485 | |
| 446 | 486 | const pbkdf_prf = struct { |
| 447 | 487 | const Self = @This(); |
| 448 | 488 | pub const mac_length = 32; |
| ... | ... | @@ -450,24 +490,24 @@ const pbkdf_prf = struct { |
| 450 | 490 | hasher: Sha512, |
| 451 | 491 | sha2pass: [Sha512.digest_length]u8, |
| 452 | 492 | |
| 453 | | pub fn create(out: *[mac_length]u8, msg: []const u8, key: []const u8) void { |
| 493 | fn create(out: *[mac_length]u8, msg: []const u8, key: []const u8) void { |
| 454 | 494 | var ctx = Self.init(key); |
| 455 | 495 | ctx.update(msg); |
| 456 | 496 | ctx.final(out); |
| 457 | 497 | } |
| 458 | 498 | |
| 459 | | pub fn init(key: []const u8) Self { |
| 499 | fn init(key: []const u8) Self { |
| 460 | 500 | var self: Self = undefined; |
| 461 | 501 | self.hasher = Sha512.init(.{}); |
| 462 | 502 | Sha512.hash(key, &self.sha2pass, .{}); |
| 463 | 503 | return self; |
| 464 | 504 | } |
| 465 | 505 | |
| 466 | | pub fn update(self: *Self, msg: []const u8) void { |
| 506 | fn update(self: *Self, msg: []const u8) void { |
| 467 | 507 | self.hasher.update(msg); |
| 468 | 508 | } |
| 469 | 509 | |
| 470 | | pub fn final(self: *Self, out: *[mac_length]u8) void { |
| 510 | fn final(self: *Self, out: *[mac_length]u8) void { |
| 471 | 511 | var sha2salt: [Sha512.digest_length]u8 = undefined; |
| 472 | 512 | self.hasher.final(&sha2salt); |
| 473 | 513 | out.* = hash(self.sha2pass, sha2salt); |
| ... | ... | @@ -517,10 +557,12 @@ const pbkdf_prf = struct { |
| 517 | 557 | } |
| 518 | 558 | }; |
| 519 | 559 | |
| 520 | | /// bcrypt PBKDF2 implementation with variations to match OpenBSD |
| 521 | | /// https://github.com/openbsd/src/blob/6df1256b7792691e66c2ed9d86a8c103069f9e34/lib/libutil/bcrypt_pbkdf.c#L98 |
| 560 | /// bcrypt-pbkdf is a key derivation function based on bcrypt. |
| 561 | /// This is the function used in OpenSSH to derive encryption keys from passphrases. |
| 522 | 562 | /// |
| 523 | | /// This particular variant is used in e.g. SSH |
| 563 | /// This implementation is compatible with the OpenBSD implementation (https://github.com/openbsd/src/blob/master/lib/libutil/bcrypt_pbkdf.c). |
| 564 | /// |
| 565 | /// Unlike the password hashing function `bcrypt`, this function doesn't silently truncate passwords longer than 72 bytes. |
| 524 | 566 | pub fn pbkdf(pass: []const u8, salt: []const u8, key: []u8, rounds: u32) !void { |
| 525 | 567 | try crypto.pwhash.pbkdf2(key, pass, salt, rounds, pbkdf_prf); |
| 526 | 568 | } |
| ... | ... | @@ -540,8 +582,9 @@ const crypt_format = struct { |
| 540 | 582 | password: []const u8, |
| 541 | 583 | salt: [salt_length]u8, |
| 542 | 584 | params: Params, |
| 585 | silently_truncate_password: bool, |
| 543 | 586 | ) [hash_length]u8 { |
| 544 | | var dk = bcrypt(password, salt, params); |
| 587 | var dk = if (silently_truncate_password) bcrypt(password, salt, params) else bcryptWithoutTruncation(password, salt, params); |
| 545 | 588 | |
| 546 | 589 | var salt_str: [salt_str_length]u8 = undefined; |
| 547 | 590 | _ = Codec.Encoder.encode(salt_str[0..], salt[0..]); |
| ... | ... | @@ -573,15 +616,16 @@ const PhcFormatHasher = struct { |
| 573 | 616 | }; |
| 574 | 617 | |
| 575 | 618 | /// Return a non-deterministic hash of the password encoded as a PHC-format string |
| 576 | | pub fn create( |
| 619 | fn create( |
| 577 | 620 | password: []const u8, |
| 578 | 621 | params: Params, |
| 622 | silently_truncate_password: bool, |
| 579 | 623 | buf: []u8, |
| 580 | 624 | ) HasherError![]const u8 { |
| 581 | 625 | var salt: [salt_length]u8 = undefined; |
| 582 | 626 | crypto.random.bytes(&salt); |
| 583 | 627 | |
| 584 | | const hash = bcrypt(password, salt, params); |
| 628 | const hash = if (silently_truncate_password) bcrypt(password, salt, params) else bcryptWithoutTruncation(password, salt, params); |
| 585 | 629 | |
| 586 | 630 | return phc_format.serialize(HashResult{ |
| 587 | 631 | .alg_id = alg_id, |
| ... | ... | @@ -592,9 +636,10 @@ const PhcFormatHasher = struct { |
| 592 | 636 | } |
| 593 | 637 | |
| 594 | 638 | /// Verify a password against a PHC-format encoded string |
| 595 | | pub fn verify( |
| 639 | fn verify( |
| 596 | 640 | str: []const u8, |
| 597 | 641 | password: []const u8, |
| 642 | silently_truncate_password: bool, |
| 598 | 643 | ) HasherError!void { |
| 599 | 644 | const hash_result = try phc_format.deserialize(HashResult, str); |
| 600 | 645 | |
| ... | ... | @@ -602,7 +647,8 @@ const PhcFormatHasher = struct { |
| 602 | 647 | if (hash_result.salt.len != salt_length or hash_result.hash.len != dk_length) |
| 603 | 648 | return HasherError.InvalidEncoding; |
| 604 | 649 | |
| 605 | | const hash = bcrypt(password, hash_result.salt.buf, .{ .rounds_log = hash_result.r }); |
| 650 | const params = Params{ .rounds_log = hash_result.r }; |
| 651 | const hash = if (silently_truncate_password) bcrypt(password, hash_result.salt.buf, params) else bcryptWithoutTruncation(password, hash_result.salt.buf, params); |
| 606 | 652 | const expected_hash = hash_result.hash.constSlice(); |
| 607 | 653 | |
| 608 | 654 | if (!mem.eql(u8, &hash, expected_hash)) return HasherError.PasswordVerificationFailed; |
| ... | ... | @@ -612,12 +658,13 @@ const PhcFormatHasher = struct { |
| 612 | 658 | /// Hash and verify passwords using the modular crypt format. |
| 613 | 659 | const CryptFormatHasher = struct { |
| 614 | 660 | /// Length of a string returned by the create() function |
| 615 | | pub const pwhash_str_length: usize = hash_length; |
| 661 | const pwhash_str_length: usize = hash_length; |
| 616 | 662 | |
| 617 | 663 | /// Return a non-deterministic hash of the password encoded into the modular crypt format |
| 618 | | pub fn create( |
| 664 | fn create( |
| 619 | 665 | password: []const u8, |
| 620 | 666 | params: Params, |
| 667 | silently_truncate_password: bool, |
| 621 | 668 | buf: []u8, |
| 622 | 669 | ) HasherError![]const u8 { |
| 623 | 670 | if (buf.len < pwhash_str_length) return HasherError.NoSpaceLeft; |
| ... | ... | @@ -625,16 +672,17 @@ const CryptFormatHasher = struct { |
| 625 | 672 | var salt: [salt_length]u8 = undefined; |
| 626 | 673 | crypto.random.bytes(&salt); |
| 627 | 674 | |
| 628 | | const hash = crypt_format.strHashInternal(password, salt, params); |
| 675 | const hash = crypt_format.strHashInternal(password, salt, params, silently_truncate_password); |
| 629 | 676 | @memcpy(buf[0..hash.len], &hash); |
| 630 | 677 | |
| 631 | 678 | return buf[0..pwhash_str_length]; |
| 632 | 679 | } |
| 633 | 680 | |
| 634 | 681 | /// Verify a password against a string in modular crypt format |
| 635 | | pub fn verify( |
| 682 | fn verify( |
| 636 | 683 | str: []const u8, |
| 637 | 684 | password: []const u8, |
| 685 | silently_truncate_password: bool, |
| 638 | 686 | ) HasherError!void { |
| 639 | 687 | if (str.len != pwhash_str_length or str[3] != '$' or str[6] != '$') |
| 640 | 688 | return HasherError.InvalidEncoding; |
| ... | ... | @@ -647,16 +695,22 @@ const CryptFormatHasher = struct { |
| 647 | 695 | var salt: [salt_length]u8 = undefined; |
| 648 | 696 | crypt_format.Codec.Decoder.decode(salt[0..], salt_str[0..]) catch return HasherError.InvalidEncoding; |
| 649 | 697 | |
| 650 | | const wanted_s = crypt_format.strHashInternal(password, salt, .{ .rounds_log = rounds_log }); |
| 698 | const wanted_s = crypt_format.strHashInternal(password, salt, .{ .rounds_log = rounds_log }, silently_truncate_password); |
| 651 | 699 | if (!mem.eql(u8, wanted_s[0..], str[0..])) return HasherError.PasswordVerificationFailed; |
| 652 | 700 | } |
| 653 | 701 | }; |
| 654 | 702 | |
| 655 | 703 | /// Options for hashing a password. |
| 656 | 704 | pub const HashOptions = struct { |
| 705 | /// For `bcrypt`, that can be left to `null`. |
| 657 | 706 | allocator: ?mem.Allocator = null, |
| 707 | /// Internal bcrypt parameters. |
| 658 | 708 | params: Params, |
| 709 | /// Encoding to use for the output of the hash function. |
| 659 | 710 | encoding: pwhash.Encoding, |
| 711 | /// Whether to silently truncate the password to 72 bytes, or pre-hash the password when it is longer. |
| 712 | /// The default is `true`, for compatibility with the original bcrypt implementation. |
| 713 | silently_truncate_password: bool = true, |
| 660 | 714 | }; |
| 661 | 715 | |
| 662 | 716 | /// Compute a hash of a password using 2^rounds_log rounds of the bcrypt key stretching function. |
| ... | ... | @@ -665,34 +719,36 @@ pub const HashOptions = struct { |
| 665 | 719 | /// The function returns a string that includes all the parameters required for verification. |
| 666 | 720 | /// |
| 667 | 721 | /// IMPORTANT: by design, bcrypt silently truncates passwords to 72 bytes. |
| 668 | | /// If this is an issue for your application, hash the password first using a function such as SHA-512, |
| 669 | | /// and then use the resulting hash as the password parameter for bcrypt. |
| 722 | /// If this is an issue for your application, set the `silently_truncate_password` option to `false`. |
| 670 | 723 | pub fn strHash( |
| 671 | 724 | password: []const u8, |
| 672 | 725 | options: HashOptions, |
| 673 | 726 | out: []u8, |
| 674 | 727 | ) Error![]const u8 { |
| 675 | 728 | switch (options.encoding) { |
| 676 | | .phc => return PhcFormatHasher.create(password, options.params, out), |
| 677 | | .crypt => return CryptFormatHasher.create(password, options.params, out), |
| 729 | .phc => return PhcFormatHasher.create(password, options.params, options.silently_truncate_password, out), |
| 730 | .crypt => return CryptFormatHasher.create(password, options.params, options.silently_truncate_password, out), |
| 678 | 731 | } |
| 679 | 732 | } |
| 680 | 733 | |
| 681 | 734 | /// Options for hash verification. |
| 682 | 735 | pub const VerifyOptions = struct { |
| 736 | /// For `bcrypt`, that can be left to `null`. |
| 683 | 737 | allocator: ?mem.Allocator = null, |
| 738 | /// Whether to silently truncate the password to 72 bytes, or pre-hash the password when it is longer. |
| 739 | silently_truncate_password: bool = false, |
| 684 | 740 | }; |
| 685 | 741 | |
| 686 | 742 | /// Verify that a previously computed hash is valid for a given password. |
| 687 | 743 | pub fn strVerify( |
| 688 | 744 | str: []const u8, |
| 689 | 745 | password: []const u8, |
| 690 | | _: VerifyOptions, |
| 746 | options: VerifyOptions, |
| 691 | 747 | ) Error!void { |
| 692 | 748 | if (mem.startsWith(u8, str, crypt_format.prefix)) { |
| 693 | | return CryptFormatHasher.verify(str, password); |
| 749 | return CryptFormatHasher.verify(str, password, options.silently_truncate_password); |
| 694 | 750 | } else { |
| 695 | | return PhcFormatHasher.verify(str, password); |
| 751 | return PhcFormatHasher.verify(str, password, options.silently_truncate_password); |
| 696 | 752 | } |
| 697 | 753 | } |
| 698 | 754 | |
| ... | ... | @@ -707,11 +763,12 @@ test "bcrypt codec" { |
| 707 | 763 | } |
| 708 | 764 | |
| 709 | 765 | test "bcrypt crypt format" { |
| 710 | | const hash_options = HashOptions{ |
| 766 | var hash_options = HashOptions{ |
| 711 | 767 | .params = .{ .rounds_log = 5 }, |
| 712 | 768 | .encoding = .crypt, |
| 769 | .silently_truncate_password = false, |
| 713 | 770 | }; |
| 714 | | const verify_options = VerifyOptions{}; |
| 771 | var verify_options = VerifyOptions{}; |
| 715 | 772 | |
| 716 | 773 | var buf: [hash_length]u8 = undefined; |
| 717 | 774 | const s = try strHash("password", hash_options, &buf); |
| ... | ... | @@ -724,10 +781,18 @@ test "bcrypt crypt format" { |
| 724 | 781 | ); |
| 725 | 782 | |
| 726 | 783 | var long_buf: [hash_length]u8 = undefined; |
| 727 | | const long_s = try strHash("password" ** 100, hash_options, &long_buf); |
| 784 | var long_s = try strHash("password" ** 100, hash_options, &long_buf); |
| 728 | 785 | |
| 729 | 786 | try testing.expect(mem.startsWith(u8, long_s, crypt_format.prefix)); |
| 730 | 787 | try strVerify(long_s, "password" ** 100, verify_options); |
| 788 | try testing.expectError( |
| 789 | error.PasswordVerificationFailed, |
| 790 | strVerify(long_s, "password" ** 101, verify_options), |
| 791 | ); |
| 792 | |
| 793 | hash_options.silently_truncate_password = true; |
| 794 | verify_options.silently_truncate_password = true; |
| 795 | long_s = try strHash("password" ** 100, hash_options, &long_buf); |
| 731 | 796 | try strVerify(long_s, "password" ** 101, verify_options); |
| 732 | 797 | |
| 733 | 798 | try strVerify( |
| ... | ... | @@ -738,11 +803,12 @@ test "bcrypt crypt format" { |
| 738 | 803 | } |
| 739 | 804 | |
| 740 | 805 | test "bcrypt phc format" { |
| 741 | | const hash_options = HashOptions{ |
| 806 | var hash_options = HashOptions{ |
| 742 | 807 | .params = .{ .rounds_log = 5 }, |
| 743 | 808 | .encoding = .phc, |
| 809 | .silently_truncate_password = false, |
| 744 | 810 | }; |
| 745 | | const verify_options = VerifyOptions{}; |
| 811 | var verify_options = VerifyOptions{}; |
| 746 | 812 | const prefix = "$bcrypt$"; |
| 747 | 813 | |
| 748 | 814 | var buf: [hash_length * 2]u8 = undefined; |
| ... | ... | @@ -756,10 +822,18 @@ test "bcrypt phc format" { |
| 756 | 822 | ); |
| 757 | 823 | |
| 758 | 824 | var long_buf: [hash_length * 2]u8 = undefined; |
| 759 | | const long_s = try strHash("password" ** 100, hash_options, &long_buf); |
| 825 | var long_s = try strHash("password" ** 100, hash_options, &long_buf); |
| 760 | 826 | |
| 761 | 827 | try testing.expect(mem.startsWith(u8, long_s, prefix)); |
| 762 | 828 | try strVerify(long_s, "password" ** 100, verify_options); |
| 829 | try testing.expectError( |
| 830 | error.PasswordVerificationFailed, |
| 831 | strVerify(long_s, "password" ** 101, verify_options), |
| 832 | ); |
| 833 | |
| 834 | hash_options.silently_truncate_password = true; |
| 835 | verify_options.silently_truncate_password = true; |
| 836 | long_s = try strHash("password" ** 100, hash_options, &long_buf); |
| 763 | 837 | try strVerify(long_s, "password" ** 101, verify_options); |
| 764 | 838 | |
| 765 | 839 | try strVerify( |