mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-08-18 08:20:44 +00:00
LibCrypto+LibWeb: Refactor HKDF
and PBKDF2
classes with OpenSSL
This commit is contained in:
parent
6d29a32fad
commit
e90d2a5713
Notes:
github-actions[bot]
2025-02-24 10:12:08 +00:00
Author: https://github.com/devgianlu
Commit: e90d2a5713
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3671
Reviewed-by: https://github.com/ADKaster
Reviewed-by: https://github.com/alimpfard
11 changed files with 210 additions and 185 deletions
45
Libraries/LibCrypto/Hash/HKDF.cpp
Normal file
45
Libraries/LibCrypto/Hash/HKDF.cpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibCrypto/Hash/HKDF.h>
|
||||
#include <LibCrypto/OpenSSL.h>
|
||||
|
||||
#include <openssl/core_names.h>
|
||||
#include <openssl/kdf.h>
|
||||
#include <openssl/params.h>
|
||||
|
||||
namespace Crypto::Hash {
|
||||
|
||||
HKDF::HKDF(HashKind hash_kind)
|
||||
: m_kdf(EVP_KDF_fetch(nullptr, "HKDF", nullptr))
|
||||
, m_hash_kind(hash_kind)
|
||||
{
|
||||
}
|
||||
|
||||
ErrorOr<ByteBuffer> HKDF::derive_key(Optional<ReadonlyBytes> maybe_salt, ReadonlyBytes key, ReadonlyBytes info, u32 key_length_bytes)
|
||||
{
|
||||
auto hash_name = TRY(hash_kind_to_openssl_digest_name(m_hash_kind));
|
||||
|
||||
auto ctx = TRY(OpenSSL_KDF_CTX::wrap(EVP_KDF_CTX_new(m_kdf)));
|
||||
|
||||
OSSL_PARAM params[] = {
|
||||
OSSL_PARAM_utf8_string(OSSL_KDF_PARAM_DIGEST, const_cast<char*>(hash_name.characters_without_null_termination()), hash_name.length()),
|
||||
OSSL_PARAM_octet_string(OSSL_KDF_PARAM_KEY, const_cast<u8*>(key.data()), key.size()),
|
||||
OSSL_PARAM_octet_string(OSSL_KDF_PARAM_INFO, const_cast<u8*>(info.data()), info.size()),
|
||||
OSSL_PARAM_END,
|
||||
OSSL_PARAM_END,
|
||||
};
|
||||
if (maybe_salt.has_value()) {
|
||||
params[3] = OSSL_PARAM_octet_string(OSSL_KDF_PARAM_SALT, const_cast<u8*>(maybe_salt->data()), maybe_salt->size());
|
||||
}
|
||||
|
||||
auto buf = TRY(ByteBuffer::create_uninitialized(key_length_bytes));
|
||||
OPENSSL_TRY(EVP_KDF_derive(ctx.ptr(), buf.data(), key_length_bytes, params));
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,88 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2023, stelar7 <dudedbz@gmail.com>
|
||||
* Copyright (c) 2024, Ben Wiederhake <BenWiederhake.GitHub@gmx.de>
|
||||
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibCrypto/Authentication/HMAC.h>
|
||||
#include <LibCrypto/Hash/HashManager.h>
|
||||
|
||||
namespace Crypto::Hash {
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc5869#section-2
|
||||
template<typename HashT>
|
||||
class HKDF {
|
||||
public:
|
||||
using HashType = HashT;
|
||||
using DigestType = typename HashType::DigestType;
|
||||
using HMACType = typename Crypto::Authentication::HMAC<HashType>;
|
||||
HKDF(HashKind hash_kind);
|
||||
|
||||
~HKDF()
|
||||
{
|
||||
EVP_KDF_free(m_kdf);
|
||||
}
|
||||
|
||||
// Note: The output is different for a salt of length zero and an absent salt,
|
||||
// so Optional<ReadonlyBytes> really is the correct type.
|
||||
static ErrorOr<ByteBuffer> derive_key(Optional<ReadonlyBytes> maybe_salt, ReadonlyBytes input_keying_material, ReadonlyBytes info, u32 output_key_length)
|
||||
{
|
||||
if (output_key_length > 255 * DigestType::Size) {
|
||||
return Error::from_string_view("requested output_key_length is too large"sv);
|
||||
}
|
||||
// Note that it feels like we should also refuse to run with output_key_length == 0,
|
||||
// but the spec allows this.
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc5869#section-2.1
|
||||
// Note that in the extract step, 'IKM' is used as the HMAC input, not as the HMAC key.
|
||||
|
||||
// salt: optional salt value (a non-secret random value); if not provided, it is set to a string of HashLen zeros.
|
||||
ByteBuffer salt_buffer;
|
||||
auto salt = maybe_salt.value_or_lazy_evaluated([&] {
|
||||
salt_buffer.resize(DigestType::Size, ByteBuffer::ZeroFillNewElements::Yes);
|
||||
return salt_buffer.bytes();
|
||||
});
|
||||
HMACType hmac_salt(salt);
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc5869#section-2.2
|
||||
// PRK = HMAC-Hash(salt, IKM)
|
||||
auto prk_digest = hmac_salt.process(input_keying_material);
|
||||
auto prk = prk_digest.bytes();
|
||||
ASSERT(prk.size() == DigestType::Size);
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc5869#section-2.3
|
||||
// N = ceil(L/HashLen)
|
||||
auto num_iterations = ceil_div(static_cast<size_t>(output_key_length), DigestType::Size);
|
||||
// T = T(1) | T(2) | T(3) | ... | T(N)
|
||||
ByteBuffer output_buffer;
|
||||
// where:
|
||||
// T(0) = empty string (zero length)
|
||||
// T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
|
||||
// T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
|
||||
// T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)
|
||||
HMACType hmac_prk(prk);
|
||||
// In iteration i we compute T(i), and deduce T(i - 1) from 'output_buffer'.
|
||||
// Hence, we do not need to run i == 0.
|
||||
// INVARIANT: At the beginning of each iteration, hmac_prk is freshly reset.
|
||||
// For the first iteration, this is given by the constructor of HMAC.
|
||||
for (size_t i = 1; i < 1 + num_iterations; ++i) {
|
||||
if (i > 1) {
|
||||
auto t_i_minus_one = output_buffer.bytes().slice_from_end(DigestType::Size);
|
||||
hmac_prk.update(t_i_minus_one);
|
||||
}
|
||||
hmac_prk.update(info);
|
||||
u8 const pad_byte = static_cast<u8>(i & 0xff);
|
||||
hmac_prk.update(ReadonlyBytes(&pad_byte, 1));
|
||||
auto t_i_digest = hmac_prk.digest();
|
||||
output_buffer.append(t_i_digest.bytes());
|
||||
}
|
||||
|
||||
// OKM = first L octets of T
|
||||
ASSERT(output_buffer.size() >= output_key_length);
|
||||
output_buffer.trim(output_key_length, false);
|
||||
|
||||
// 5. Output the derived key DK
|
||||
return { output_buffer };
|
||||
}
|
||||
ErrorOr<ByteBuffer> derive_key(Optional<ReadonlyBytes> maybe_salt, ReadonlyBytes input_keying_material, ReadonlyBytes info, u32 key_length_bytes);
|
||||
|
||||
private:
|
||||
HKDF() = delete;
|
||||
EVP_KDF* m_kdf;
|
||||
HashKind m_hash_kind;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
42
Libraries/LibCrypto/Hash/PBKDF2.cpp
Normal file
42
Libraries/LibCrypto/Hash/PBKDF2.cpp
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibCrypto/Hash/PBKDF2.h>
|
||||
#include <LibCrypto/OpenSSL.h>
|
||||
|
||||
#include <openssl/core_names.h>
|
||||
#include <openssl/kdf.h>
|
||||
#include <openssl/params.h>
|
||||
|
||||
namespace Crypto::Hash {
|
||||
|
||||
PBKDF2::PBKDF2(HashKind hash_kind)
|
||||
: m_kdf(EVP_KDF_fetch(nullptr, "PBKDF2", nullptr))
|
||||
, m_hash_kind(hash_kind)
|
||||
{
|
||||
}
|
||||
|
||||
ErrorOr<ByteBuffer> PBKDF2::derive_key(ReadonlyBytes password, ReadonlyBytes salt, u32 iterations, u32 key_length_bytes)
|
||||
{
|
||||
auto hash_name = TRY(hash_kind_to_openssl_digest_name(m_hash_kind));
|
||||
|
||||
auto ctx = TRY(OpenSSL_KDF_CTX::wrap(EVP_KDF_CTX_new(m_kdf)));
|
||||
|
||||
OSSL_PARAM params[] = {
|
||||
OSSL_PARAM_utf8_string(OSSL_KDF_PARAM_DIGEST, const_cast<char*>(hash_name.characters_without_null_termination()), hash_name.length()),
|
||||
OSSL_PARAM_octet_string(OSSL_KDF_PARAM_PASSWORD, const_cast<u8*>(password.data()), password.size()),
|
||||
OSSL_PARAM_octet_string(OSSL_KDF_PARAM_SALT, const_cast<u8*>(salt.data()), salt.size()),
|
||||
OSSL_PARAM_uint32(OSSL_KDF_PARAM_ITER, &iterations),
|
||||
OSSL_PARAM_END,
|
||||
};
|
||||
|
||||
auto buf = TRY(ByteBuffer::create_uninitialized(key_length_bytes));
|
||||
OPENSSL_TRY(EVP_KDF_derive(ctx.ptr(), buf.data(), key_length_bytes, params));
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,84 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2023, stelar7 <dudedbz@gmail.com>
|
||||
* Copyright (c) 2025, Altomani Gianluca <altomanigianluca@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Math.h>
|
||||
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
|
||||
#include <LibCrypto/Hash/HashManager.h>
|
||||
|
||||
namespace Crypto::Hash {
|
||||
|
||||
// https://www.rfc-editor.org/rfc/rfc2898#section-5.2
|
||||
class PBKDF2 {
|
||||
public:
|
||||
template<typename PRF>
|
||||
static ErrorOr<ByteBuffer> derive_key(ReadonlyBytes password, ReadonlyBytes salt, u32 iterations, u32 key_length_bytes)
|
||||
requires requires(PRF t) {
|
||||
t.digest_size();
|
||||
}
|
||||
PBKDF2(HashKind hash_kind);
|
||||
|
||||
~PBKDF2()
|
||||
{
|
||||
PRF prf(password);
|
||||
|
||||
// Note: hLen denotes the length in octets of the pseudorandom function output
|
||||
u32 h_len = prf.digest_size();
|
||||
|
||||
// 1. If dkLen > (2^32 - 1) * hLen, output "derived key too long" and stop.
|
||||
if (key_length_bytes > (AK::pow(2.0, 32.0) - 1) * h_len)
|
||||
return Error::from_string_literal("derived key too long");
|
||||
|
||||
// 2 . Let l be the number of hLen-octet blocks in the derived key rounding up,
|
||||
// and let r be the number of octets in the last block
|
||||
u32 l = AK::ceil_div(key_length_bytes, h_len);
|
||||
u32 r = key_length_bytes - (l - 1) * h_len;
|
||||
|
||||
// 3. For each block of the derived key apply the function F defined
|
||||
// below to the password P, the salt S, the iteration count c, and
|
||||
// the block index to compute the block:
|
||||
|
||||
ByteBuffer ui = TRY(ByteBuffer::create_zeroed(h_len));
|
||||
ByteBuffer ti = TRY(ByteBuffer::create_zeroed(h_len));
|
||||
ByteBuffer key = TRY(ByteBuffer::create_zeroed(key_length_bytes));
|
||||
|
||||
// T_i = F (P, S, c, i)
|
||||
u8 iteration_bytes[4];
|
||||
for (u32 i = 1; i <= l; i++) {
|
||||
iteration_bytes[3] = i;
|
||||
iteration_bytes[2] = ((i >> 8) & 0xff);
|
||||
iteration_bytes[1] = ((i >> 16) & 0xff);
|
||||
iteration_bytes[0] = ((i >> 24) & 0xff);
|
||||
|
||||
prf.update(salt);
|
||||
prf.update(ReadonlyBytes { iteration_bytes, 4 });
|
||||
auto digest = prf.digest();
|
||||
ui.overwrite(0, digest.immutable_data(), h_len);
|
||||
ti.overwrite(0, digest.immutable_data(), h_len);
|
||||
|
||||
// U_1 = PRF (P, S || INT (i))
|
||||
// U_j = PRF (P, U_{j-1})
|
||||
|
||||
// F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c
|
||||
for (u32 j = 2; j <= iterations; j++) {
|
||||
prf.update(ui.bytes());
|
||||
auto digest_inner = prf.digest();
|
||||
ui.overwrite(0, digest_inner.immutable_data(), h_len);
|
||||
|
||||
UnsignedBigInteger ti_temp = UnsignedBigInteger::import_data(ti.data(), ti.size());
|
||||
UnsignedBigInteger ui_temp = UnsignedBigInteger::import_data(ui.data(), ui.size());
|
||||
UnsignedBigInteger r_temp = ti_temp.bitwise_xor(ui_temp);
|
||||
|
||||
r_temp.export_data(ti.bytes());
|
||||
}
|
||||
|
||||
// 4. Concatenate the blocks and extract the first dkLen octets to produce a derived key DK:
|
||||
key.overwrite((i - 1) * h_len, ti.data(), i == l ? r : h_len);
|
||||
}
|
||||
|
||||
// 5. Output the derived key DK
|
||||
return key;
|
||||
EVP_KDF_free(m_kdf);
|
||||
}
|
||||
|
||||
ErrorOr<ByteBuffer> derive_key(ReadonlyBytes password, ReadonlyBytes salt, u32 iterations, u32 key_length_bytes);
|
||||
|
||||
private:
|
||||
EVP_KDF* m_kdf;
|
||||
HashKind m_hash_kind;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue