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         if (secret.length > blockSize / 8)
77         {
78             digest.start();
79             digest.put(secret);
80             buffer = digest.finish();
81             secret = buffer[];
82         }
83 
84         // if secret is too short, it will be padded with zeroes
85         // (the key buffer is already zero-initialized)
86         import std.algorithm.mutation : copy;
87         secret.copy(key[]);
88 
89         start();
90     }
91 
92     ///
93     @safe pure nothrow @nogc unittest
94     {
95         import std.digest.sha : SHA1;
96         import std..string : representation;
97         auto hmac = HMAC!SHA1("My s3cR3T keY".representation);
98         hmac.put("Hello, world".representation);
99         static immutable expected = [
100             130, 32, 235, 44, 208, 141,
101             150, 232, 211, 214, 162, 195,
102             188, 127, 52, 89, 100, 68, 90, 216];
103         assert(hmac.finish() == expected);
104     }
105 
106     /**
107      * Reinitializes the digest, making it ready for reuse.
108      *
109      * Note:
110      * The constructor leaves the digest in an initialized state, so that this
111      * method only needs to be called if an unfinished digest is to be reused.
112      *
113      * Returns:
114      * A reference to the digest for convenient chaining.
115      */
116 
117     ref HMAC!(H, blockSize) start() return
118     {
119         ubyte[blockSize / 8] ipad = void;
120         foreach (immutable i; 0 .. blockSize / 8)
121             ipad[i] = key[i] ^ 0x36;
122 
123         digest.start();
124         digest.put(ipad[]);
125 
126         return this;
127     }
128 
129     ///
130     @safe pure nothrow @nogc unittest
131     {
132         import std.digest.sha : SHA1;
133         import std..string : representation;
134         string data1 = "Hello, world", data2 = "Hola mundo";
135         auto hmac = HMAC!SHA1("My s3cR3T keY".representation);
136         hmac.put(data1.representation);
137         hmac.start();                   // reset digest
138         hmac.put(data2.representation); // start over
139         static immutable expected = [
140             122, 151, 232, 240, 249, 80,
141             19, 178, 186, 77, 110, 23, 208,
142             52, 11, 88, 34, 151, 192, 255];
143         assert(hmac.finish() == expected);
144     }
145 
146     /**
147      * Feeds a piece of data into the hash computation. This method allows the
148      * type to be used as an $(REF OutputRange, std,range).
149      *
150      * Returns:
151      * A reference to the digest for convenient chaining.
152      */
153 
154     ref HMAC!(H, blockSize) put(in ubyte[] data...) return
155     {
156         digest.put(data);
157         return this;
158     }
159 
160     ///
161     @safe pure nothrow @nogc unittest
162     {
163         import std.digest.hmac, std.digest.sha;
164         import std..string : representation;
165         string data1 = "Hello, world", data2 = "Hola mundo";
166         auto hmac = HMAC!SHA1("My s3cR3T keY".representation);
167         hmac.put(data1.representation)
168             .put(data2.representation);
169         static immutable expected = [
170             197, 57, 52, 3, 13, 194, 13,
171             36, 117, 228, 8, 11, 111, 51,
172             165, 3, 123, 31, 251, 113];
173         assert(hmac.finish() == expected);
174     }
175 
176     /**
177      * Resets the digest and returns the finished hash.
178      */
179 
180     DigestType!H finish()
181     {
182         ubyte[blockSize / 8] opad = void;
183         foreach (immutable i; 0 .. blockSize / 8)
184             opad[i] = key[i] ^ 0x5c;
185 
186         auto tmp = digest.finish();
187 
188         digest.start();
189         digest.put(opad[]);
190         digest.put(tmp);
191         auto result = digest.finish();
192         start();    // reset the digest
193         return result;
194     }
195 
196     ///
197     @safe pure nothrow @nogc unittest
198     {
199         import std.digest.sha : SHA1;
200         import std..string : representation;
201         string data1 = "Hello, world", data2 = "Hola mundo";
202         auto hmac = HMAC!SHA1("My s3cR3T keY".representation);
203         auto digest = hmac.put(data1.representation)
204                           .put(data2.representation)
205                           .finish();
206         static immutable expected = [
207             197, 57, 52, 3, 13, 194, 13,
208             36, 117, 228, 8, 11, 111, 51,
209             165, 3, 123, 31, 251, 113];
210         assert(digest == expected);
211     }
212 }
213 
214 /// ditto
215 template hmac(H)
216 if (isDigest!H && hasBlockSize!H)
217 {
218     alias hmac = hmac!(H, H.blockSize);
219 }
220 
221 /// ditto
222 template hmac(H, size_t blockSize)
223 if (isDigest!H)
224 {
225     /**
226      * Constructs an HMAC digest with the specified secret.
227      *
228      * Returns:
229      * An instance of HMAC that can be fed data as desired, and finished
230      * to compute the final hash when done.
231      */
232     auto hmac(scope const(ubyte)[] secret)
233     {
234         return HMAC!(H, blockSize)(secret);
235     }
236 
237     ///
238     @safe pure nothrow @nogc unittest
239     {
240         import std.digest.sha : SHA1;
241         import std..string : representation;
242         string data1 = "Hello, world", data2 = "Hola mundo";
243         auto digest = hmac!SHA1("My s3cR3T keY".representation)
244                           .put(data1.representation)
245                           .put(data2.representation)
246                           .finish();
247         static immutable expected = [
248             197, 57, 52, 3, 13, 194, 13, 36,
249             117, 228, 8, 11, 111, 51, 165,
250             3, 123, 31, 251, 113];
251         assert(digest == expected);
252     }
253 
254     /**
255      * Computes an _HMAC digest over the given range of data with the
256      * specified secret.
257      *
258      * Returns:
259      * The final _HMAC hash.
260      */
261     DigestType!H hmac(T...)(scope T data, scope const(ubyte)[] secret)
262     if (allSatisfy!(isDigestibleRange, typeof(data)))
263     {
264         import std.range.primitives : put;
265         auto hash = HMAC!(H, blockSize)(secret);
266         foreach (datum; data)
267             put(hash, datum);
268         return hash.finish();
269     }
270 
271     ///
272     @safe pure nothrow @nogc unittest
273     {
274         import std.algorithm.iteration : map;
275         import std.digest.sha : SHA1;
276         import std..string : representation;
277         string data = "Hello, world";
278         auto digest = data.representation
279                       .map!(a => cast(ubyte)(a+1))
280                       .hmac!SHA1("My s3cR3T keY".representation);
281         static assert(is(typeof(digest) == ubyte[20]));
282         static immutable expected = [
283             163, 208, 118, 179, 216, 93,
284             17, 10, 84, 200, 87, 104, 244,
285             111, 136, 214, 167, 210, 58, 10];
286         assert(digest == expected);
287     }
288 }
289 
290 ///
291 @safe pure nothrow @nogc unittest
292 {
293     import std.digest.sha : SHA1;
294     import std..string : representation;
295     string data1 = "Hello, world", data2 = "Hola mundo";
296     auto hmac = HMAC!SHA1("My s3cR3T keY".representation);
297     auto digest = hmac.put(data1.representation)
298                       .put(data2.representation)
299                       .finish();
300     static immutable expected = [
301         197, 57, 52, 3, 13, 194, 13,
302         36, 117, 228, 8, 11, 111, 51,
303         165, 3, 123, 31, 251, 113];
304     assert(digest == expected);
305 }
306 
307 @safe pure nothrow @nogc
308 unittest
309 {
310     import std.digest.md : MD5;
311     import std.range : isOutputRange;
312     static assert(isOutputRange!(HMAC!MD5, ubyte));
313     static assert(isDigest!(HMAC!MD5));
314     static assert(hasBlockSize!(HMAC!MD5) && HMAC!MD5.blockSize == MD5.blockSize);
315 }
316 
317 @safe pure nothrow
318 unittest
319 {
320     import std.digest.md : MD5;
321     import std.digest.sha : SHA1, SHA256;
322 
323     // Note, can't be UFCS because we don't want to import inside
324     // version (StdUnittest).
325     import std.digest : toHexString, LetterCase;
326     alias hex = toHexString!(LetterCase.lower);
327 
328     ubyte[] nada;
329     assert(hex(hmac!MD5   (nada, nada)) == "74e6f7298a9c2d168935f58c001bad88");
330     assert(hex(hmac!SHA1  (nada, nada)) == "fbdb1d1b18aa6c08324b7d64b71fb76370690e1d");
331     assert(hex(hmac!SHA256(nada, nada)) == "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad");
332 
333     import std..string : representation;
334     auto key      = "key".representation,
335          long_key = ("012345678901234567890123456789012345678901"
336             ~"234567890123456789012345678901234567890123456789").representation,
337          data1    = "The quick brown fox ".representation,
338          data2    = "jumps over the lazy dog".representation,
339          data     = data1 ~ data2;
340 
341     assert(hex(data.hmac!MD5   (key)) == "80070713463e7749b90c2dc24911e275");
342     assert(hex(data.hmac!SHA1  (key)) == "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9");
343     assert(hex(data.hmac!SHA256(key)) == "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8");
344 
345     assert(hex(data.hmac!MD5   (long_key)) == "e1728d68e05beae186ea768561963778");
346     assert(hex(data.hmac!SHA1  (long_key)) == "560d3cd77316e57ab4bba0c186966200d2b37ba3");
347     assert(hex(data.hmac!SHA256(long_key)) == "a1b0065a5d1edd93152c677e1bc1b1e3bc70d3a76619842e7f733f02b8135c04");
348 
349     assert(hmac!MD5   (key).put(data1).put(data2).finish == data.hmac!MD5   (key));
350     assert(hmac!SHA1  (key).put(data1).put(data2).finish == data.hmac!SHA1  (key));
351     assert(hmac!SHA256(key).put(data1).put(data2).finish == data.hmac!SHA256(key));
352 }
Suggestion Box / Bug Report