diff --git a/include/boost/crypt/hash/detail/hasher_base_512.hpp b/include/boost/crypt/hash/detail/hasher_base_512.hpp index 10b941a7..4b2ac5ec 100644 --- a/include/boost/crypt/hash/detail/hasher_base_512.hpp +++ b/include/boost/crypt/hash/detail/hasher_base_512.hpp @@ -35,6 +35,9 @@ template class hasher_base_512 { +public: + static constexpr boost::crypt::size_t block_size {64U}; + protected: // Use CRTP to make this constexpr with C++14 @@ -46,7 +49,7 @@ class hasher_base_512 BOOST_CRYPT_GPU_ENABLED constexpr auto update(ForwardIter data, boost::crypt::size_t size) noexcept -> hasher_state; boost::crypt::array intermediate_hash_ {}; - boost::crypt::array buffer_ {}; + boost::crypt::array buffer_ {}; boost::crypt::size_t buffer_index_ {}; boost::crypt::size_t low_ {}; boost::crypt::size_t high_ {}; diff --git a/include/boost/crypt/hash/detail/sha3_base.hpp b/include/boost/crypt/hash/detail/sha3_base.hpp index 96e435c1..6e4a4659 100644 --- a/include/boost/crypt/hash/detail/sha3_base.hpp +++ b/include/boost/crypt/hash/detail/sha3_base.hpp @@ -36,13 +36,17 @@ namespace hash_detail { template class sha3_base { +public: + + static constexpr boost::crypt::size_t block_size {200U - 2U * digest_size}; + private: static_assert((!is_xof && (digest_size == 28U || digest_size == 32U || digest_size == 48U || digest_size == 64U)) || is_xof, "Digest size must be 28 (SHA3-224), 32 (SHA3-256), 48 (SHA3-384), or 64(SHA3-512) or this must be an xof"); boost::crypt::array state_array_ {}; - boost::crypt::array buffer_ {}; + boost::crypt::array buffer_ {}; boost::crypt::size_t buffer_index_ {}; bool computed_ {}; bool corrupted_ {}; diff --git a/include/boost/crypt/hash/detail/sha512_base.hpp b/include/boost/crypt/hash/detail/sha512_base.hpp index a41186a5..0f403371 100644 --- a/include/boost/crypt/hash/detail/sha512_base.hpp +++ b/include/boost/crypt/hash/detail/sha512_base.hpp @@ -35,6 +35,10 @@ namespace hash_detail { template class sha512_base final { +public: + + static constexpr boost::crypt::size_t block_size {128U}; + private: static_assert(digest_size == 28U || digest_size == 32U || digest_size == 48U || digest_size == 64U, diff --git a/include/boost/crypt/hash/hmac.hpp b/include/boost/crypt/hash/hmac.hpp new file mode 100644 index 00000000..9982cf3a --- /dev/null +++ b/include/boost/crypt/hash/hmac.hpp @@ -0,0 +1,243 @@ +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_CRYPT_HASH_HMAC_HPP +#define BOOST_CRYPT_HASH_HMAC_HPP + +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace crypt { + +BOOST_CRYPT_EXPORT template +class hmac +{ +public: + + static constexpr boost::crypt::size_t block_size_ {HasherType::block_size}; + using return_type = typename HasherType::return_type; + using key_type = boost::crypt::array; + +private: + + key_type inner_key_ {}; + key_type outer_key_ {}; + HasherType inner_hash_; + HasherType outer_hash_; + bool initialized_ {false}; + bool computed_ {false}; + bool corrupted_ {false}; + +public: + + BOOST_CRYPT_GPU_ENABLED constexpr hmac() noexcept = default; + + template + BOOST_CRYPT_GPU_ENABLED constexpr hmac(ForwardIter key, boost::crypt::size_t size) noexcept { init(key, size); } + + BOOST_CRYPT_GPU_ENABLED constexpr hmac(const key_type& inner_key, const key_type& outer_key) noexcept { init_from_keys(inner_key, outer_key); } + + template + BOOST_CRYPT_GPU_ENABLED constexpr auto init(ForwardIter key, boost::crypt::size_t size) noexcept -> hasher_state; + + BOOST_CRYPT_GPU_ENABLED constexpr auto init_from_keys(const key_type& inner_key, + const key_type& outer_key) noexcept -> hasher_state; + + template + BOOST_CRYPT_GPU_ENABLED constexpr auto process_bytes(ForwardIter data, boost::crypt::size_t size) noexcept -> hasher_state; + + BOOST_CRYPT_GPU_ENABLED constexpr auto get_digest() noexcept -> return_type; + + BOOST_CRYPT_GPU_ENABLED constexpr auto get_outer_key() noexcept -> key_type; + + BOOST_CRYPT_GPU_ENABLED constexpr auto get_inner_key() noexcept -> key_type; +}; + +template +constexpr auto hmac::get_inner_key() noexcept -> key_type +{ + return inner_key_; +} + +template +constexpr auto hmac::get_outer_key() noexcept -> key_type +{ + return outer_key_; +} + +template +constexpr auto +hmac::init_from_keys(const boost::crypt::array &inner_key, + const boost::crypt::array &outer_key) noexcept -> hasher_state +{ + computed_ = false; + corrupted_ = false; + inner_hash_.init(); + outer_hash_.init(); + + inner_key_ = inner_key; + outer_key_ = outer_key; + + const auto inner_result {inner_hash_.process_bytes(inner_key_.begin(), inner_key_.size())}; + const auto outer_result {outer_hash_.process_bytes(outer_key_.begin(), outer_key_.size())}; + + if (BOOST_CRYPT_LIKELY(inner_result == hasher_state::success && outer_result == hasher_state::success)) + { + initialized_ = true; + return hasher_state::success; + } + else + { + // If we have some weird OOM result + // LCOV_EXCL_START + if (inner_result != hasher_state::success) + { + return inner_result; + } + else + { + return outer_result; + } + // LCOV_EXCL_STOP + } +} + +template +constexpr auto hmac::get_digest() noexcept -> return_type +{ + if (computed_) + { + corrupted_ = true; + } + if (corrupted_) + { + return return_type {}; + } + + computed_ = true; + const auto r_inner {inner_hash_.get_digest()}; + outer_hash_.process_bytes(r_inner.begin(), r_inner.size()); + return outer_hash_.get_digest(); +} + +template +template +constexpr auto hmac::process_bytes(ForwardIter data, boost::crypt::size_t size) noexcept -> hasher_state +{ + if (utility::is_null(data) || size == 0U) + { + return hasher_state::null; + } + else if (!initialized_ || corrupted_) + { + return hasher_state::state_error; + } + + const auto status_code {inner_hash_.process_bytes(data, size)}; + if (BOOST_CRYPT_LIKELY(status_code == hasher_state::success)) + { + return hasher_state::success; + } + else + { + // Cannot test 64 and 128 bit OOM + // LCOV_EXCL_START + switch (status_code) + { + case hasher_state::state_error: + corrupted_ = true; + return hasher_state::state_error; + case hasher_state::input_too_long: + corrupted_ = true; + return hasher_state::input_too_long; + default: + BOOST_CRYPT_UNREACHABLE; + } + // LCOV_EXCL_STOP + } +} + +template +template +constexpr auto hmac::init(ForwardIter key, boost::crypt::size_t size) noexcept -> hasher_state +{ + computed_ = false; + corrupted_ = false; + inner_hash_.init(); + outer_hash_.init(); + + boost::crypt::array k0 {}; + + if (utility::is_null(key) || size == 0U) + { + return hasher_state::null; + } + + // Step 1: If the length of K = B set K0 = K. Go to step 4 + // OR + // Step 3: If the length of K < B: append zeros to the end of K. + if (size <= block_size_) + { + auto key_iter {key}; + for (boost::crypt::size_t i {}; i < size; ++i) + { + k0[i] = static_cast(*key_iter++); + } + } + // Step 2: If the length of K > B: hash K to obtain an L byte string + else if (size > block_size_) + { + HasherType hasher; + hasher.process_bytes(key, size); + const auto res {hasher.get_digest()}; + + BOOST_CRYPT_ASSERT(res.size() <= k0.size()); + + for (boost::crypt::size_t i {}; i < res.size(); ++i) + { + k0[i] = res[i]; + } + } + + // Step 4: XOR k0 with ipad to produce a B-byte string K0 ^ ipad + // Step 7: XOR k0 with opad to produce a B-byte string K0 ^ opad + for (boost::crypt::size_t i {}; i < k0.size(); ++i) + { + inner_key_[i] = static_cast(k0[i] ^ static_cast(0x36)); + outer_key_[i] = static_cast(k0[i] ^ static_cast(0x5c)); + } + + const auto inner_result {inner_hash_.process_bytes(inner_key_.begin(), inner_key_.size())}; + const auto outer_result {outer_hash_.process_bytes(outer_key_.begin(), outer_key_.size())}; + + if (BOOST_CRYPT_LIKELY(inner_result == hasher_state::success && outer_result == hasher_state::success)) + { + initialized_ = true; + return hasher_state::success; + } + else + { + // If we have some weird OOM result + // LCOV_EXCL_START + if (inner_result != hasher_state::success) + { + return inner_result; + } + else + { + return outer_result; + } + // LCOV_EXCL_STOP + } +} + +} // namespace crypt +} // namespace boost + +#endif //BOOST_CRYPT_HASH_HMAC_HPP diff --git a/include/boost/crypt/hash/md5.hpp b/include/boost/crypt/hash/md5.hpp index a36ac655..e70cfc68 100644 --- a/include/boost/crypt/hash/md5.hpp +++ b/include/boost/crypt/hash/md5.hpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include diff --git a/include/boost/crypt/hash/sha1.hpp b/include/boost/crypt/hash/sha1.hpp index 7ea87b12..b47016b6 100644 --- a/include/boost/crypt/hash/sha1.hpp +++ b/include/boost/crypt/hash/sha1.hpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include diff --git a/test/Jamfile b/test/Jamfile index a72f7747..15621daf 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -64,6 +64,8 @@ run test_sha3_224.cpp ; run test_shake128.cpp ; run test_shake256.cpp ; +run test_hmac.cpp ; + # NIST standard testing run test_nist_cavs_sha1_monte.cpp ; run test_nist_cavs_sha1_short_long.cpp ; diff --git a/test/test_hmac.cpp b/test/test_hmac.cpp new file mode 100644 index 00000000..93da74f0 --- /dev/null +++ b/test/test_hmac.cpp @@ -0,0 +1,164 @@ +// Copyright 2024 Matt Borland +// Distributed under the Boost Software License, Version 1.0. +// https://www.boost.org/LICENSE_1_0.txt + +#define BOOST_CRYPT_ENABLE_MD5 + +#include +#include +#include +#include +#include +#include + +template +void basic_tests() +{ + boost::crypt::hmac hmac_tester; + const auto state_1 {hmac_tester.init("key", 3)}; + BOOST_TEST(state_1 == boost::crypt::hasher_state::success); + + const char* msg {"The quick brown fox jumps over the lazy dog"}; + const auto state_2 {hmac_tester.process_bytes(msg, std::strlen(msg))}; + BOOST_TEST(state_2 == boost::crypt::hasher_state::success); + + const auto res {hmac_tester.get_digest()}; + + BOOST_CRYPT_IF_CONSTEXPR (boost::crypt::is_same_v) + { + constexpr boost::crypt::array soln = { + 0x80, 0x07, 0x07, 0x13, 0x46, 0x3e, 0x77, 0x49, 0xb9, 0x0c, 0x2d, 0xc2, 0x49, 0x11, 0xe2, 0x75 + }; + + for (boost::crypt::size_t i {}; i < res.size(); ++i) + { + BOOST_TEST_EQ(res[i], soln[i]); + } + } + else BOOST_CRYPT_IF_CONSTEXPR (boost::crypt::is_same_v) + { + constexpr boost::crypt::array soln = { + 0xde, 0x7c, 0x9b, 0x85, 0xb8, 0xb7, 0x8a, 0xa6, 0xbc, 0x8a, + 0x7a, 0x36, 0xf7, 0x0a, 0x90, 0x70, 0x1c, 0x9d, 0xb4, 0xd9 + }; + + for (boost::crypt::size_t i {}; i < res.size(); ++i) + { + BOOST_TEST_EQ(res[i], soln[i]); + } + } + else BOOST_CRYPT_IF_CONSTEXPR (boost::crypt::is_same_v) + { + constexpr boost::crypt::array soln = { + 0xf7, 0xbc, 0x83, 0xf4, 0x30, 0x53, 0x84, 0x24, + 0xb1, 0x32, 0x98, 0xe6, 0xaa, 0x6f, 0xb1, 0x43, + 0xef, 0x4d, 0x59, 0xa1, 0x49, 0x46, 0x17, 0x59, + 0x97, 0x47, 0x9d, 0xbc, 0x2d, 0x1a, 0x3c, 0xd8 + }; + + for (boost::crypt::size_t i {}; i < res.size(); ++i) + { + BOOST_TEST_EQ(res[i], soln[i]); + } + } + else BOOST_CRYPT_IF_CONSTEXPR (boost::crypt::is_same_v) + { + constexpr boost::crypt::array soln = { + 0xb4, 0x2a, 0xf0, 0x90, 0x57, 0xba, 0xc1, 0xe2, + 0xd4, 0x17, 0x08, 0xe4, 0x8a, 0x90, 0x2e, 0x09, + 0xb5, 0xff, 0x7f, 0x12, 0xab, 0x42, 0x8a, 0x4f, + 0xe8, 0x66, 0x53, 0xc7, 0x3d, 0xd2, 0x48, 0xfb, + 0x82, 0xf9, 0x48, 0xa5, 0x49, 0xf7, 0xb7, 0x91, + 0xa5, 0xb4, 0x19, 0x15, 0xee, 0x4d, 0x1e, 0xc3, + 0x93, 0x53, 0x57, 0xe4, 0xe2, 0x31, 0x72, 0x50, + 0xd0, 0x37, 0x2a, 0xfa, 0x2e, 0xbe, 0xeb, 0x3a + }; + + for (boost::crypt::size_t i {}; i < res.size(); ++i) + { + BOOST_TEST_EQ(res[i], soln[i]); + } + } +} + +template +void test_edges() +{ + boost::crypt::hmac hmac_tester; + const char* msg {"The quick brown fox jumps over the lazy dog"}; + + // Usage before init + const auto state1 {hmac_tester.process_bytes(msg, std::strlen(msg))}; + BOOST_TEST(state1 == boost::crypt::hasher_state::state_error); + + // Init with nullptr + const auto state2 {hmac_tester.init("nullptr", 0)}; + BOOST_TEST(state2 == boost::crypt::hasher_state::null); + + // Good init + const auto state3 {hmac_tester.init("key", 3)}; + BOOST_TEST(state3 == boost::crypt::hasher_state::success); + + // Pass in nullptr + const auto state4 {hmac_tester.process_bytes("msg", 0)}; + BOOST_TEST(state4 == boost::crypt::hasher_state::null); + + // Good pass + const auto state5 {hmac_tester.process_bytes(msg, std::strlen(msg))}; + BOOST_TEST(state5 == boost::crypt::hasher_state::success); + + // Get digest twice + hmac_tester.get_digest(); + const auto res {hmac_tester.get_digest()}; + + for (const auto byte : res) + { + BOOST_TEST_EQ(byte, static_cast(0)); + } + + const char* big_key {"This is a really really really really really really really really really really" + " really really really really really really really really really really" + " really really really really really really really really really really" + " really really really really really really really really really really" + " really really really really really really really really really really" + " really really really really really really really really really really" + " really really really really really really really really really really" + " really really really really really really really really really really" + " really really really really really really really really really really" + " really really really really really really really really really really" + " long key"}; + + const auto state6 {hmac_tester.init(big_key, std::strlen(big_key))}; + BOOST_TEST(state6 == boost::crypt::hasher_state::success); + + // Init from keys + const auto outer_key {hmac_tester.get_outer_key()}; + const auto inner_key {hmac_tester.get_inner_key()}; + + hmac_tester.process_bytes(msg, std::strlen(msg)); + const auto res2 {hmac_tester.get_digest()}; + + hmac_tester.init_from_keys(inner_key, outer_key); + hmac_tester.process_bytes(msg, std::strlen(msg)); + const auto res3 {hmac_tester.get_digest()}; + + for (std::size_t i {}; i < res2.size(); ++i) + { + BOOST_TEST_EQ(res2[i], res3[i]); + } +} + +int main() +{ + basic_tests(); + basic_tests(); + basic_tests(); + basic_tests(); + + test_edges(); + test_edges(); + test_edges(); + test_edges(); + + return boost::report_errors(); +}