1 // Written in the D programming language.
2 
3 /**
4 This package implements the hash-based message authentication code (_HMAC)
5 algorithm as defined in $(HTTP tools.ietf.org/html/rfc2104, RFC2104). See also
6 the corresponding $(HTTP en.wikipedia.org/wiki/Hash-based_message_authentication_code, Wikipedia article).
7 
8 $(SCRIPT inhibitQuickIndex = 1;)
9 
10 Macros:
11 
12 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0).
13 
14 Source: $(PHOBOSSRC std/digest/hmac.d)
15  */
16 
17 module std.digest.hmac;
18 
19 import std.digest : isDigest, hasBlockSize, isDigestibleRange, DigestType;
20 import std.meta : allSatisfy;
21 
22 @safe:
23 
24 /**
25  * Template API HMAC implementation.
26  *
27  * This implements an _HMAC over the digest H. If H doesn't provide
28  * information about the block size, it can be supplied explicitly using
29  * the second overload.
30  *
31  * This type conforms to $(REF isDigest, std,digest).
32  */
33 
34 /// Compute HMAC over an input string
35 @safe unittest
36 {
37     import std.ascii : LetterCase;
38     import std.digest : toHexString;
39     import std.digest.sha : SHA1;
40     import std.string : representation;
41 
42     auto secret = "secret".representation;
43     assert("The quick brown fox jumps over the lazy dog"
44             .representation
45             .hmac!SHA1(secret)
46             .toHexString!(LetterCase.lower) == "198ea1ea04c435c1246b586a06d5cf11c3ffcda6");
47 }
48 
49 template HMAC(H)
50 if (isDigest!H && hasBlockSize!H)
51 {
52     alias HMAC = HMAC!(H, H.blockSize);
53 }
54 
55 /**
56  * Overload of HMAC to be used if H doesn't provide information about its
57  * block size.
58  */
59 
60 struct HMAC(H, size_t hashBlockSize)
61 if (hashBlockSize % 8 == 0)
62 {
63     enum blockSize = hashBlockSize;
64 
65     private H digest;
66     private ubyte[blockSize / 8] key;
67 
68     /**
69      * Constructs the HMAC digest using the specified secret.
70      */
71 
72     this(scope const(ubyte)[] secret)
73     {
74         // if secret is too long, shorten it by computing its hash
75         typeof(digest.finish()) buffer = void;
76         typeof(secret) secretBytes = secret;
77 
78         if (secret.length > blockSize / 8)
79         {
80             digest.start();
81             digest.put(secret);
82             buffer = digest.finish();
83             secretBytes = buffer[];
84         }
85 
86         // if secret is too short, it will be padded with zeroes
87         // (the key buffer is already zero-initialized)
88         import std.algorithm.mutation : copy;
89         secretBytes.copy(key[]);
90 
91         start();
92     }
93 
94     ///
95     @safe pure nothrow @nogc unittest
96     {
97         import std.digest.sha : SHA1;
98         import std.string : representation;
99         auto hmac = HMAC!SHA1("My s3cR3T keY".representation);
100         hmac.put("Hello, world".representation);
101         static immutable expected = [
102             130, 32, 235, 44, 208, 141,
103             150, 232, 211, 214, 162, 195,
104             188, 127, 52, 89, 100, 68, 90, 216];
105         assert(hmac.finish() == expected);
106     }
107 
108     /**
109      * Reinitializes the digest, making it ready for reuse.
110      *
111      * Note:
112      * The constructor leaves the digest in an initialized state, so that this
113      * method only needs to be called if an unfinished digest is to be reused.
114      *
115      * Returns:
116      * A reference to the digest for convenient chaining.
117      */
118 
119     ref HMAC!(H, blockSize) start() return
120     {
121         ubyte[blockSize / 8] ipad = void;
122         foreach (immutable i; 0 .. blockSize / 8)
123             ipad[i] = key[i] ^ 0x36;
124 
125         digest.start();
126         digest.put(ipad[]);
127 
128         return this;
129     }
130 
131     ///
132     @safe pure nothrow @nogc unittest
133     {
134         import std.digest.sha : SHA1;
135         import std.string : representation;
136         string data1 = "Hello, world", data2 = "Hola mundo";
137         auto hmac = HMAC!SHA1("My s3cR3T keY".representation);
138         hmac.put(data1.representation);
139         hmac.start();                   // reset digest
140         hmac.put(data2.representation); // start over
141         static immutable expected = [
142             122, 151, 232, 240, 249, 80,
143             19, 178, 186, 77, 110, 23, 208,
144             52, 11, 88, 34, 151, 192, 255];
145         assert(hmac.finish() == expected);
146     }
147 
148     /**
149      * Feeds a piece of data into the hash computation. This method allows the
150      * type to be used as an $(REF OutputRange, std,range).
151      *
152      * Returns:
153      * A reference to the digest for convenient chaining.
154      */
155 
156     ref HMAC!(H, blockSize) put(in ubyte[] data...) return
157     {
158         digest.put(data);
159         return this;
160     }
161 
162     ///
163     @safe pure nothrow @nogc unittest
164     {
165         import std.digest.hmac, std.digest.sha;
166         import std.string : representation;
167         string data1 = "Hello, world", data2 = "Hola mundo";
168         auto hmac = HMAC!SHA1("My s3cR3T keY".representation);
169         hmac.put(data1.representation)
170             .put(data2.representation);
171         static immutable expected = [
172             197, 57, 52, 3, 13, 194, 13,
173             36, 117, 228, 8, 11, 111, 51,
174             165, 3, 123, 31, 251, 113];
175         assert(hmac.finish() == expected);
176     }
177 
178     /**
179      * Resets the digest and returns the finished hash.
180      */
181 
182     DigestType!H finish()
183     {
184         ubyte[blockSize / 8] opad = void;
185         foreach (immutable i; 0 .. blockSize / 8)
186             opad[i] = key[i] ^ 0x5c;
187 
188         auto tmp = digest.finish();
189 
190         digest.start();
191         digest.put(opad[]);
192         digest.put(tmp);
193         auto result = digest.finish();
194         start();    // reset the digest
195         return result;
196     }
197 
198     ///
199     @safe pure nothrow @nogc unittest
200     {
201         import std.digest.sha : SHA1;
202         import std.string : representation;
203         string data1 = "Hello, world", data2 = "Hola mundo";
204         auto hmac = HMAC!SHA1("My s3cR3T keY".representation);
205         auto testDigest = hmac.put(data1.representation)
206                           .put(data2.representation)
207                           .finish();
208         static immutable expected = [
209             197, 57, 52, 3, 13, 194, 13,
210             36, 117, 228, 8, 11, 111, 51,
211             165, 3, 123, 31, 251, 113];
212         assert(testDigest == expected);
213     }
214 }
215 
216 /// ditto
217 template hmac(H)
218 if (isDigest!H && hasBlockSize!H)
219 {
220     alias hmac = hmac!(H, H.blockSize);
221 }
222 
223 /// ditto
224 template hmac(H, size_t blockSize)
225 if (isDigest!H)
226 {
227     /**
228      * Constructs an HMAC digest with the specified secret.
229      *
230      * Returns:
231      * An instance of HMAC that can be fed data as desired, and finished
232      * to compute the final hash when done.
233      */
234     auto hmac(scope const(ubyte)[] secret)
235     {
236         return HMAC!(H, blockSize)(secret);
237     }
238 
239     ///
240     @safe pure nothrow @nogc unittest
241     {
242         import std.digest.sha : SHA1;
243         import std.string : representation;
244         string data1 = "Hello, world", data2 = "Hola mundo";
245         auto digest = hmac!SHA1("My s3cR3T keY".representation)
246                           .put(data1.representation)
247                           .put(data2.representation)
248                           .finish();
249         static immutable expected = [
250             197, 57, 52, 3, 13, 194, 13, 36,
251             117, 228, 8, 11, 111, 51, 165,
252             3, 123, 31, 251, 113];
253         assert(digest == expected);
254     }
255 
256     /**
257      * Computes an _HMAC digest over the given range of data with the
258      * specified secret.
259      *
260      * Returns:
261      * The final _HMAC hash.
262      */
263     DigestType!H hmac(T...)(scope T data, scope const(ubyte)[] secret)
264     if (allSatisfy!(isDigestibleRange, typeof(data)))
265     {
266         import std.range.primitives : put;
267         auto hash = HMAC!(H, blockSize)(secret);
268         foreach (datum; data)
269             put(hash, datum);
270         return hash.finish();
271     }
272 
273     ///
274     @safe pure nothrow @nogc unittest
275     {
276         import std.algorithm.iteration : map;
277         import std.digest.sha : SHA1;
278         import std.string : representation;
279         string data = "Hello, world";
280         auto digest = data.representation
281                       .map!(a => cast(ubyte)(a+1))
282                       .hmac!SHA1("My s3cR3T keY".representation);
283         static assert(is(typeof(digest) == ubyte[20]));
284         static immutable expected = [
285             163, 208, 118, 179, 216, 93,
286             17, 10, 84, 200, 87, 104, 244,
287             111, 136, 214, 167, 210, 58, 10];
288         assert(digest == expected);
289     }
290 }
291 
292 ///
293 @safe pure nothrow @nogc unittest
294 {
295     import std.digest.sha : SHA1;
296     import std.string : representation;
297     string data1 = "Hello, world", data2 = "Hola mundo";
298     auto hmac = HMAC!SHA1("My s3cR3T keY".representation);
299     auto digest = hmac.put(data1.representation)
300                       .put(data2.representation)
301                       .finish();
302     static immutable expected = [
303         197, 57, 52, 3, 13, 194, 13,
304         36, 117, 228, 8, 11, 111, 51,
305         165, 3, 123, 31, 251, 113];
306     assert(digest == expected);
307 }
308 
309 @safe pure nothrow @nogc
310 unittest
311 {
312     import std.digest.md : MD5;
313     import std.range : isOutputRange;
314     static assert(isOutputRange!(HMAC!MD5, ubyte));
315     static assert(isDigest!(HMAC!MD5));
316     static assert(hasBlockSize!(HMAC!MD5) && HMAC!MD5.blockSize == MD5.blockSize);
317 }
318 
319 @safe pure nothrow
320 unittest
321 {
322     import std.digest.md : MD5;
323     import std.digest.sha : SHA1, SHA256;
324 
325     // Note, can't be UFCS because we don't want to import inside
326     // version (StdUnittest).
327     import std.digest : toHexString, LetterCase;
328     alias hex = toHexString!(LetterCase.lower);
329 
330     ubyte[] nada;
331     assert(hex(hmac!MD5   (nada, nada)) == "74e6f7298a9c2d168935f58c001bad88");
332     assert(hex(hmac!SHA1  (nada, nada)) == "fbdb1d1b18aa6c08324b7d64b71fb76370690e1d");
333     assert(hex(hmac!SHA256(nada, nada)) == "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad");
334 
335     import std.string : representation;
336     auto key      = "key".representation,
337          long_key = ("012345678901234567890123456789012345678901"
338             ~"234567890123456789012345678901234567890123456789").representation,
339          data1    = "The quick brown fox ".representation,
340          data2    = "jumps over the lazy dog".representation,
341          data     = data1 ~ data2;
342 
343     assert(hex(data.hmac!MD5   (key)) == "80070713463e7749b90c2dc24911e275");
344     assert(hex(data.hmac!SHA1  (key)) == "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9");
345     assert(hex(data.hmac!SHA256(key)) == "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8");
346 
347     assert(hex(data.hmac!MD5   (long_key)) == "e1728d68e05beae186ea768561963778");
348     assert(hex(data.hmac!SHA1  (long_key)) == "560d3cd77316e57ab4bba0c186966200d2b37ba3");
349     assert(hex(data.hmac!SHA256(long_key)) == "a1b0065a5d1edd93152c677e1bc1b1e3bc70d3a76619842e7f733f02b8135c04");
350 
351     assert(hmac!MD5   (key).put(data1).put(data2).finish == data.hmac!MD5   (key));
352     assert(hmac!SHA1  (key).put(data1).put(data2).finish == data.hmac!SHA1  (key));
353     assert(hmac!SHA256(key).put(data1).put(data2).finish == data.hmac!SHA256(key));
354 }