LibCrypto+LibWeb: Refactor HKDF and PBKDF2 classes with OpenSSL

This commit is contained in:
devgianlu 2025-02-16 17:38:58 +01:00 committed by Ali Mohammad Pur
commit e90d2a5713
Notes: github-actions[bot] 2025-02-24 10:12:08 +00:00
11 changed files with 210 additions and 185 deletions

View 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;
}
}

View file

@ -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;
};
}

View 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;
}
}

View file

@ -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;
};
}