aboutsummaryrefslogtreecommitdiff
path: root/libphobos/src/std/digest/hmac.d
diff options
context:
space:
mode:
Diffstat (limited to 'libphobos/src/std/digest/hmac.d')
-rw-r--r--libphobos/src/std/digest/hmac.d336
1 files changed, 336 insertions, 0 deletions
diff --git a/libphobos/src/std/digest/hmac.d b/libphobos/src/std/digest/hmac.d
new file mode 100644
index 0000000..bf0cbf3
--- /dev/null
+++ b/libphobos/src/std/digest/hmac.d
@@ -0,0 +1,336 @@
+// Written in the D programming language.
+
+/**
+This package implements the hash-based message authentication code (_HMAC)
+algorithm as defined in $(HTTP tools.ietf.org/html/rfc2104, RFC2104). See also
+the corresponding $(HTTP en.wikipedia.org/wiki/Hash-based_message_authentication_code, Wikipedia article).
+
+$(SCRIPT inhibitQuickIndex = 1;)
+
+Macros:
+
+License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
+
+Source: $(PHOBOSSRC std/digest/_hmac.d)
+ */
+
+module std.digest.hmac;
+
+import std.digest : isDigest, hasBlockSize, isDigestibleRange, DigestType;
+import std.meta : allSatisfy;
+
+@safe:
+
+/**
+ * Template API HMAC implementation.
+ *
+ * This implements an _HMAC over the digest H. If H doesn't provide
+ * information about the block size, it can be supplied explicitly using
+ * the second overload.
+ *
+ * This type conforms to $(REF isDigest, std,digest).
+ */
+
+/// Compute HMAC over an input string
+@safe unittest
+{
+ import std.ascii : LetterCase;
+ import std.digest : toHexString;
+ import std.digest.sha : SHA1;
+ import std.string : representation;
+
+ auto secret = "secret".representation;
+ assert("The quick brown fox jumps over the lazy dog"
+ .representation
+ .hmac!SHA1(secret)
+ .toHexString!(LetterCase.lower) == "198ea1ea04c435c1246b586a06d5cf11c3ffcda6");
+}
+
+template HMAC(H)
+if (isDigest!H && hasBlockSize!H)
+{
+ alias HMAC = HMAC!(H, H.blockSize);
+}
+
+/**
+ * Overload of HMAC to be used if H doesn't provide information about its
+ * block size.
+ */
+
+struct HMAC(H, size_t hashBlockSize)
+if (hashBlockSize % 8 == 0)
+{
+ enum blockSize = hashBlockSize;
+
+ private H digest;
+ private ubyte[blockSize / 8] key;
+
+ /**
+ * Constructs the HMAC digest using the specified secret.
+ */
+
+ this(scope const(ubyte)[] secret)
+ {
+ // if secret is too long, shorten it by computing its hash
+ typeof(digest.finish()) buffer = void;
+ if (secret.length > blockSize / 8)
+ {
+ digest.start();
+ digest.put(secret);
+ buffer = digest.finish();
+ secret = buffer[];
+ }
+
+ // if secret is too short, it will be padded with zeroes
+ // (the key buffer is already zero-initialized)
+ import std.algorithm.mutation : copy;
+ secret.copy(key[]);
+
+ start();
+ }
+
+ ///
+ @safe pure nothrow @nogc unittest
+ {
+ import std.digest.hmac, std.digest.sha;
+ import std.string : representation;
+ auto hmac = HMAC!SHA1("My s3cR3T keY".representation);
+ hmac.put("Hello, world".representation);
+ static immutable expected = [
+ 130, 32, 235, 44, 208, 141,
+ 150, 232, 211, 214, 162, 195,
+ 188, 127, 52, 89, 100, 68, 90, 216];
+ assert(hmac.finish() == expected);
+ }
+
+ /**
+ * Reinitializes the digest, making it ready for reuse.
+ *
+ * Note:
+ * The constructor leaves the digest in an initialized state, so that this
+ * method only needs to be called if an unfinished digest is to be reused.
+ *
+ * Returns:
+ * A reference to the digest for convenient chaining.
+ */
+
+ ref HMAC!(H, blockSize) start() return
+ {
+ ubyte[blockSize / 8] ipad = void;
+ foreach (immutable i; 0 .. blockSize / 8)
+ ipad[i] = key[i] ^ 0x36;
+
+ digest.start();
+ digest.put(ipad[]);
+
+ return this;
+ }
+
+ ///
+ @safe pure nothrow @nogc unittest
+ {
+ import std.digest.hmac, std.digest.sha;
+ import std.string : representation;
+ string data1 = "Hello, world", data2 = "Hola mundo";
+ auto hmac = HMAC!SHA1("My s3cR3T keY".representation);
+ hmac.put(data1.representation);
+ hmac.start(); // reset digest
+ hmac.put(data2.representation); // start over
+ static immutable expected = [
+ 122, 151, 232, 240, 249, 80,
+ 19, 178, 186, 77, 110, 23, 208,
+ 52, 11, 88, 34, 151, 192, 255];
+ assert(hmac.finish() == expected);
+ }
+
+ /**
+ * Feeds a piece of data into the hash computation. This method allows the
+ * type to be used as an $(REF OutputRange, std,range).
+ *
+ * Returns:
+ * A reference to the digest for convenient chaining.
+ */
+
+ ref HMAC!(H, blockSize) put(in ubyte[] data...) return
+ {
+ digest.put(data);
+ return this;
+ }
+
+ ///
+ @safe pure nothrow @nogc unittest
+ {
+ import std.digest.hmac, std.digest.sha;
+ import std.string : representation;
+ string data1 = "Hello, world", data2 = "Hola mundo";
+ auto hmac = HMAC!SHA1("My s3cR3T keY".representation);
+ hmac.put(data1.representation)
+ .put(data2.representation);
+ static immutable expected = [
+ 197, 57, 52, 3, 13, 194, 13,
+ 36, 117, 228, 8, 11, 111, 51,
+ 165, 3, 123, 31, 251, 113];
+ assert(hmac.finish() == expected);
+ }
+
+ /**
+ * Resets the digest and returns the finished hash.
+ */
+
+ DigestType!H finish()
+ {
+ ubyte[blockSize / 8] opad = void;
+ foreach (immutable i; 0 .. blockSize / 8)
+ opad[i] = key[i] ^ 0x5c;
+
+ auto tmp = digest.finish();
+
+ digest.start();
+ digest.put(opad[]);
+ digest.put(tmp);
+ auto result = digest.finish();
+ start(); // reset the digest
+ return result;
+ }
+
+ ///
+ @safe pure nothrow @nogc unittest
+ {
+ import std.digest.hmac, std.digest.sha;
+ import std.string : representation;
+ string data1 = "Hello, world", data2 = "Hola mundo";
+ auto hmac = HMAC!SHA1("My s3cR3T keY".representation);
+ auto digest = hmac.put(data1.representation)
+ .put(data2.representation)
+ .finish();
+ static immutable expected = [
+ 197, 57, 52, 3, 13, 194, 13,
+ 36, 117, 228, 8, 11, 111, 51,
+ 165, 3, 123, 31, 251, 113];
+ assert(digest == expected);
+ }
+}
+
+/// Convenience constructor for $(LREF HMAC).
+template hmac(H)
+if (isDigest!H && hasBlockSize!H)
+{
+ alias hmac = hmac!(H, H.blockSize);
+}
+
+/// ditto
+template hmac(H, size_t blockSize)
+if (isDigest!H)
+{
+ /**
+ * Constructs an HMAC digest with the specified secret.
+ *
+ * Returns:
+ * An instance of HMAC that can be fed data as desired, and finished
+ * to compute the final hash when done.
+ */
+ auto hmac(scope const(ubyte)[] secret)
+ {
+ return HMAC!(H, blockSize)(secret);
+ }
+
+ ///
+ @safe pure nothrow @nogc unittest
+ {
+ import std.digest.hmac, std.digest.sha;
+ import std.string : representation;
+ string data1 = "Hello, world", data2 = "Hola mundo";
+ auto digest = hmac!SHA1("My s3cR3T keY".representation)
+ .put(data1.representation)
+ .put(data2.representation)
+ .finish();
+ static immutable expected = [
+ 197, 57, 52, 3, 13, 194, 13, 36,
+ 117, 228, 8, 11, 111, 51, 165,
+ 3, 123, 31, 251, 113];
+ assert(digest == expected);
+ }
+
+ /**
+ * Computes an _HMAC digest over the given range of data with the
+ * specified secret.
+ *
+ * Returns:
+ * The final _HMAC hash.
+ */
+ DigestType!H hmac(T...)(scope T data, scope const(ubyte)[] secret)
+ if (allSatisfy!(isDigestibleRange, typeof(data)))
+ {
+ import std.range.primitives : put;
+ auto hash = HMAC!(H, blockSize)(secret);
+ foreach (datum; data)
+ put(hash, datum);
+ return hash.finish();
+ }
+
+ ///
+ @safe pure nothrow @nogc unittest
+ {
+ import std.algorithm.iteration : map;
+ import std.digest.hmac, std.digest.sha;
+ import std.string : representation;
+ string data = "Hello, world";
+ auto digest = data.representation
+ .map!(a => cast(ubyte)(a+1))
+ .hmac!SHA1("My s3cR3T keY".representation);
+ static assert(is(typeof(digest) == ubyte[20]));
+ static immutable expected = [
+ 163, 208, 118, 179, 216, 93,
+ 17, 10, 84, 200, 87, 104, 244,
+ 111, 136, 214, 167, 210, 58, 10];
+ assert(digest == expected);
+ }
+}
+
+version (unittest)
+{
+ import std.digest : toHexString, LetterCase;
+ alias hex = toHexString!(LetterCase.lower);
+}
+
+@safe pure nothrow @nogc
+unittest
+{
+ import std.digest.md : MD5;
+ import std.range : isOutputRange;
+ static assert(isOutputRange!(HMAC!MD5, ubyte));
+ static assert(isDigest!(HMAC!MD5));
+ static assert(hasBlockSize!(HMAC!MD5) && HMAC!MD5.blockSize == MD5.blockSize);
+}
+
+@safe pure nothrow
+unittest
+{
+ import std.digest.md : MD5;
+ import std.digest.sha : SHA1, SHA256;
+
+ ubyte[] nada;
+ assert(hmac!MD5 (nada, nada).hex == "74e6f7298a9c2d168935f58c001bad88");
+ assert(hmac!SHA1 (nada, nada).hex == "fbdb1d1b18aa6c08324b7d64b71fb76370690e1d");
+ assert(hmac!SHA256(nada, nada).hex == "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad");
+
+ import std.string : representation;
+ auto key = "key".representation,
+ long_key = ("012345678901234567890123456789012345678901"
+ ~"234567890123456789012345678901234567890123456789").representation,
+ data1 = "The quick brown fox ".representation,
+ data2 = "jumps over the lazy dog".representation,
+ data = data1 ~ data2;
+
+ assert(data.hmac!MD5 (key).hex == "80070713463e7749b90c2dc24911e275");
+ assert(data.hmac!SHA1 (key).hex == "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9");
+ assert(data.hmac!SHA256(key).hex == "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8");
+
+ assert(data.hmac!MD5 (long_key).hex == "e1728d68e05beae186ea768561963778");
+ assert(data.hmac!SHA1 (long_key).hex == "560d3cd77316e57ab4bba0c186966200d2b37ba3");
+ assert(data.hmac!SHA256(long_key).hex == "a1b0065a5d1edd93152c677e1bc1b1e3bc70d3a76619842e7f733f02b8135c04");
+
+ assert(hmac!MD5 (key).put(data1).put(data2).finish == data.hmac!MD5 (key));
+ assert(hmac!SHA1 (key).put(data1).put(data2).finish == data.hmac!SHA1 (key));
+ assert(hmac!SHA256(key).put(data1).put(data2).finish == data.hmac!SHA256(key));
+}