Skip to content

Commit 286ecb3

Browse files
committed
LibCrypto+LibWeb: Refactor HKDF and PBKDF2 classes with OpenSSL
1 parent abfb3f8 commit 286ecb3

File tree

11 files changed

+208
-183
lines changed

11 files changed

+208
-183
lines changed

Libraries/LibCrypto/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ set(SOURCES
2525
Curves/EdwardsCurve.cpp
2626
Curves/SECPxxxr1.cpp
2727
Hash/BLAKE2b.cpp
28+
Hash/HKDF.cpp
2829
Hash/MD5.cpp
30+
Hash/PBKDF2.cpp
2931
Hash/SHA1.cpp
3032
Hash/SHA2.cpp
3133
NumberTheory/ModularFunctions.cpp

Libraries/LibCrypto/Hash/HKDF.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright (c) 2025, Altomani Gianluca <[email protected]>
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#include <LibCrypto/Hash/HKDF.h>
8+
#include <LibCrypto/OpenSSL.h>
9+
10+
#include <openssl/core_names.h>
11+
#include <openssl/kdf.h>
12+
#include <openssl/params.h>
13+
14+
namespace Crypto::Hash {
15+
16+
HKDF::HKDF(HashKind hash_kind)
17+
: m_kdf(EVP_KDF_fetch(nullptr, "HKDF", nullptr))
18+
, m_hash_kind(hash_kind)
19+
{
20+
}
21+
22+
ErrorOr<ByteBuffer> HKDF::derive_key(Optional<ReadonlyBytes> maybe_salt, ReadonlyBytes key, ReadonlyBytes info, u32 key_length_bytes)
23+
{
24+
auto hash_name = TRY(hash_kind_to_openssl_digest_name(m_hash_kind));
25+
26+
auto ctx = TRY(OpenSSL_KDF_CTX::wrap(EVP_KDF_CTX_new(m_kdf)));
27+
28+
OSSL_PARAM params[] = {
29+
OSSL_PARAM_utf8_string(OSSL_KDF_PARAM_DIGEST, const_cast<char*>(hash_name.characters_without_null_termination()), hash_name.length()),
30+
OSSL_PARAM_octet_string(OSSL_KDF_PARAM_KEY, const_cast<u8*>(key.data()), key.size()),
31+
OSSL_PARAM_octet_string(OSSL_KDF_PARAM_INFO, const_cast<u8*>(info.data()), info.size()),
32+
OSSL_PARAM_END,
33+
OSSL_PARAM_END,
34+
};
35+
if (maybe_salt.has_value()) {
36+
params[3] = OSSL_PARAM_octet_string(OSSL_KDF_PARAM_SALT, const_cast<u8*>(maybe_salt->data()), maybe_salt->size());
37+
}
38+
39+
auto buf = TRY(ByteBuffer::create_uninitialized(key_length_bytes));
40+
OPENSSL_TRY(EVP_KDF_derive(ctx.ptr(), buf.data(), key_length_bytes, params));
41+
42+
return buf;
43+
}
44+
45+
}

Libraries/LibCrypto/Hash/HKDF.h

Lines changed: 11 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,33 @@
11
/*
22
* Copyright (c) 2023, stelar7 <[email protected]>
33
* Copyright (c) 2024, Ben Wiederhake <[email protected]>
4+
* Copyright (c) 2025, Altomani Gianluca <[email protected]>
45
*
56
* SPDX-License-Identifier: BSD-2-Clause
67
*/
78

89
#pragma once
910

10-
#include <LibCrypto/Authentication/HMAC.h>
11+
#include <LibCrypto/Hash/HashManager.h>
1112

1213
namespace Crypto::Hash {
1314

14-
// https://www.rfc-editor.org/rfc/rfc5869#section-2
15-
template<typename HashT>
1615
class HKDF {
1716
public:
18-
using HashType = HashT;
19-
using DigestType = typename HashType::DigestType;
20-
using HMACType = typename Crypto::Authentication::HMAC<HashType>;
17+
HKDF(HashKind hash_kind);
2118

22-
// Note: The output is different for a salt of length zero and an absent salt,
23-
// so Optional<ReadonlyBytes> really is the correct type.
24-
static ErrorOr<ByteBuffer> derive_key(Optional<ReadonlyBytes> maybe_salt, ReadonlyBytes input_keying_material, ReadonlyBytes info, u32 output_key_length)
19+
~HKDF()
2520
{
26-
if (output_key_length > 255 * DigestType::Size) {
27-
return Error::from_string_view("requested output_key_length is too large"sv);
28-
}
29-
// Note that it feels like we should also refuse to run with output_key_length == 0,
30-
// but the spec allows this.
31-
32-
// https://www.rfc-editor.org/rfc/rfc5869#section-2.1
33-
// Note that in the extract step, 'IKM' is used as the HMAC input, not as the HMAC key.
34-
35-
// salt: optional salt value (a non-secret random value); if not provided, it is set to a string of HashLen zeros.
36-
ByteBuffer salt_buffer;
37-
auto salt = maybe_salt.value_or_lazy_evaluated([&] {
38-
salt_buffer.resize(DigestType::Size, ByteBuffer::ZeroFillNewElements::Yes);
39-
return salt_buffer.bytes();
40-
});
41-
HMACType hmac_salt(salt);
42-
43-
// https://www.rfc-editor.org/rfc/rfc5869#section-2.2
44-
// PRK = HMAC-Hash(salt, IKM)
45-
auto prk_digest = hmac_salt.process(input_keying_material);
46-
auto prk = prk_digest.bytes();
47-
ASSERT(prk.size() == DigestType::Size);
48-
49-
// https://www.rfc-editor.org/rfc/rfc5869#section-2.3
50-
// N = ceil(L/HashLen)
51-
auto num_iterations = ceil_div(static_cast<size_t>(output_key_length), DigestType::Size);
52-
// T = T(1) | T(2) | T(3) | ... | T(N)
53-
ByteBuffer output_buffer;
54-
// where:
55-
// T(0) = empty string (zero length)
56-
// T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
57-
// T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
58-
// T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)
59-
HMACType hmac_prk(prk);
60-
// In iteration i we compute T(i), and deduce T(i - 1) from 'output_buffer'.
61-
// Hence, we do not need to run i == 0.
62-
// INVARIANT: At the beginning of each iteration, hmac_prk is freshly reset.
63-
// For the first iteration, this is given by the constructor of HMAC.
64-
for (size_t i = 1; i < 1 + num_iterations; ++i) {
65-
if (i > 1) {
66-
auto t_i_minus_one = output_buffer.bytes().slice_from_end(DigestType::Size);
67-
hmac_prk.update(t_i_minus_one);
68-
}
69-
hmac_prk.update(info);
70-
u8 const pad_byte = static_cast<u8>(i & 0xff);
71-
hmac_prk.update(ReadonlyBytes(&pad_byte, 1));
72-
auto t_i_digest = hmac_prk.digest();
73-
output_buffer.append(t_i_digest.bytes());
74-
}
75-
76-
// OKM = first L octets of T
77-
ASSERT(output_buffer.size() >= output_key_length);
78-
output_buffer.trim(output_key_length, false);
79-
80-
// 5. Output the derived key DK
81-
return { output_buffer };
21+
EVP_KDF_free(m_kdf);
8222
}
8323

24+
// Note: The output is different for a salt of length zero and an absent salt,
25+
// so Optional<ReadonlyBytes> really is the correct type.
26+
ErrorOr<ByteBuffer> derive_key(Optional<ReadonlyBytes> maybe_salt, ReadonlyBytes input_keying_material, ReadonlyBytes info, u32 key_length_bytes);
27+
8428
private:
85-
HKDF() = delete;
29+
EVP_KDF* m_kdf;
30+
HashKind m_hash_kind;
8631
};
8732

8833
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright (c) 2025, Altomani Gianluca <[email protected]>
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#include <LibCrypto/Hash/PBKDF2.h>
8+
#include <LibCrypto/OpenSSL.h>
9+
10+
#include <openssl/core_names.h>
11+
#include <openssl/kdf.h>
12+
#include <openssl/params.h>
13+
14+
namespace Crypto::Hash {
15+
16+
PBKDF2::PBKDF2(HashKind hash_kind)
17+
: m_kdf(EVP_KDF_fetch(nullptr, "PBKDF2", nullptr))
18+
, m_hash_kind(hash_kind)
19+
{
20+
}
21+
22+
ErrorOr<ByteBuffer> PBKDF2::derive_key(ReadonlyBytes password, ReadonlyBytes salt, u32 iterations, u32 key_length_bytes)
23+
{
24+
auto hash_name = TRY(hash_kind_to_openssl_digest_name(m_hash_kind));
25+
26+
auto ctx = TRY(OpenSSL_KDF_CTX::wrap(EVP_KDF_CTX_new(m_kdf)));
27+
28+
OSSL_PARAM params[] = {
29+
OSSL_PARAM_utf8_string(OSSL_KDF_PARAM_DIGEST, const_cast<char*>(hash_name.characters_without_null_termination()), hash_name.length()),
30+
OSSL_PARAM_octet_string(OSSL_KDF_PARAM_PASSWORD, const_cast<u8*>(password.data()), password.size()),
31+
OSSL_PARAM_octet_string(OSSL_KDF_PARAM_SALT, const_cast<u8*>(salt.data()), salt.size()),
32+
OSSL_PARAM_uint32(OSSL_KDF_PARAM_ITER, &iterations),
33+
OSSL_PARAM_END,
34+
};
35+
36+
auto buf = TRY(ByteBuffer::create_uninitialized(key_length_bytes));
37+
OPENSSL_TRY(EVP_KDF_derive(ctx.ptr(), buf.data(), key_length_bytes, params));
38+
39+
return buf;
40+
}
41+
42+
}

Libraries/LibCrypto/Hash/PBKDF2.h

Lines changed: 11 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,30 @@
11
/*
22
* Copyright (c) 2023, stelar7 <[email protected]>
3+
* Copyright (c) 2025, Altomani Gianluca <[email protected]>
34
*
45
* SPDX-License-Identifier: BSD-2-Clause
56
*/
67

78
#pragma once
89

9-
#include <AK/Math.h>
10-
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
10+
#include <LibCrypto/Hash/HashManager.h>
1111

1212
namespace Crypto::Hash {
1313

14-
// https://www.rfc-editor.org/rfc/rfc2898#section-5.2
1514
class PBKDF2 {
1615
public:
17-
template<typename PRF>
18-
static ErrorOr<ByteBuffer> derive_key(ReadonlyBytes password, ReadonlyBytes salt, u32 iterations, u32 key_length_bytes)
19-
requires requires(PRF t) {
20-
t.digest_size();
21-
}
22-
{
23-
PRF prf(password);
24-
25-
// Note: hLen denotes the length in octets of the pseudorandom function output
26-
u32 h_len = prf.digest_size();
27-
28-
// 1. If dkLen > (2^32 - 1) * hLen, output "derived key too long" and stop.
29-
if (key_length_bytes > (AK::pow(2.0, 32.0) - 1) * h_len)
30-
return Error::from_string_literal("derived key too long");
31-
32-
// 2 . Let l be the number of hLen-octet blocks in the derived key rounding up,
33-
// and let r be the number of octets in the last block
34-
u32 l = AK::ceil_div(key_length_bytes, h_len);
35-
u32 r = key_length_bytes - (l - 1) * h_len;
36-
37-
// 3. For each block of the derived key apply the function F defined
38-
// below to the password P, the salt S, the iteration count c, and
39-
// the block index to compute the block:
40-
41-
ByteBuffer ui = TRY(ByteBuffer::create_zeroed(h_len));
42-
ByteBuffer ti = TRY(ByteBuffer::create_zeroed(h_len));
43-
ByteBuffer key = TRY(ByteBuffer::create_zeroed(key_length_bytes));
44-
45-
// T_i = F (P, S, c, i)
46-
u8 iteration_bytes[4];
47-
for (u32 i = 1; i <= l; i++) {
48-
iteration_bytes[3] = i;
49-
iteration_bytes[2] = ((i >> 8) & 0xff);
50-
iteration_bytes[1] = ((i >> 16) & 0xff);
51-
iteration_bytes[0] = ((i >> 24) & 0xff);
16+
PBKDF2(HashKind hash_kind);
5217

53-
prf.update(salt);
54-
prf.update(ReadonlyBytes { iteration_bytes, 4 });
55-
auto digest = prf.digest();
56-
ui.overwrite(0, digest.immutable_data(), h_len);
57-
ti.overwrite(0, digest.immutable_data(), h_len);
58-
59-
// U_1 = PRF (P, S || INT (i))
60-
// U_j = PRF (P, U_{j-1})
61-
62-
// F (P, S, c, i) = U_1 \xor U_2 \xor ... \xor U_c
63-
for (u32 j = 2; j <= iterations; j++) {
64-
prf.update(ui.bytes());
65-
auto digest_inner = prf.digest();
66-
ui.overwrite(0, digest_inner.immutable_data(), h_len);
67-
68-
UnsignedBigInteger ti_temp = UnsignedBigInteger::import_data(ti.data(), ti.size());
69-
UnsignedBigInteger ui_temp = UnsignedBigInteger::import_data(ui.data(), ui.size());
70-
UnsignedBigInteger r_temp = ti_temp.bitwise_xor(ui_temp);
71-
72-
r_temp.export_data(ti.bytes());
73-
}
18+
~PBKDF2()
19+
{
20+
EVP_KDF_free(m_kdf);
21+
}
7422

75-
// 4. Concatenate the blocks and extract the first dkLen octets to produce a derived key DK:
76-
key.overwrite((i - 1) * h_len, ti.data(), i == l ? r : h_len);
77-
}
23+
ErrorOr<ByteBuffer> derive_key(ReadonlyBytes password, ReadonlyBytes salt, u32 iterations, u32 key_length_bytes);
7824

79-
// 5. Output the derived key DK
80-
return key;
81-
}
25+
private:
26+
EVP_KDF* m_kdf;
27+
HashKind m_hash_kind;
8228
};
8329

8430
}

Libraries/LibCrypto/OpenSSL.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,20 @@ ErrorOr<UnsignedBigInteger> openssl_bignum_to_unsigned_big_integer(OpenSSL_BN co
4444
return UnsignedBigInteger::import_data(buf.bytes().data(), size);
4545
}
4646

47+
ErrorOr<StringView> hash_kind_to_openssl_digest_name(Hash::HashKind hash)
48+
{
49+
switch (hash) {
50+
case Hash::HashKind::SHA1:
51+
return "SHA1"sv;
52+
case Hash::HashKind::SHA256:
53+
return "SHA256"sv;
54+
case Hash::HashKind::SHA384:
55+
return "SHA384"sv;
56+
case Hash::HashKind::SHA512:
57+
return "SHA512"sv;
58+
default:
59+
return Error::from_string_literal("Unsupported hash kind");
60+
}
61+
}
62+
4763
}

Libraries/LibCrypto/OpenSSL.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024, Altomani Gianluca <[email protected]>
2+
* Copyright (c) 2024-2025, Altomani Gianluca <[email protected]>
33
*
44
* SPDX-License-Identifier: BSD-2-Clause
55
*/
@@ -9,6 +9,7 @@
99
#include <AK/Error.h>
1010
#include <AK/Format.h>
1111
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
12+
#include <LibCrypto/Hash/HashManager.h>
1213
#include <LibCrypto/OpenSSLForward.h>
1314

1415
inline int openssl_print_errors(char const* str, size_t len, [[maybe_unused]] void* u)
@@ -106,9 +107,15 @@ class OpenSSL_MD_CTX {
106107
static ErrorOr<OpenSSL_MD_CTX> create();
107108
};
108109

110+
class OpenSSL_KDF_CTX {
111+
OPENSSL_WRAPPER_CLASS(OpenSSL_KDF_CTX, EVP_KDF_CTX, EVP_KDF_CTX);
112+
};
113+
109114
#undef OPENSSL_WRAPPER_CLASS
110115

111116
ErrorOr<OpenSSL_BN> unsigned_big_integer_to_openssl_bignum(UnsignedBigInteger const& integer);
112117
ErrorOr<UnsignedBigInteger> openssl_bignum_to_unsigned_big_integer(OpenSSL_BN const& bn);
113118

119+
ErrorOr<StringView> hash_kind_to_openssl_digest_name(Hash::HashKind hash);
120+
114121
}

