diff --git a/Makefile.am b/Makefile.am
index fd2e19d3..4393bb8a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -45,6 +45,8 @@ UTIL_FILES = \
hash/crc.cpp \
hash/crc.hpp \
hash/fletcher.hpp \
+ hash/hmac.cpp \
+ hash/hmac.hpp \
hash/md2.cpp \
hash/md2.hpp \
hash/md4.cpp \
@@ -238,6 +240,7 @@ TEST_BIN = \
test/extent \
test/fixed \
test/float \
+ test/hmac \
test/hton \
test/ip \
test/json_types \
diff --git a/hash/hmac.cpp b/hash/hmac.cpp
new file mode 100644
index 00000000..7088fdf4
--- /dev/null
+++ b/hash/hmac.cpp
@@ -0,0 +1,102 @@
+/*
+ * This file is part of libgim.
+ *
+ * libgim is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * libgim is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with libgim. If not, see .
+ *
+ * Copyright 2015 Danny Robson
+ */
+
+#include "hmac.hpp"
+
+#include "debug.hpp"
+
+#include
+
+using util::hash::HMAC;
+
+
+//-----------------------------------------------------------------------------
+static const uint8_t IFILL = 0x36;
+static const uint8_t OFILL = 0x5C;
+
+
+//-----------------------------------------------------------------------------
+HMAC::HMAC (const uint8_t *restrict key, size_t len)
+{
+ CHECK (key);
+ CHECK_LE (len, m_ikey.size ());
+ CHECK_LE (len, m_okey.size ());
+
+ static_assert (sizeof (m_ikey) == sizeof (m_okey), "key padding must match");
+
+
+ std::copy (key, key + len, m_ikey.begin ());
+ std::fill (m_ikey.begin () + len,
+ m_ikey.end (),
+ 0);
+
+ m_okey = m_ikey;
+
+ std::transform (m_ikey.begin (),
+ m_ikey.end (),
+ m_ikey.begin (),
+ [] (auto v) { return v ^ IFILL; });
+
+ std::transform (m_okey.begin (),
+ m_okey.end (),
+ m_okey.begin (),
+ [] (auto v) { return v ^ OFILL; });
+
+ m_hash.update (m_ikey.data (), m_ikey.size ());
+}
+
+
+
+//-----------------------------------------------------------------------------
+void
+HMAC::update (const void *restrict data, size_t len)
+{
+ m_hash.update ((const uint8_t*)data, len);
+}
+
+
+//-----------------------------------------------------------------------------
+void
+HMAC::finish (void)
+{
+ m_hash.finish ();
+ auto d = m_hash.digest ();
+
+ m_hash.reset ();
+ m_hash.update (m_okey.data (), m_okey.size ());
+ m_hash.update (d.data (), d.size ());
+ m_hash.finish ();
+}
+
+
+//-----------------------------------------------------------------------------
+void
+HMAC::reset (void)
+{
+ m_hash.reset ();
+ m_hash.update (m_ikey.data (), m_ikey.size ());
+}
+
+
+//-----------------------------------------------------------------------------
+HMAC::digest_t
+HMAC::digest (void)
+{
+ return m_hash.digest ();
+}
diff --git a/hash/hmac.hpp b/hash/hmac.hpp
new file mode 100644
index 00000000..e187acb4
--- /dev/null
+++ b/hash/hmac.hpp
@@ -0,0 +1,47 @@
+/*
+ * This file is part of libgim.
+ *
+ * libgim is free software: you can redistribute it and/or modify it under the
+ * terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * libgim is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with libgim. If not, see .
+ *
+ * Copyright 2015 Danny Robson
+ */
+
+#ifndef __UTIL_HASH_HMAC_HPP
+#define __UTIL_HASH_HMAC_HPP
+
+#include "md5.hpp"
+
+namespace util { namespace hash {
+ /// RFC 2104 key-hashing for message authentication
+ class HMAC {
+ public:
+ using digest_t = MD5::digest_t;
+
+ HMAC (const uint8_t *key, size_t);
+
+ void update (const void *restrict, size_t);
+ void finish (void);
+ void reset (void);
+
+ digest_t digest (void);
+
+ private:
+ std::array m_ikey;
+ std::array m_okey;
+
+ MD5 m_hash;
+ };
+} }
+
+#endif
diff --git a/test/hmac.cpp b/test/hmac.cpp
new file mode 100644
index 00000000..4a13c953
--- /dev/null
+++ b/test/hmac.cpp
@@ -0,0 +1,67 @@
+#include "hash/hmac.hpp"
+#include "types.hpp"
+#include "debug.hpp"
+
+#include
+#include
+
+using util::hash::HMAC;
+
+
+static const struct {
+ std::vector key;
+ std::vector dat;
+ HMAC::digest_t res;
+} TESTS[] = {
+ // RFC 2104 test data, MD5
+ {
+ { 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+ 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b },
+ { 'H', 'i', ' ', 'T', 'h', 'e', 'r', 'e' },
+ { { 0x92, 0x94, 0x72, 0x7a, 0x36, 0x38, 0xbb, 0x1c,
+ 0x13, 0xf4, 0x8e, 0xf8, 0x15, 0x8b, 0xfc, 0x9d } }
+ },
+
+ {
+ { 'J', 'e', 'f', 'e' },
+ { 'w', 'h', 'a', 't', ' ', 'd', 'o', ' ',
+ 'y', 'a', ' ', 'w', 'a', 'n', 't', ' ',
+ 'f', 'o', 'r', ' ', 'n', 'o', 't', 'h',
+ 'i', 'n', 'g', '?' },
+ { { 0x75, 0x0c, 0x78, 0x3e, 0x6a, 0xb0, 0xb5, 0x03,
+ 0xea, 0xa8, 0x6e, 0x31, 0x0a, 0x5d, 0xb7, 0x38 } }
+ },
+
+ {
+ { 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
+ 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA },
+ { 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
+ 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
+ 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
+ 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
+ 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
+ 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDD,
+ 0xDD, 0xDD },
+ { { 0x56, 0xbe, 0x34, 0x52, 0x1d, 0x14, 0x4c, 0x88,
+ 0xdb, 0xb8, 0xc7, 0x33, 0xf0, 0xe8, 0xb3, 0xf6 } }
+ }
+};
+
+
+
+
+
+int
+main (int, char**)
+{
+ for (const auto &t: TESTS) {
+ util::hash::HMAC h (t.key.data (), t.key.size ());
+ h.update (t.dat.data (), t.dat.size ());
+ h.finish ();
+
+ if (h.digest () != t.res)
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}