| ... | @@ -563,15 +563,57 @@ const pbkdf_prf = struct { | ... | @@ -563,15 +563,57 @@ const pbkdf_prf = struct { |
| 563 | }; | 563 | }; |
| 564 | | 564 | |
| 565 | /// bcrypt-pbkdf is a key derivation function based on bcrypt. | 565 | /// bcrypt-pbkdf is a key derivation function based on bcrypt. |
| 566 | /// This is the function used in OpenSSH to derive encryption keys from passphrases. | | |
| 567 | /// | | |
| 568 | /// This implementation is compatible with the OpenBSD implementation (https://github.com/openbsd/src/blob/master/lib/libutil/bcrypt_pbkdf.c). | | |
| 569 | /// | 566 | /// |
| 570 | /// Unlike the password hashing function `bcrypt`, this function doesn't silently truncate passwords longer than 72 bytes. | 567 | /// Unlike the password hashing function `bcrypt`, this function doesn't silently truncate passwords longer than 72 bytes. |
| 571 | pub fn pbkdf(pass: []const u8, salt: []const u8, key: []u8, rounds: u32) !void { | 568 | pub fn pbkdf(pass: []const u8, salt: []const u8, key: []u8, rounds: u32) !void { |
| 572 | try crypto.pwhash.pbkdf2(key, pass, salt, rounds, pbkdf_prf); | 569 | try crypto.pwhash.pbkdf2(key, pass, salt, rounds, pbkdf_prf); |
| 573 | } | 570 | } |
| 574 | | 571 | |
| | 572 | /// The function used in OpenSSH to derive encryption keys from passphrases. |
| | 573 | /// |
| | 574 | /// This implementation is compatible with the OpenBSD implementation (https://github.com/openbsd/src/blob/master/lib/libutil/bcrypt_pbkdf.c). |
| | 575 | pub fn opensshKdf(pass: []const u8, salt: []const u8, key: []u8, rounds: u32) !void { |
| | 576 | var tmp: [32]u8 = undefined; |
| | 577 | var tmp2: [32]u8 = undefined; |
| | 578 | if (rounds < 1 or pass.len == 0 or salt.len == 0 or key.len == 0 or key.len > tmp.len * tmp.len) { |
| | 579 | return error.InvalidInput; |
| | 580 | } |
| | 581 | var sha2pass: [Sha512.digest_length]u8 = undefined; |
| | 582 | Sha512.hash(pass, &sha2pass, .{}); |
| | 583 | const stride = (key.len + tmp.len - 1) / tmp.len; |
| | 584 | var amt = (key.len + stride - 1) / stride; |
| | 585 | if (math.shr(usize, key.len, 32) >= amt) { |
| | 586 | return error.InvalidInput; |
| | 587 | } |
| | 588 | var key_remainder = key.len; |
| | 589 | var count: u32 = 1; |
| | 590 | while (key_remainder > 0) : (count += 1) { |
| | 591 | var count_salt: [4]u8 = undefined; |
| | 592 | std.mem.writeInt(u32, count_salt[0..], count, .big); |
| | 593 | var sha2salt: [Sha512.digest_length]u8 = undefined; |
| | 594 | var h = Sha512.init(.{}); |
| | 595 | h.update(salt); |
| | 596 | h.update(&count_salt); |
| | 597 | h.final(&sha2salt); |
| | 598 | tmp2 = pbkdf_prf.hash(sha2pass, sha2salt); |
| | 599 | tmp = tmp2; |
| | 600 | for (1..rounds) |_| { |
| | 601 | Sha512.hash(&tmp2, &sha2salt, .{}); |
| | 602 | tmp2 = pbkdf_prf.hash(sha2pass, sha2salt); |
| | 603 | for (&tmp, tmp2) |*o, t| o.* ^= t; |
| | 604 | } |
| | 605 | amt = @min(amt, key_remainder); |
| | 606 | key_remainder -= for (0..amt) |i| { |
| | 607 | const dest = i * stride + (count - 1); |
| | 608 | if (dest >= key.len) break i; |
| | 609 | key[dest] = tmp[i]; |
| | 610 | } else amt; |
| | 611 | } |
| | 612 | crypto.secureZero(u8, &tmp); |
| | 613 | crypto.secureZero(u8, &tmp2); |
| | 614 | crypto.secureZero(u8, &sha2pass); |
| | 615 | } |
| | 616 | |
| 575 | const crypt_format = struct { | 617 | const crypt_format = struct { |
| 576 | /// String prefix for bcrypt | 618 | /// String prefix for bcrypt |
| 577 | pub const prefix = "$2"; | 619 | pub const prefix = "$2"; |
| ... | @@ -847,3 +889,13 @@ test "bcrypt phc format" { | ... | @@ -847,3 +889,13 @@ test "bcrypt phc format" { |
| 847 | verify_options, | 889 | verify_options, |
| 848 | ); | 890 | ); |
| 849 | } | 891 | } |
| | 892 | |
| | 893 | test "openssh kdf" { |
| | 894 | var key: [100]u8 = undefined; |
| | 895 | const pass = "password"; |
| | 896 | const salt = "salt"; |
| | 897 | const rounds = 5; |
| | 898 | try opensshKdf(pass, salt, &key, rounds); |
| | 899 | const expected = [_]u8{ 65, 207, 68, 58, 55, 252, 114, 141, 255, 65, 216, 175, 5, 92, 235, 68, 220, 92, 118, 161, 40, 13, 241, 190, 56, 152, 69, 136, 41, 214, 51, 205, 37, 221, 101, 59, 105, 73, 133, 36, 14, 59, 94, 212, 111, 107, 109, 237, 213, 235, 246, 119, 59, 76, 45, 130, 142, 81, 178, 231, 161, 158, 138, 108, 18, 162, 26, 50, 218, 251, 23, 66, 2, 232, 20, 202, 216, 46, 12, 250, 247, 246, 252, 23, 155, 74, 77, 195, 120, 113, 57, 88, 126, 81, 9, 249, 72, 18, 208, 160 }; |
| | 900 | try testing.expectEqualSlices(u8, &key, &expected); |
| | 901 | } |