Libraries/LibCrypto/OpenSSLForward.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ typedef struct evp_md_st EVP_MD;
1313
typedef struct evp_md_ctx_st EVP_MD_CTX;
1414
typedef struct evp_pkey_st EVP_PKEY;
1515
typedef struct evp_pkey_ctx_st EVP_PKEY_CTX;
16+
typedef struct evp_kdf_st EVP_KDF;
17+
typedef struct evp_kdf_ctx_st EVP_KDF_CTX;
1618

1719
void ERR_print_errors_cb(int (*cb)(char const* str, size_t len, void* u), void* u);
1820

@@ -24,8 +26,8 @@ int EVP_DigestFinal_ex(EVP_MD_CTX*, unsigned char*, unsigned int*);
2426
int EVP_MD_CTX_copy_ex(EVP_MD_CTX*, EVP_MD_CTX const*);
2527

2628
void EVP_PKEY_CTX_free(EVP_PKEY_CTX*);
27-
2829
void EVP_PKEY_free(EVP_PKEY*);
29-
30+
void EVP_KDF_CTX_free(EVP_KDF_CTX* ctx);
31+
void EVP_KDF_free(EVP_KDF* kdf);
3032
void BN_free(BIGNUM*);
3133
}

0 commit comments

Comments
 (0)