mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
crypto.pwhash.bcrypt: make silently_truncate_password a member of Params (#22792)
* bcrypt: make silently_truncate_password a member of Params This removes the need for having both `bcrypt()` and `bcryptWithTruncation()` in the public API. And whether truncation happens or not becomes even more explicit. * Update crypto benchmark
This commit is contained in:
parent
bd237bced4
commit
8d824dfdd0
@ -412,21 +412,19 @@ pub const Params = struct {
|
|||||||
/// log2 of the number of rounds
|
/// log2 of the number of rounds
|
||||||
rounds_log: u6,
|
rounds_log: u6,
|
||||||
|
|
||||||
|
/// As originally defined, bcrypt silently truncates passwords to 72 bytes.
|
||||||
|
/// In order to overcome this limitation, if `silently_truncate_password` is set to `false`,
|
||||||
|
/// long passwords will be automatically pre-hashed using HMAC-SHA512 before being passed to bcrypt.
|
||||||
|
/// Only set `silently_truncate_password` to `true` for compatibility with traditional bcrypt implementations,
|
||||||
|
/// or if you want to handle the truncation yourself.
|
||||||
|
silently_truncate_password: bool,
|
||||||
|
|
||||||
/// Minimum recommended parameters according to the
|
/// Minimum recommended parameters according to the
|
||||||
/// [OWASP cheat sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html).
|
/// [OWASP cheat sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html).
|
||||||
pub const owasp = Self{ .rounds_log = 10 };
|
pub const owasp = Self{ .rounds_log = 10, .silently_truncate_password = false };
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Compute a hash of a password using 2^rounds_log rounds of the bcrypt key stretching function.
|
fn bcryptWithTruncation(
|
||||||
/// bcrypt is a computationally expensive and cache-hard function, explicitly designed to slow down exhaustive searches.
|
|
||||||
///
|
|
||||||
/// The function returns the hash as a `dk_length` byte array, that doesn't include anything besides the hash output.
|
|
||||||
///
|
|
||||||
/// For a generic key-derivation function, use `bcrypt.pbkdf()` instead.
|
|
||||||
///
|
|
||||||
/// IMPORTANT: by design, bcrypt silently truncates passwords to 72 bytes.
|
|
||||||
/// If this is an issue for your application, use `bcryptWithoutTruncation` instead.
|
|
||||||
pub fn bcrypt(
|
|
||||||
password: []const u8,
|
password: []const u8,
|
||||||
salt: [salt_length]u8,
|
salt: [salt_length]u8,
|
||||||
params: Params,
|
params: Params,
|
||||||
@ -465,17 +463,15 @@ pub fn bcrypt(
|
|||||||
///
|
///
|
||||||
/// The function returns the hash as a `dk_length` byte array, that doesn't include anything besides the hash output.
|
/// The function returns the hash as a `dk_length` byte array, that doesn't include anything besides the hash output.
|
||||||
///
|
///
|
||||||
/// For a generic key-derivation function, use `bcrypt.pbkdf()` instead.
|
/// This function was designed for password storage, not for key derivation.
|
||||||
///
|
/// For key derivation, use `bcrypt.pbkdf()` or `bcrypt.opensshKdf()` instead.
|
||||||
/// This function is identical to `bcrypt`, except that it doesn't silently truncate passwords.
|
pub fn bcrypt(
|
||||||
/// Instead, passwords longer than 72 bytes are pre-hashed using HMAC-SHA512 before being passed to bcrypt.
|
|
||||||
pub fn bcryptWithoutTruncation(
|
|
||||||
password: []const u8,
|
password: []const u8,
|
||||||
salt: [salt_length]u8,
|
salt: [salt_length]u8,
|
||||||
params: Params,
|
params: Params,
|
||||||
) [dk_length]u8 {
|
) [dk_length]u8 {
|
||||||
if (password.len <= 72) {
|
if (password.len <= 72 or params.silently_truncate_password) {
|
||||||
return bcrypt(password, salt, params);
|
return bcryptWithTruncation(password, salt, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
var pre_hash: [HmacSha512.mac_length]u8 = undefined;
|
var pre_hash: [HmacSha512.mac_length]u8 = undefined;
|
||||||
@ -485,7 +481,7 @@ pub fn bcryptWithoutTruncation(
|
|||||||
var pre_hash_b64: [Encoder.calcSize(pre_hash.len)]u8 = undefined;
|
var pre_hash_b64: [Encoder.calcSize(pre_hash.len)]u8 = undefined;
|
||||||
_ = Encoder.encode(&pre_hash_b64, &pre_hash);
|
_ = Encoder.encode(&pre_hash_b64, &pre_hash);
|
||||||
|
|
||||||
return bcrypt(&pre_hash_b64, salt, params);
|
return bcryptWithTruncation(&pre_hash_b64, salt, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
const pbkdf_prf = struct {
|
const pbkdf_prf = struct {
|
||||||
@ -629,9 +625,8 @@ const crypt_format = struct {
|
|||||||
password: []const u8,
|
password: []const u8,
|
||||||
salt: [salt_length]u8,
|
salt: [salt_length]u8,
|
||||||
params: Params,
|
params: Params,
|
||||||
silently_truncate_password: bool,
|
|
||||||
) [hash_length]u8 {
|
) [hash_length]u8 {
|
||||||
var dk = if (silently_truncate_password) bcrypt(password, salt, params) else bcryptWithoutTruncation(password, salt, params);
|
var dk = bcrypt(password, salt, params);
|
||||||
|
|
||||||
var salt_str: [salt_str_length]u8 = undefined;
|
var salt_str: [salt_str_length]u8 = undefined;
|
||||||
_ = Codec.Encoder.encode(salt_str[0..], salt[0..]);
|
_ = Codec.Encoder.encode(salt_str[0..], salt[0..]);
|
||||||
@ -666,13 +661,12 @@ const PhcFormatHasher = struct {
|
|||||||
fn create(
|
fn create(
|
||||||
password: []const u8,
|
password: []const u8,
|
||||||
params: Params,
|
params: Params,
|
||||||
silently_truncate_password: bool,
|
|
||||||
buf: []u8,
|
buf: []u8,
|
||||||
) HasherError![]const u8 {
|
) HasherError![]const u8 {
|
||||||
var salt: [salt_length]u8 = undefined;
|
var salt: [salt_length]u8 = undefined;
|
||||||
crypto.random.bytes(&salt);
|
crypto.random.bytes(&salt);
|
||||||
|
|
||||||
const hash = if (silently_truncate_password) bcrypt(password, salt, params) else bcryptWithoutTruncation(password, salt, params);
|
const hash = bcrypt(password, salt, params);
|
||||||
|
|
||||||
return phc_format.serialize(HashResult{
|
return phc_format.serialize(HashResult{
|
||||||
.alg_id = alg_id,
|
.alg_id = alg_id,
|
||||||
@ -694,8 +688,11 @@ const PhcFormatHasher = struct {
|
|||||||
if (hash_result.salt.len != salt_length or hash_result.hash.len != dk_length)
|
if (hash_result.salt.len != salt_length or hash_result.hash.len != dk_length)
|
||||||
return HasherError.InvalidEncoding;
|
return HasherError.InvalidEncoding;
|
||||||
|
|
||||||
const params = Params{ .rounds_log = hash_result.r };
|
const params = Params{
|
||||||
const hash = if (silently_truncate_password) bcrypt(password, hash_result.salt.buf, params) else bcryptWithoutTruncation(password, hash_result.salt.buf, params);
|
.rounds_log = hash_result.r,
|
||||||
|
.silently_truncate_password = silently_truncate_password,
|
||||||
|
};
|
||||||
|
const hash = bcrypt(password, hash_result.salt.buf, params);
|
||||||
const expected_hash = hash_result.hash.constSlice();
|
const expected_hash = hash_result.hash.constSlice();
|
||||||
|
|
||||||
if (!mem.eql(u8, &hash, expected_hash)) return HasherError.PasswordVerificationFailed;
|
if (!mem.eql(u8, &hash, expected_hash)) return HasherError.PasswordVerificationFailed;
|
||||||
@ -711,7 +708,6 @@ const CryptFormatHasher = struct {
|
|||||||
fn create(
|
fn create(
|
||||||
password: []const u8,
|
password: []const u8,
|
||||||
params: Params,
|
params: Params,
|
||||||
silently_truncate_password: bool,
|
|
||||||
buf: []u8,
|
buf: []u8,
|
||||||
) HasherError![]const u8 {
|
) HasherError![]const u8 {
|
||||||
if (buf.len < pwhash_str_length) return HasherError.NoSpaceLeft;
|
if (buf.len < pwhash_str_length) return HasherError.NoSpaceLeft;
|
||||||
@ -719,7 +715,7 @@ const CryptFormatHasher = struct {
|
|||||||
var salt: [salt_length]u8 = undefined;
|
var salt: [salt_length]u8 = undefined;
|
||||||
crypto.random.bytes(&salt);
|
crypto.random.bytes(&salt);
|
||||||
|
|
||||||
const hash = crypt_format.strHashInternal(password, salt, params, silently_truncate_password);
|
const hash = crypt_format.strHashInternal(password, salt, params);
|
||||||
@memcpy(buf[0..hash.len], &hash);
|
@memcpy(buf[0..hash.len], &hash);
|
||||||
|
|
||||||
return buf[0..pwhash_str_length];
|
return buf[0..pwhash_str_length];
|
||||||
@ -742,7 +738,10 @@ const CryptFormatHasher = struct {
|
|||||||
var salt: [salt_length]u8 = undefined;
|
var salt: [salt_length]u8 = undefined;
|
||||||
crypt_format.Codec.Decoder.decode(salt[0..], salt_str[0..]) catch return HasherError.InvalidEncoding;
|
crypt_format.Codec.Decoder.decode(salt[0..], salt_str[0..]) catch return HasherError.InvalidEncoding;
|
||||||
|
|
||||||
const wanted_s = crypt_format.strHashInternal(password, salt, .{ .rounds_log = rounds_log }, silently_truncate_password);
|
const wanted_s = crypt_format.strHashInternal(password, salt, .{
|
||||||
|
.rounds_log = rounds_log,
|
||||||
|
.silently_truncate_password = silently_truncate_password,
|
||||||
|
});
|
||||||
if (!mem.eql(u8, wanted_s[0..], str[0..])) return HasherError.PasswordVerificationFailed;
|
if (!mem.eql(u8, wanted_s[0..], str[0..])) return HasherError.PasswordVerificationFailed;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -755,9 +754,6 @@ pub const HashOptions = struct {
|
|||||||
params: Params,
|
params: Params,
|
||||||
/// Encoding to use for the output of the hash function.
|
/// Encoding to use for the output of the hash function.
|
||||||
encoding: pwhash.Encoding,
|
encoding: pwhash.Encoding,
|
||||||
/// Whether to silently truncate the password to 72 bytes, or pre-hash the password when it is longer.
|
|
||||||
/// The default is `true`, for compatibility with the original bcrypt implementation.
|
|
||||||
silently_truncate_password: bool = true,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Compute a hash of a password using 2^rounds_log rounds of the bcrypt key stretching function.
|
/// Compute a hash of a password using 2^rounds_log rounds of the bcrypt key stretching function.
|
||||||
@ -773,8 +769,8 @@ pub fn strHash(
|
|||||||
out: []u8,
|
out: []u8,
|
||||||
) Error![]const u8 {
|
) Error![]const u8 {
|
||||||
switch (options.encoding) {
|
switch (options.encoding) {
|
||||||
.phc => return PhcFormatHasher.create(password, options.params, options.silently_truncate_password, out),
|
.phc => return PhcFormatHasher.create(password, options.params, out),
|
||||||
.crypt => return CryptFormatHasher.create(password, options.params, options.silently_truncate_password, out),
|
.crypt => return CryptFormatHasher.create(password, options.params, out),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -783,7 +779,7 @@ pub const VerifyOptions = struct {
|
|||||||
/// For `bcrypt`, that can be left to `null`.
|
/// For `bcrypt`, that can be left to `null`.
|
||||||
allocator: ?mem.Allocator = null,
|
allocator: ?mem.Allocator = null,
|
||||||
/// Whether to silently truncate the password to 72 bytes, or pre-hash the password when it is longer.
|
/// Whether to silently truncate the password to 72 bytes, or pre-hash the password when it is longer.
|
||||||
silently_truncate_password: bool = false,
|
silently_truncate_password: bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Verify that a previously computed hash is valid for a given password.
|
/// Verify that a previously computed hash is valid for a given password.
|
||||||
@ -811,11 +807,10 @@ test "bcrypt codec" {
|
|||||||
|
|
||||||
test "bcrypt crypt format" {
|
test "bcrypt crypt format" {
|
||||||
var hash_options = HashOptions{
|
var hash_options = HashOptions{
|
||||||
.params = .{ .rounds_log = 5 },
|
.params = .{ .rounds_log = 5, .silently_truncate_password = false },
|
||||||
.encoding = .crypt,
|
.encoding = .crypt,
|
||||||
.silently_truncate_password = false,
|
|
||||||
};
|
};
|
||||||
var verify_options = VerifyOptions{};
|
var verify_options = VerifyOptions{ .silently_truncate_password = false };
|
||||||
|
|
||||||
var buf: [hash_length]u8 = undefined;
|
var buf: [hash_length]u8 = undefined;
|
||||||
const s = try strHash("password", hash_options, &buf);
|
const s = try strHash("password", hash_options, &buf);
|
||||||
@ -837,7 +832,7 @@ test "bcrypt crypt format" {
|
|||||||
strVerify(long_s, "password" ** 101, verify_options),
|
strVerify(long_s, "password" ** 101, verify_options),
|
||||||
);
|
);
|
||||||
|
|
||||||
hash_options.silently_truncate_password = true;
|
hash_options.params.silently_truncate_password = true;
|
||||||
verify_options.silently_truncate_password = true;
|
verify_options.silently_truncate_password = true;
|
||||||
long_s = try strHash("password" ** 100, hash_options, &long_buf);
|
long_s = try strHash("password" ** 100, hash_options, &long_buf);
|
||||||
try strVerify(long_s, "password" ** 101, verify_options);
|
try strVerify(long_s, "password" ** 101, verify_options);
|
||||||
@ -851,11 +846,10 @@ test "bcrypt crypt format" {
|
|||||||
|
|
||||||
test "bcrypt phc format" {
|
test "bcrypt phc format" {
|
||||||
var hash_options = HashOptions{
|
var hash_options = HashOptions{
|
||||||
.params = .{ .rounds_log = 5 },
|
.params = .{ .rounds_log = 5, .silently_truncate_password = false },
|
||||||
.encoding = .phc,
|
.encoding = .phc,
|
||||||
.silently_truncate_password = false,
|
|
||||||
};
|
};
|
||||||
var verify_options = VerifyOptions{};
|
var verify_options = VerifyOptions{ .silently_truncate_password = false };
|
||||||
const prefix = "$bcrypt$";
|
const prefix = "$bcrypt$";
|
||||||
|
|
||||||
var buf: [hash_length * 2]u8 = undefined;
|
var buf: [hash_length * 2]u8 = undefined;
|
||||||
@ -878,7 +872,7 @@ test "bcrypt phc format" {
|
|||||||
strVerify(long_s, "password" ** 101, verify_options),
|
strVerify(long_s, "password" ** 101, verify_options),
|
||||||
);
|
);
|
||||||
|
|
||||||
hash_options.silently_truncate_password = true;
|
hash_options.params.silently_truncate_password = true;
|
||||||
verify_options.silently_truncate_password = true;
|
verify_options.silently_truncate_password = true;
|
||||||
long_s = try strHash("password" ** 100, hash_options, &long_buf);
|
long_s = try strHash("password" ** 100, hash_options, &long_buf);
|
||||||
try strVerify(long_s, "password" ** 101, verify_options);
|
try strVerify(long_s, "password" ** 101, verify_options);
|
||||||
|
|||||||
@ -391,7 +391,7 @@ const CryptoPwhash = struct {
|
|||||||
params: *const anyopaque,
|
params: *const anyopaque,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
};
|
};
|
||||||
const bcrypt_params = crypto.pwhash.bcrypt.Params{ .rounds_log = 8 };
|
const bcrypt_params = crypto.pwhash.bcrypt.Params{ .rounds_log = 8, .silently_truncate_password = true };
|
||||||
const pwhashes = [_]CryptoPwhash{
|
const pwhashes = [_]CryptoPwhash{
|
||||||
.{
|
.{
|
||||||
.ty = crypto.pwhash.bcrypt,
|
.ty = crypto.pwhash.bcrypt,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user