diff --git a/include/boost/crypt2/detail/clear_mem.hpp b/include/boost/crypt2/detail/clear_mem.hpp
index a9c32fdc..2cd83a89 100644
--- a/include/boost/crypt2/detail/clear_mem.hpp
+++ b/include/boost/crypt2/detail/clear_mem.hpp
@@ -93,9 +93,9 @@ constexpr void clear_mem(std::span<std::byte> data)
 }
 */
 
-using generic_meset_t = void(*)(void*, size_t);
+using generic_meset_t = void(*)(void*, compat::size_t);
 
-inline void generic_runtime_memset_func_impl(void* ptr, size_t size)
+inline void generic_runtime_memset_func_impl(void* ptr, compat::size_t size)
 {
     #if defined(__clang__) && __clang_major__ >= 20
     #pragma clang diagnostic push
@@ -120,7 +120,7 @@ constexpr void clear_mem(T& data)
     }
     else
     {
-        generic_runtime_memset_func_impl(data.data(), data.size());
+        generic_runtime_memset_func(data.data(), data.size());
     }
 }
 
diff --git a/include/boost/crypt2/detail/compat.hpp b/include/boost/crypt2/detail/compat.hpp
index 9d6941db..c84f19ea 100644
--- a/include/boost/crypt2/detail/compat.hpp
+++ b/include/boost/crypt2/detail/compat.hpp
@@ -214,9 +214,9 @@ BOOST_CRYPT_GPU_ENABLED constexpr auto make_span(R&& r)
     else
     {
         #if BOOST_CRYPT_HAS_CUDA
-        return cuda::std::span(cuda::std::forward<R>(r));
+        return cuda::std::span{cuda::std::forward<R>(r).data(), cuda::std::forward<R>(r).size()};
         #else
-        return std::span(std::forward<R>(r));
+        return std::span{std::forward<R>(r).data(), std::forward<R>(r).size()};
         #endif
     }
 }
diff --git a/include/boost/crypt2/detail/unreachable.hpp b/include/boost/crypt2/detail/unreachable.hpp
new file mode 100644
index 00000000..7705faee
--- /dev/null
+++ b/include/boost/crypt2/detail/unreachable.hpp
@@ -0,0 +1,29 @@
+// Copyright 2024 - 2025 Matt Borland
+// Distributed under the Boost Software License, Version 1.0.
+// https://www.boost.org/LICENSE_1_0.txt
+
+#ifndef BOOST_CRYPT_DETAIL_UNREACHABLE_HPP
+#define BOOST_CRYPT_DETAIL_UNREACHABLE_HPP
+
+#include <boost/crypt2/detail/config.hpp>
+#include <boost/crypt2/detail/compat.hpp>
+
+namespace boost::crypt::detail {
+
+// LCOV_EXCL_START
+[[noreturn]] inline void unreachable()
+{
+    // Uses compiler specific extensions if possible.
+    // Even if no extension is used, undefined behavior is still raised by
+    // an empty function body and the noreturn attribute.
+#if defined(_MSC_VER) && !defined(__clang__) // MSVC
+    __assume(false);
+#else // GCC, Clang, NVCC
+    __builtin_unreachable();
+#endif
+}
+// LCOV_EXCL_STOP
+
+} // namespace boost::crypt::detail
+
+#endif // BOOST_CRYPT_DETAIL_UNREACHABLE_HPP
diff --git a/include/boost/crypt2/hash/detail/sha_1_2_hasher_base.hpp b/include/boost/crypt2/hash/detail/sha_1_2_hasher_base.hpp
index 32d5859e..d80699b7 100644
--- a/include/boost/crypt2/hash/detail/sha_1_2_hasher_base.hpp
+++ b/include/boost/crypt2/hash/detail/sha_1_2_hasher_base.hpp
@@ -55,7 +55,7 @@ class sha_1_2_hasher_base
     BOOST_CRYPT_GPU_ENABLED_CONSTEXPR auto finalize() noexcept -> state;
 
     // TODO(mborland): Allow this to take dynamic extent, check the length and then use a fixed amount. See sha512_base
