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
parent 6d29a32fad
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

@ -25,7 +25,9 @@ set(SOURCES
Curves/EdwardsCurve.cpp
Curves/SECPxxxr1.cpp
Hash/BLAKE2b.cpp
Hash/HKDF.cpp
Hash/MD5.cpp
Hash/PBKDF2.cpp
Hash/SHA1.cpp
Hash/SHA2.cpp
NumberTheory/ModularFunctions.cpp

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

View file

@ -44,4 +44,20 @@ ErrorOr<UnsignedBigInteger> openssl_bignum_to_unsigned_big_integer(OpenSSL_BN co
return UnsignedBigInteger::import_data(buf.bytes().data(), size);
}
ErrorOr<StringView> hash_kind_to_openssl_digest_name(Hash::HashKind hash)
{
switch (hash) {
case Hash::HashKind::SHA1:
return "SHA1"sv;
case Hash::HashKind::SHA256:
return "SHA256"sv;
case Hash::HashKind::SHA384:
return "SHA384"sv;
case Hash::HashKind::SHA512:
return "SHA512"sv;
default:
return Error::from_string_literal("Unsupported hash kind");
}
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024, Altomani Gianluca <altomanigianluca@gmail.com>
* Copyright (c) 2024-2025, Altomani Gianluca <altomanigianluca@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -9,6 +9,7 @@
#include <AK/Error.h>
#include <AK/Format.h>
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
#include <LibCrypto/Hash/HashManager.h>
#include <LibCrypto/OpenSSLForward.h>
inline int openssl_print_errors(char const* str, size_t len, [[maybe_unused]] void* u)
@ -106,9 +107,15 @@ public:
static ErrorOr<OpenSSL_MD_CTX> create();
};
class OpenSSL_KDF_CTX {
OPENSSL_WRAPPER_CLASS(OpenSSL_KDF_CTX, EVP_KDF_CTX, EVP_KDF_CTX);
};
#undef OPENSSL_WRAPPER_CLASS
ErrorOr<OpenSSL_BN> unsigned_big_integer_to_openssl_bignum(UnsignedBigInteger const& integer);
ErrorOr<UnsignedBigInteger> openssl_bignum_to_unsigned_big_integer(OpenSSL_BN const& bn);
ErrorOr<StringView> hash_kind_to_openssl_digest_name(Hash::HashKind hash);
}

View file

@ -13,6 +13,8 @@ typedef struct evp_md_st EVP_MD;
typedef struct evp_md_ctx_st EVP_MD_CTX;
typedef struct evp_pkey_st EVP_PKEY;
typedef struct evp_pkey_ctx_st EVP_PKEY_CTX;
typedef struct evp_kdf_st EVP_KDF;
typedef struct evp_kdf_ctx_st EVP_KDF_CTX;
void ERR_print_errors_cb(int (*cb)(char const* str, size_t len, void* u), void* u);
@ -24,8 +26,8 @@ int EVP_DigestFinal_ex(EVP_MD_CTX*, unsigned char*, unsigned int*);
int EVP_MD_CTX_copy_ex(EVP_MD_CTX*, EVP_MD_CTX const*);
void EVP_PKEY_CTX_free(EVP_PKEY_CTX*);
void EVP_PKEY_free(EVP_PKEY*);
void EVP_KDF_CTX_free(EVP_KDF_CTX* ctx);
void EVP_KDF_free(EVP_KDF* kdf);
void BN_free(BIGNUM*);
}

View file