-    [[nodiscard("Digest is the function return value")]] BOOST_CRYPT_GPU_ENABLED_CONSTEXPR auto get_digest() noexcept -> compat::expected<return_type, state>;
+    [[nodiscard("Digest is the function return value")]] BOOST_CRYPT_GPU_ENABLED_CONSTEXPR auto get_digest() const noexcept -> compat::expected<return_type, state>;
 
     template <compat::size_t Extent = compat::dynamic_extent>
     [[nodiscard]] BOOST_CRYPT_GPU_ENABLED_CONSTEXPR auto get_digest(compat::span<compat::byte, Extent> data) const noexcept -> state;
@@ -145,7 +145,7 @@ sha_1_2_hasher_base<digest_size, intermediate_hash_size>::get_digest(compat::spa
 
 template <compat::size_t digest_size, compat::size_t intermediate_hash_size>
 BOOST_CRYPT_GPU_ENABLED_CONSTEXPR auto
-sha_1_2_hasher_base<digest_size, intermediate_hash_size>::get_digest() noexcept -> compat::expected<return_type, state>
+sha_1_2_hasher_base<digest_size, intermediate_hash_size>::get_digest() const noexcept -> compat::expected<return_type, state>
 {
     if (corrupted_ || !computed_)
     {
diff --git a/include/boost/crypt2/mac/hmac.hpp b/include/boost/crypt2/mac/hmac.hpp
new file mode 100644
index 00000000..8a4eb53a
--- /dev/null
+++ b/include/boost/crypt2/mac/hmac.hpp
@@ -0,0 +1,322 @@
+// Copyright 2024 - 2025 Matt Borland
+// Distributed under the Boost Software License, Version 1.0.
+// https://www.boost.org/LICENSE_1_0.txt
+
+#ifndef BOOST_CRYPT2_MAC_HMAC_HPP
+#define BOOST_CRYPT2_MAC_HMAC_HPP
+
+#include <boost/crypt2/detail/config.hpp>
+#include <boost/crypt2/detail/concepts.hpp>
+#include <boost/crypt2/detail/compat.hpp>
+#include <boost/crypt2/detail/clear_mem.hpp>
+#include <boost/crypt2/detail/expected.hpp>
+#include <boost/crypt2/detail/unreachable.hpp>
+#include <boost/crypt2/state.hpp>
+
+namespace boost::crypt {
+
+BOOST_CRYPT_EXPORT template <typename HasherType>
+class hmac
+{
+public:
+
+    static constexpr compat::size_t block_size {HasherType::block_size};
+    using return_type = typename HasherType::return_type;
+    using key_type = compat::array<compat::byte, block_size>;
+
+private:
+
+    key_type inner_key_ {};
+    key_type outer_key_ {};
+    HasherType inner_hash_;
+    HasherType outer_hash_;
+    bool initialized_ {false};
+    bool computed_ {false};
+    bool corrupted_ {false};
+
+    template <compat::size_t Extent = compat::dynamic_extent>
+    BOOST_CRYPT_GPU_ENABLED_CONSTEXPR auto init_impl(compat::span<const compat::byte, Extent> data) noexcept -> state;
+
+public:
+
+    BOOST_CRYPT_GPU_ENABLED_CONSTEXPR hmac() noexcept = default;
+
+    template <compat::size_t Extent = compat::dynamic_extent>
+    explicit BOOST_CRYPT_GPU_ENABLED_CONSTEXPR hmac(const compat::span<const compat::byte, Extent> key) noexcept { init(key); }
+    
+    BOOST_CRYPT_GPU_ENABLED_CONSTEXPR ~hmac() noexcept;
+
+    BOOST_CRYPT_GPU_ENABLED_CONSTEXPR auto init_from_keys(const key_type& inner_key,
+                                                          const key_type& outer_key) noexcept -> state;
+
+    template <compat::size_t Extent = compat::dynamic_extent>
+    BOOST_CRYPT_GPU_ENABLED_CONSTEXPR auto init(compat::span<const compat::byte, Extent> data) noexcept -> state;
+
+    template <concepts::sized_range SizedRange>
+    BOOST_CRYPT_GPU_ENABLED auto init(SizedRange&& data) noexcept -> state;
+
+    template <compat::size_t Extent = compat::dynamic_extent>
+    BOOST_CRYPT_GPU_ENABLED_CONSTEXPR auto process_bytes(compat::span<const compat::byte, Extent> data) noexcept -> state;
+
+    template <concepts::sized_range SizedRange>
+    BOOST_CRYPT_GPU_ENABLED auto process_bytes(SizedRange&& data) noexcept -> state;
+
+    BOOST_CRYPT_GPU_ENABLED_CONSTEXPR auto finalize() noexcept -> state;
+
+    [[nodiscard]] BOOST_CRYPT_GPU_ENABLED_CONSTEXPR auto get_digest() const noexcept -> compat::expected<return_type, state>;
+
+    template <compat::size_t Extent = compat::dynamic_extent>
+    [[nodiscard]] BOOST_CRYPT_GPU_ENABLED_CONSTEXPR
+    auto get_digest(compat::span<compat::byte, Extent> data) const noexcept -> state;
+
+    template <concepts::writable_output_range Range>
+    [[nodiscard]] BOOST_CRYPT_GPU_ENABLED auto get_digest(Range&& data) const noexcept -> state;
+
+    [[nodiscard]] BOOST_CRYPT_GPU_ENABLED_CONSTEXPR auto get_outer_key() const noexcept -> key_type;
+
+    [[nodiscard]] BOOST_CRYPT_GPU_ENABLED_CONSTEXPR auto get_inner_key() const noexcept -> key_type;
+};
+
+template <typename HasherType>
+template <compat::size_t Extent>
+BOOST_CRYPT_GPU_ENABLED_CONSTEXPR auto
+hmac<HasherType>::init_impl(const compat::span<const compat::byte, Extent> data) noexcept -> state
+{
+    computed_ = false;
+    corrupted_ = false;
+    inner_hash_.init();
+    outer_hash_.init();
+
+    key_type k0 {};
+
+    // 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 (data.size() <= block_size)
+    {
+        for (compat::size_t i {}; i < data.size() && i < block_size; ++i)
+        {
+            k0[i] = data[i];
+        }
+    }
+    // Step 2: If the length of K > B: hash K to obtain an L byte string
+    else
+    {
+        HasherType hasher;
+        hasher.process_bytes(data);
+        hasher.finalize();
+        const auto res {hasher.get_digest()};
+        BOOST_CRYPT_ASSERT(res.has_value());
+
+        const auto data_hash {res.value()};
+        BOOST_CRYPT_ASSERT(data_hash.size() <= k0.size());
+
+        for (compat::size_t i {}; i < data_hash.size(); ++i)
+        {
+            k0[i] = data_hash[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 (compat::size_t i {}; i < k0.size(); ++i)
+    {
+        inner_key_[i] = k0[i] ^ compat::byte{0x36};
+        outer_key_[i] = k0[i] ^ compat::byte{0x5C};
+    }
+
+    const auto inner_result {inner_hash_.process_bytes(inner_key_)};
+    const auto outer_result {outer_hash_.process_bytes(outer_key_)};
+    
+    if (inner_result == state::success && outer_result == state::success) [[likely]]
+    {
+        initialized_ = true;
+        return state::success;
+    }
+    else
+    {
+        // If we have some weird OOM result
+        // LCOV_EXCL_START
+        if (inner_result != state::success)
+        {
+            return inner_result;
+        }
+        else
+        {
+            return outer_result;
+        }
+        // LCOV_EXCL_STOP
+    }
+}
+
+template <typename HasherType>
+template <compat::size_t Extent>
+BOOST_CRYPT_GPU_ENABLED_CONSTEXPR auto
+hmac<HasherType>::init(const compat::span<const compat::byte, Extent> data) noexcept -> state
+{
+    return init_impl(data);
+}
+
+template <typename HasherType>
+template <concepts::sized_range SizedRange>
+BOOST_CRYPT_GPU_ENABLED auto hmac<HasherType>::init(SizedRange&& data) noexcept -> state
+{
+    const auto data_span {compat::make_span(compat::forward<SizedRange>(data))};
+    return init_impl(compat::as_bytes(data_span));
+}
+
+template <typename HasherType>
+BOOST_CRYPT_GPU_ENABLED_CONSTEXPR auto
+hmac<HasherType>::init_from_keys(const hmac::key_type& inner_key, const hmac::key_type& outer_key) noexcept -> 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)};
+    const auto outer_result {outer_hash_.process_bytes(outer_key)};
+
+    if (inner_result == state::success && outer_result == state::success) [[likely]]
+    {
+        initialized_ = true;
+        return state::success;
+    }
+    else
+    {
+        // These fail states would imply something deeply wrong with the hasher or key
+        // LCOV_EXCL_START
+        initialized_ = false;
+
+        if (inner_result != state::success)
+        {
+            return inner_result;
+        }
+        else
+        {
+            return outer_result;
+        }
+        // LCOV_EXCL_STOP
+    }
+}
+
+template <typename HasherType>
+BOOST_CRYPT_GPU_ENABLED_CONSTEXPR hmac<HasherType>::~hmac() noexcept
+{
+    // Inner and outer has will clear their own memory on destruction
+
+    detail::clear_mem(inner_key_);
+    detail::clear_mem(outer_key_);
+    initialized_ = false;
+    computed_ = false;
+    corrupted_ = false;
+}
+
+template <typename HasherType>
+template <compat::size_t Extent>
+BOOST_CRYPT_GPU_ENABLED_CONSTEXPR auto
+hmac<HasherType>::process_bytes(const compat::span<const compat::byte, Extent> data) noexcept -> state
+{
+    if (!initialized_ || corrupted_)
+    {
+        return state::state_error;
+    }
+
+    const auto return_code {inner_hash_.process_bytes(data)};
+    if (return_code == state::success)
+    {
+        return state::success;
+    }
+    else
+    {
+        // Cannot test 64 and 128 bit OOM
+        // LCOV_EXCL_START
+        switch (return_code)
+        {
+            case state::state_error:
+                corrupted_ = true;
+                return state::state_error;
+            case state::input_too_long:
+                corrupted_ = true;
+                return state::input_too_long;
+            default:
+                detail::unreachable();
+        }
+        // LCOV_EXCL_STOP
+    }
+}
+
+template <typename HasherType>
+template <concepts::sized_range SizedRange>
+BOOST_CRYPT_GPU_ENABLED auto hmac<HasherType>::process_bytes(SizedRange&& data) noexcept -> state
+{
+    const auto data_span {compat::make_span(compat::forward<SizedRange>(data))};
+    return process_bytes(compat::as_bytes(data_span));
+}
+
+template <typename HasherType>
+BOOST_CRYPT_GPU_ENABLED_CONSTEXPR auto hmac<HasherType>::finalize() noexcept -> state
+{
+    if (computed_)
+    {
+        corrupted_ = true;
+    }
+    if (corrupted_)
+    {
+        return state::state_error;
+    }
+
+    computed_ = true;
+    [[maybe_unused]] const auto inner_final_state {inner_hash_.finalize()};
+    BOOST_CRYPT_ASSERT(inner_final_state == state::success);
+    const auto r_inner {inner_hash_.get_digest()};
+    BOOST_CRYPT_ASSERT(r_inner.has_value());
+
+    outer_hash_.process_bytes(r_inner.value());
+    [[maybe_unused]] const auto outer_final_state {outer_hash_.finalize()};
+    BOOST_CRYPT_ASSERT(outer_final_state == state::success);
+
+    return state::success;
+}
+
+template <typename HasherType>
+BOOST_CRYPT_GPU_ENABLED_CONSTEXPR auto
+hmac<HasherType>::get_digest() const noexcept -> compat::expected<return_type, state>
+{
+    return outer_hash_.get_digest();
+}
+
+template <typename HasherType>
+template <concepts::writable_output_range Range>
+BOOST_CRYPT_GPU_ENABLED auto
+hmac<HasherType>::get_digest(Range&& data) const noexcept -> state
+{
+    return outer_hash_.get_digest(data);
+}
+
+template <typename HasherType>
+template <compat::size_t Extent>
+BOOST_CRYPT_GPU_ENABLED_CONSTEXPR auto
+hmac<HasherType>::get_digest(compat::span<compat::byte, Extent> data) const noexcept -> state
+{
+    return outer_hash_.get_digest(data);
+}
+
+template <typename HasherType>
+BOOST_CRYPT_GPU_ENABLED_CONSTEXPR auto hmac<HasherType>::get_outer_key() const noexcept -> hmac::key_type
+{
+    return outer_key_;
+}
+
+template <typename HasherType>
+BOOST_CRYPT_GPU_ENABLED_CONSTEXPR auto hmac<HasherType>::get_inner_key() const noexcept -> hmac::key_type
+{
+    return inner_key_;
+}
+
+} // namespace boost::crypt
+
+#endif //BOOST_CRYPT2_MAC_HMAC_HPP
diff --git a/test/Jamfile b/test/Jamfile
index 3d744097..b1846d6a 100644
--- a/test/Jamfile
+++ b/test/Jamfile
@@ -73,7 +73,7 @@ run test_sha3_224.cpp ;
 run test_shake128.cpp ;
 run test_shake256.cpp ;
 
-#run test_hmac.cpp ;
+run test_hmac.cpp ;
 
 #run test_hmac_drbg.cpp ;
 
diff --git a/test/test_hmac.cpp b/test/test_hmac.cpp
index 65d2bfb5..0600ff24 100644
--- a/test/test_hmac.cpp
+++ b/test/test_hmac.cpp
@@ -2,68 +2,55 @@
 // Distributed under the Boost Software License, Version 1.0.
 // https://www.boost.org/LICENSE_1_0.txt
 
-#define BOOST_CRYPT_ENABLE_MD5
-
-#include "boost/crypt/mac/hmac.hpp"
-#include <boost/crypt/hash/md5.hpp>
-#include <boost/crypt/hash/sha1.hpp>
-#include <boost/crypt/hash/sha256.hpp>
-#include <boost/crypt/hash/sha512.hpp>
+#include <boost/crypt2/mac/hmac.hpp>
+#include <boost/crypt2/hash/sha1.hpp>
+#include <boost/crypt2/hash/sha256.hpp>
+#include <boost/crypt2/hash/sha512.hpp>
 #include <boost/core/lightweight_test.hpp>
 
 template <typename HasherType>
 void basic_tests()
 {
     boost::crypt::hmac<HasherType> hmac_tester;
-    const auto state_1 {hmac_tester.init("key", 3)};
+    const auto state_1 {hmac_tester.init(std::string{"key"})};
     BOOST_TEST(state_1 == boost::crypt::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))};
+    std::string msg {"The quick brown fox jumps over the lazy dog"};
+    const auto state_2 {hmac_tester.process_bytes(msg)};
     BOOST_TEST(state_2 == boost::crypt::state::success);
 
-    const auto res {hmac_tester.get_digest()};
-
-    BOOST_CRYPT_IF_CONSTEXPR (boost::crypt::is_same_v<HasherType, boost::crypt::md5_hasher>)
-    {
-        constexpr boost::crypt::array<boost::crypt::uint8_t, 16U> 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<HasherType, boost::crypt::sha1_hasher>)
+    hmac_tester.finalize();
+    const auto res {hmac_tester.get_digest().value()};
+    
+    if constexpr (std::is_same_v<HasherType, boost::crypt::sha1_hasher>)
     {
-        constexpr boost::crypt::array<boost::crypt::uint8_t, 20U> soln = {
+        constexpr std::array<std::uint8_t, 20U> 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)
+        for (std::size_t i {}; i < res.size(); ++i)
         {
-            BOOST_TEST_EQ(res[i], soln[i]);
+            BOOST_TEST(res[i] == static_cast<std::byte>(soln[i]));
         }
     }
-    else BOOST_CRYPT_IF_CONSTEXPR (boost::crypt::is_same_v<HasherType, boost::crypt::sha256_hasher>)
+    else if constexpr (std::is_same_v<HasherType, boost::crypt::sha256_hasher>)
     {
-        constexpr boost::crypt::array<boost::crypt::uint8_t, 32U> soln = {
+        constexpr std::array<std::uint8_t, 32U> 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)
+        for (std::size_t i {}; i < res.size(); ++i)
         {
-            BOOST_TEST_EQ(res[i], soln[i]);
+            BOOST_TEST(res[i] == static_cast<std::byte>(soln[i]));
         }
     }
-    else BOOST_CRYPT_IF_CONSTEXPR (boost::crypt::is_same_v<HasherType, boost::crypt::sha512_hasher>)
+    else if constexpr (std::is_same_v<HasherType, boost::crypt::sha512_hasher>)
     {
-        constexpr boost::crypt::array<boost::crypt::uint8_t, 64U> soln = {
+        constexpr std::array<std::uint8_t, 64U> 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,
@@ -74,51 +61,38 @@ void basic_tests()
             0xd0, 0x37, 0x2a, 0xfa, 0x2e, 0xbe, 0xeb, 0x3a
         };
 
-        for (boost::crypt::size_t i {}; i < res.size(); ++i)
+        for (std::size_t i {}; i < res.size(); ++i)
         {
-            BOOST_TEST_EQ(res[i], soln[i]);
+            BOOST_TEST(res[i] == static_cast<std::byte>(soln[i]));
         }
     }
-
-    hmac_tester.destroy();
 }
 
 template <typename HasherType>
 void test_edges()
 {
     boost::crypt::hmac<HasherType> hmac_tester;
-    const char* msg {"The quick brown fox jumps over the lazy dog"};
+    std::string msg {"The quick brown fox jumps over the lazy dog"};
 
     // Usage before init
-    const auto state1 {hmac_tester.process_bytes(msg, std::strlen(msg))};
+    const auto state1 {hmac_tester.process_bytes(msg)};
     BOOST_TEST(state1 == boost::crypt::state::state_error);
 
-    // Init with nullptr
-    const auto state2 {hmac_tester.init("nullptr", 0)};
-    BOOST_TEST(state2 == boost::crypt::state::null);
-
     // Good init
-    const auto state3 {hmac_tester.init("key", 3)};
+    const auto state3 {hmac_tester.init(std::string{"key"})};
     BOOST_TEST(state3 == boost::crypt::state::success);
 
-    // Pass in nullptr
-    const auto state4 {hmac_tester.process_bytes("msg", 0)};
-    BOOST_TEST(state4 == boost::crypt::state::null);
-
     // Good pass
-    const auto state5 {hmac_tester.process_bytes(msg, std::strlen(msg))};
+    const auto state5 {hmac_tester.process_bytes(msg)};
     BOOST_TEST(state5 == boost::crypt::state::success);
 
     // Get digest twice
-    hmac_tester.get_digest();
+    hmac_tester.finalize();
+    [[maybe_unused]] const auto garbage = hmac_tester.get_digest();
     const auto res {hmac_tester.get_digest()};
+    BOOST_TEST(res.has_value());
 
-    for (const auto byte : res)
-    {
-        BOOST_TEST_EQ(byte, static_cast<std::uint8_t>(0));
-    }
-
-    const char* big_key {"This is a really really really really really really really really really really"
+    std::string 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"
@@ -130,34 +104,36 @@ void test_edges()
                          " really really really really really really really really really really"
                          " long key"};
 
-    const auto state6 {hmac_tester.init(big_key, std::strlen(big_key))};
+    const auto state6 {hmac_tester.init(big_key)};
     BOOST_TEST(state6 == boost::crypt::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.process_bytes(msg);
+    hmac_tester.finalize();
+    const auto res2 {hmac_tester.get_digest().value()};
 
     hmac_tester.init_from_keys(inner_key, outer_key);
-    hmac_tester.process_bytes(msg, std::strlen(msg));
-    const auto res3 {hmac_tester.get_digest()};
+    hmac_tester.process_bytes(msg);
+    hmac_tester.finalize();
+    const auto res3 {hmac_tester.get_digest().value()};
 
     for (std::size_t i {}; i < res2.size(); ++i)
     {
-        BOOST_TEST_EQ(res2[i], res3[i]);
+        BOOST_TEST(res2[i] == res3[i]);
     }
+
+    BOOST_TEST(hmac_tester.finalize() == boost::crypt::state::state_error);
 }
 
 int main()
 {
-    basic_tests<boost::crypt::md5_hasher>();
     basic_tests<boost::crypt::sha1_hasher>();
     basic_tests<boost::crypt::sha256_hasher>();
     basic_tests<boost::crypt::sha512_hasher>();
 
-    test_edges<boost::crypt::md5_hasher>();
     test_edges<boost::crypt::sha1_hasher>();
     test_edges<boost::crypt::sha256_hasher>();
     test_edges<boost::crypt::sha512_hasher>();
diff --git a/test/test_nist_cavs_detail.hpp b/test/test_nist_cavs_detail.hpp
index de0f1bc0..032fff19 100644
--- a/test/test_nist_cavs_detail.hpp
+++ b/test/test_nist_cavs_detail.hpp
@@ -2133,9 +2133,19 @@ auto test_vectors_hmac(const nist::cavs::test_vector_container_type& test_vector
 
         this_hash.process_bytes(test_vector.my_msg.data(), test_vector.my_msg.size());
 
+        this_hash.finalize();
+
         const local_result_type result_01 { this_hash.get_digest() };
 
-        const bool result_hash_01_is_ok { std::equal(test_vector.my_result.cbegin(), test_vector.my_result.cend(), result_01.cbegin()) };
+        bool result_hash_01_is_ok {true};
+        for (std::size_t i {}; i < test_vector.my_result.size(); ++i)
+        {
+            if (static_cast<std::byte>(test_vector.my_result[i]) != result_01[i])
+            {
+                result_hash_01_is_ok = false;
+                break;
+            }
+        }
 
         BOOST_TEST(result_hash_01_is_ok);
 
@@ -2149,9 +2159,19 @@ auto test_vectors_hmac(const nist::cavs::test_vector_container_type& test_vector
 
         this_hash.process_bytes(test_vector.my_msg);
 
+        this_hash.finalize();
+
         const local_result_type result_02 { this_hash.get_digest() };
 
-        const bool result_hash_02_is_ok { std::equal(test_vector.my_result.cbegin(), test_vector.my_result.cend(), result_02.cbegin()) };
+        bool result_hash_02_is_ok {true};
+        for (std::size_t i {}; i < test_vector.my_result.size(); ++i)
+        {
+            if (static_cast<std::byte>(test_vector.my_result[i]) != result_02[i])
+            {
+                result_hash_02_is_ok = false;
+                break;
+            }
+        }
 
         BOOST_TEST(result_hash_02_is_ok);