@ -6657,26 +6657,34 @@ WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> HKDF::derive_bits(AlgorithmParams
// all major browsers instead raise a TypeError, for example:
// "Failed to execute 'deriveBits' on 'SubtleCrypto': HkdfParams: salt: Not a BufferSource"
// Because we are forced by neither peer pressure nor the spec, we don't support it either.
auto const& hash_algorithm = TRY(normalized_algorithm.hash.name(realm.vm()));
ErrorOr<ByteBuffer> result = Error::from_string_literal("noop error");
if (hash_algorithm == "SHA-1") {
result = ::Crypto::Hash::HKDF<::Crypto::Hash::SHA1>::derive_key(Optional<ReadonlyBytes>(normalized_algorithm.salt), key_derivation_key, normalized_algorithm.info, *length_optional / 8);
} else if (hash_algorithm == "SHA-256") {
result = ::Crypto::Hash::HKDF<::Crypto::Hash::SHA256>::derive_key(Optional<ReadonlyBytes>(normalized_algorithm.salt), key_derivation_key, normalized_algorithm.info, *length_optional / 8);
} else if (hash_algorithm == "SHA-384") {
result = ::Crypto::Hash::HKDF<::Crypto::Hash::SHA384>::derive_key(Optional<ReadonlyBytes>(normalized_algorithm.salt), key_derivation_key, normalized_algorithm.info, *length_optional / 8);
} else if (hash_algorithm == "SHA-512") {
result = ::Crypto::Hash::HKDF<::Crypto::Hash::SHA512>::derive_key(Optional<ReadonlyBytes>(normalized_algorithm.salt), key_derivation_key, normalized_algorithm.info, *length_optional / 8);
} else {
return WebIDL::NotSupportedError::create(m_realm, MUST(String::formatted("Invalid hash function '{}'", hash_algorithm)));
// Note: Check for zero length early because our implementation doesn't support it.
if (*length_optional == 0) {
return TRY(JS::ArrayBuffer::create(realm, static_cast<size_t>(0)));
}
auto const& hash_algorithm = TRY(normalized_algorithm.hash.name(realm.vm()));
auto hash_kind = TRY([&] -> WebIDL::ExceptionOr<::Crypto::Hash::HashKind> {
if (hash_algorithm == "SHA-1")
return ::Crypto::Hash::HashKind::SHA1;
if (hash_algorithm == "SHA-256")
return ::Crypto::Hash::HashKind::SHA256;
if (hash_algorithm == "SHA-384")
return ::Crypto::Hash::HashKind::SHA384;
if (hash_algorithm == "SHA-512")
return ::Crypto::Hash::HashKind::SHA512;
return WebIDL::NotSupportedError::create(m_realm, MUST(String::formatted("Invalid hash function '{}'", hash_algorithm)));
}());
::Crypto::Hash::HKDF hkdf(hash_kind);
auto maybe_result = hkdf.derive_key(Optional<ReadonlyBytes>(normalized_algorithm.salt), key_derivation_key, normalized_algorithm.info, *length_optional / 8);
// 4. If the key derivation operation fails, then throw an OperationError.
if (result.is_error())
if (maybe_result.is_error())
return WebIDL::OperationError::create(realm, "Failed to derive key"_string);
// 5. Return result
return JS::ArrayBuffer::create(realm, result.release_value());
return JS::ArrayBuffer::create(realm, maybe_result.release_value());
}
WebIDL::ExceptionOr<JS::Value> HKDF::get_key_length(AlgorithmParams const&)
@ -6708,32 +6716,32 @@ WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> PBKDF2::derive_bits(AlgorithmParam
// the contents of the salt attribute of normalizedAlgorithm as the salt, S,
// the value of the iterations attribute of normalizedAlgorithm as the iteration count, c,
// and length divided by 8 as the intended key length, dkLen.
ErrorOr<ByteBuffer> result = Error::from_string_literal("noop error");
auto password = key->handle().get<ByteBuffer>();
auto salt = normalized_algorithm.salt;
auto iterations = normalized_algorithm.iterations;
auto derived_key_length_bytes = *length_optional / 8;
if (hash_algorithm == "SHA-1") {
result = ::Crypto::Hash::PBKDF2::derive_key<::Crypto::Authentication::HMAC<::Crypto::Hash::SHA1>>(password, salt, iterations, derived_key_length_bytes);
} else if (hash_algorithm == "SHA-256") {
result = ::Crypto::Hash::PBKDF2::derive_key<::Crypto::Authentication::HMAC<::Crypto::Hash::SHA256>>(password, salt, iterations, derived_key_length_bytes);
} else if (hash_algorithm == "SHA-384") {
result = ::Crypto::Hash::PBKDF2::derive_key<::Crypto::Authentication::HMAC<::Crypto::Hash::SHA384>>(password, salt, iterations, derived_key_length_bytes);
} else if (hash_algorithm == "SHA-512") {
result = ::Crypto::Hash::PBKDF2::derive_key<::Crypto::Authentication::HMAC<::Crypto::Hash::SHA512>>(password, salt, iterations, derived_key_length_bytes);
} else {
auto hash_kind = TRY([&] -> WebIDL::ExceptionOr<::Crypto::Hash::HashKind> {
if (hash_algorithm == "SHA-1")
return ::Crypto::Hash::HashKind::SHA1;
if (hash_algorithm == "SHA-256")
return ::Crypto::Hash::HashKind::SHA256;
if (hash_algorithm == "SHA-384")
return ::Crypto::Hash::HashKind::SHA384;
if (hash_algorithm == "SHA-512")
return ::Crypto::Hash::HashKind::SHA512;
return WebIDL::NotSupportedError::create(m_realm, MUST(String::formatted("Invalid hash function '{}'", hash_algorithm)));
}
}());
::Crypto::Hash::PBKDF2 pbkdf2(hash_kind);
auto maybe_result = pbkdf2.derive_key(password, salt, iterations, derived_key_length_bytes);
// 5. If the key derivation operation fails, then throw an OperationError.
if (result.is_error())
if (maybe_result.is_error())
return WebIDL::OperationError::create(realm, "Failed to derive key"_string);
// 6. Return result
return JS::ArrayBuffer::create(realm, result.release_value());
return JS::ArrayBuffer::create(realm, maybe_result.release_value());
}
// https://w3c.github.io/webcrypto/#pbkdf2-operations

View file

@ -6,15 +6,13 @@
*/
#include <LibCrypto/Hash/HKDF.h>
#include <LibCrypto/Hash/SHA1.h>
#include <LibCrypto/Hash/SHA2.h>
#include <LibTest/TestCase.h>
TEST_CASE(test_error_extreme_output_key_length)
{
auto result = Crypto::Hash::HKDF<Crypto::Hash::SHA1>::derive_key(Optional<ReadonlyBytes>(), ReadonlyBytes(), ReadonlyBytes(), 999999);
Crypto::Hash::HKDF hkdf(Crypto::Hash::HashKind::SHA1);
auto result = hkdf.derive_key(Optional<ReadonlyBytes>(), ReadonlyBytes(), ReadonlyBytes(), 999999);
EXPECT(result.is_error());
EXPECT_EQ(result.error().string_literal(), "requested output_key_length is too large");
}
// https://www.rfc-editor.org/rfc/rfc5869#appendix-A.1
@ -35,7 +33,8 @@ TEST_CASE(test_vector_A_1)
// L = 42
size_t const output_key_length = 42;
auto result = TRY_OR_FAIL(Crypto::Hash::HKDF<Crypto::Hash::SHA256>::derive_key(Optional<ReadonlyBytes>(salt), ikm, info, output_key_length));
Crypto::Hash::HKDF hkdf(Crypto::Hash::HashKind::SHA256);
auto result = TRY_OR_FAIL(hkdf.derive_key(Optional<ReadonlyBytes>(salt), ikm, info, output_key_length));
// Intermediate value 'PRK' isn't checked explicitly. However, any bit error would have
// an avalanche effect on the output, so if the output is correct then PRK can be presumed correct, too.
u8 const expected_output_key[] = {
@ -76,7 +75,8 @@ TEST_CASE(test_vector_A_2)
static_assert(sizeof(info) == 80);
size_t const output_key_length = 82;
auto result = TRY_OR_FAIL(Crypto::Hash::HKDF<Crypto::Hash::SHA256>::derive_key(Optional<ReadonlyBytes>(salt), ikm, info, output_key_length));
Crypto::Hash::HKDF hkdf(Crypto::Hash::HashKind::SHA256);
auto result = TRY_OR_FAIL(hkdf.derive_key(Optional<ReadonlyBytes>(salt), ikm, info, output_key_length));
// Intermediate value 'PRK' isn't checked explicitly. However, any bit error would have
// an avalanche effect on the output, so if the output is correct then PRK can be presumed correct, too.
u8 const expected_output_key[] = {
@ -101,8 +101,10 @@ TEST_CASE(test_vector_A_3)
static_assert(sizeof(ikm) == 22);
size_t const output_key_length = 42;
Crypto::Hash::HKDF hkdf(Crypto::Hash::HashKind::SHA256);
// Note: This creates a salt of zero bytes.
auto result = TRY_OR_FAIL(Crypto::Hash::HKDF<Crypto::Hash::SHA256>::derive_key(Optional<ReadonlyBytes>(ReadonlyBytes()), ikm, ReadonlyBytes(), output_key_length));
auto result = TRY_OR_FAIL(hkdf.derive_key(Optional<ReadonlyBytes>(ReadonlyBytes()), ikm, ReadonlyBytes(), output_key_length));
// Intermediate value 'PRK' isn't checked explicitly. However, any bit error would have
// an avalanche effect on the output, so if the output is correct then PRK can be presumed correct, too.
u8 const expected_output_key[] = {
@ -127,7 +129,8 @@ TEST_CASE(test_vector_A_4)
u8 const info[] = { 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9 };
static_assert(sizeof(info) == 10);
auto result = TRY_OR_FAIL(Crypto::Hash::HKDF<Crypto::Hash::SHA1>::derive_key(Optional<ReadonlyBytes>(salt), ikm, info, output_key_length));
Crypto::Hash::HKDF hkdf(Crypto::Hash::HashKind::SHA1);
auto result = TRY_OR_FAIL(hkdf.derive_key(Optional<ReadonlyBytes>(salt), ikm, info, output_key_length));
// Intermediate value 'PRK' isn't checked explicitly. However, any bit error would have
// an avalanche effect on the output, so if the output is correct then PRK can be presumed correct, too.
u8 const expected_output_key[] = {
@ -168,7 +171,8 @@ TEST_CASE(test_vector_A_5)
static_assert(sizeof(info) == 80);
size_t const output_key_length = 82;
auto result = TRY_OR_FAIL(Crypto::Hash::HKDF<Crypto::Hash::SHA1>::derive_key(Optional<ReadonlyBytes>(salt), ikm, info, output_key_length));
Crypto::Hash::HKDF hkdf(Crypto::Hash::HashKind::SHA1);
auto result = TRY_OR_FAIL(hkdf.derive_key(Optional<ReadonlyBytes>(salt), ikm, info, output_key_length));
// Intermediate value 'PRK' isn't checked explicitly. However, any bit error would have
// an avalanche effect on the output, so if the output is correct then PRK can be presumed correct, too.
u8 const expected_output_key[] = {
@ -193,8 +197,10 @@ TEST_CASE(test_vector_A_6)
static_assert(sizeof(ikm) == 22);
size_t const output_key_length = 42;
Crypto::Hash::HKDF hkdf(Crypto::Hash::HashKind::SHA1);
// Note: This creates a salt of length zero.
auto result = TRY_OR_FAIL(Crypto::Hash::HKDF<Crypto::Hash::SHA1>::derive_key(Optional<ReadonlyBytes>(ReadonlyBytes()), ikm, ReadonlyBytes(), output_key_length));
auto result = TRY_OR_FAIL(hkdf.derive_key(Optional<ReadonlyBytes>(ReadonlyBytes()), ikm, ReadonlyBytes(), output_key_length));
// Intermediate value 'PRK' isn't checked explicitly. However, any bit error would have
// an avalanche effect on the output, so if the output is correct then PRK can be presumed correct, too.
u8 const expected_output_key[] = {
@ -216,8 +222,10 @@ TEST_CASE(test_vector_A_7)
static_assert(sizeof(ikm) == 22);
size_t const output_key_length = 42;
Crypto::Hash::HKDF hkdf(Crypto::Hash::HashKind::SHA1);
// Note: This creates a "None" salt.
auto result = TRY_OR_FAIL(Crypto::Hash::HKDF<Crypto::Hash::SHA1>::derive_key(Optional<ReadonlyBytes>(), ikm, ReadonlyBytes(), output_key_length));
auto result = TRY_OR_FAIL(hkdf.derive_key(Optional<ReadonlyBytes>(), ikm, ReadonlyBytes(), output_key_length));
// Intermediate value 'PRK' isn't checked explicitly. However, any bit error would have
// an avalanche effect on the output, so if the output is correct then PRK can be presumed correct, too.
u8 const expected_output_key[] = {

View file

@ -4,10 +4,7 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibCrypto/Authentication/HMAC.h>
#include <LibCrypto/Hash/PBKDF2.h>
#include <LibCrypto/Hash/SHA1.h>
#include <LibCrypto/Hash/SHA2.h>
#include <LibTest/TestCase.h>
// https://www.rfc-editor.org/rfc/rfc6070#section-2
@ -27,7 +24,8 @@ TEST_CASE(test_vector_1_sha1)
u32 iterations = 1;
u32 derived_key_length_bytes = 20;
auto result = MUST(Crypto::Hash::PBKDF2::derive_key<Crypto::Authentication::HMAC<Crypto::Hash::SHA1>>(password, salt, iterations, derived_key_length_bytes));
Crypto::Hash::PBKDF2 pbkdf2(Crypto::Hash::HashKind::SHA1);
auto result = MUST(pbkdf2.derive_key(password, salt, iterations, derived_key_length_bytes));
EXPECT_EQ(result, expected.span());
}
@ -48,7 +46,8 @@ TEST_CASE(test_vector_2_sha1)
u32 iterations = 2;
u32 derived_key_length_bytes = 20;
auto result = MUST(Crypto::Hash::PBKDF2::derive_key<Crypto::Authentication::HMAC<Crypto::Hash::SHA1>>(password, salt, iterations, derived_key_length_bytes));
Crypto::Hash::PBKDF2 pbkdf2(Crypto::Hash::HashKind::SHA1);
auto result = MUST(pbkdf2.derive_key(password, salt, iterations, derived_key_length_bytes));
EXPECT_EQ(result, expected.span());
}
@ -69,7 +68,8 @@ TEST_CASE(test_vector_1_sha256)
u32 iterations = 1;
u32 derived_key_length_bytes = 20;
auto result = MUST(Crypto::Hash::PBKDF2::derive_key<Crypto::Authentication::HMAC<Crypto::Hash::SHA256>>(password, salt, iterations, derived_key_length_bytes));
Crypto::Hash::PBKDF2 pbkdf2(Crypto::Hash::HashKind::SHA256);
auto result = MUST(pbkdf2.derive_key(password, salt, iterations, derived_key_length_bytes));
EXPECT_EQ(result, expected.span());
}
@ -90,7 +90,8 @@ TEST_CASE(test_vector_2_sha256)
u32 iterations = 2;
u32 derived_key_length_bytes = 20;
auto result = MUST(Crypto::Hash::PBKDF2::derive_key<Crypto::Authentication::HMAC<Crypto::Hash::SHA256>>(password, salt, iterations, derived_key_length_bytes));
Crypto::Hash::PBKDF2 pbkdf2(Crypto::Hash::HashKind::SHA256);
auto result = MUST(pbkdf2.derive_key(password, salt, iterations, derived_key_length_bytes));
EXPECT_EQ(result, expected.span());
}
@ -111,7 +112,8 @@ TEST_CASE(test_vector_3_sha256)
u32 iterations = 4096;
u32 derived_key_length_bytes = 20;
auto result = MUST(Crypto::Hash::PBKDF2::derive_key<Crypto::Authentication::HMAC<Crypto::Hash::SHA256>>(password, salt, iterations, derived_key_length_bytes));
Crypto::Hash::PBKDF2 pbkdf2(Crypto::Hash::HashKind::SHA256);
auto result = MUST(pbkdf2.derive_key(password, salt, iterations, derived_key_length_bytes));
EXPECT_EQ(result, expected.span());
}
@ -139,7 +141,8 @@ TEST_CASE(test_vector_4_sha256)
u32 iterations = 4096;
u32 derived_key_length_bytes = 25;
auto result = MUST(Crypto::Hash::PBKDF2::derive_key<Crypto::Authentication::HMAC<Crypto::Hash::SHA256>>(password, salt, iterations, derived_key_length_bytes));
Crypto::Hash::PBKDF2 pbkdf2(Crypto::Hash::HashKind::SHA256);
auto result = MUST(pbkdf2.derive_key(password, salt, iterations, derived_key_length_bytes));
EXPECT_EQ(result, expected.span());
}
@ -160,7 +163,8 @@ TEST_CASE(test_vector_5_sha256)
u32 iterations = 4096;
u32 derived_key_length_bytes = 16;
auto result = MUST(Crypto::Hash::PBKDF2::derive_key<Crypto::Authentication::HMAC<Crypto::Hash::SHA256>>(password, salt, iterations, derived_key_length_bytes));
Crypto::Hash::PBKDF2 pbkdf2(Crypto::Hash::HashKind::SHA256);
auto result = MUST(pbkdf2.derive_key(password, salt, iterations, derived_key_length_bytes));
EXPECT_EQ(result, expected.span());
}