]>
Commit | Line | Data |
---|---|---|
b4c522fa IB |
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 | ||
5fee5ec3 | 14 | Source: $(PHOBOSSRC std/digest/hmac.d) |
b4c522fa IB |
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; | |
5fee5ec3 IB |
76 | typeof(secret) secretBytes = secret; |
77 | ||
b4c522fa IB |
78 | if (secret.length > blockSize / 8) |
79 | { | |
80 | digest.start(); | |
81 | digest.put(secret); | |
82 | buffer = digest.finish(); | |
5fee5ec3 | 83 | secretBytes = buffer[]; |
b4c522fa IB |
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; | |
5fee5ec3 | 89 | secretBytes.copy(key[]); |
b4c522fa IB |
90 | |
91 | start(); | |
92 | } | |
93 | ||
94 | /// | |
95 | @safe pure nothrow @nogc unittest | |
96 | { | |
5fee5ec3 | 97 | import std.digest.sha : SHA1; |
b4c522fa IB |
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 | { | |
5fee5ec3 | 134 | import std.digest.sha : SHA1; |
b4c522fa IB |
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 | { | |
5fee5ec3 | 201 | import std.digest.sha : SHA1; |
b4c522fa IB |
202 | import std.string : representation; |
203 | string data1 = "Hello, world", data2 = "Hola mundo"; | |
204 | auto hmac = HMAC!SHA1("My s3cR3T keY".representation); | |
205 | auto digest = 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(digest == expected); | |
213 | } | |
214 | } | |
215 | ||
5fee5ec3 | 216 | /// ditto |
b4c522fa IB |
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 | { | |
5fee5ec3 | 242 | import std.digest.sha : SHA1; |
b4c522fa IB |
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; | |
5fee5ec3 | 277 | import std.digest.sha : SHA1; |
b4c522fa IB |
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 | ||
5fee5ec3 IB |
292 | /// |
293 | @safe pure nothrow @nogc unittest | |
b4c522fa | 294 | { |
5fee5ec3 IB |
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); | |
b4c522fa IB |
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 | ||
5fee5ec3 IB |
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 | ||
b4c522fa | 330 | ubyte[] nada; |
5fee5ec3 IB |
331 | assert(hex(hmac!MD5 (nada, nada)) == "74e6f7298a9c2d168935f58c001bad88"); |
332 | assert(hex(hmac!SHA1 (nada, nada)) == "fbdb1d1b18aa6c08324b7d64b71fb76370690e1d"); | |
333 | assert(hex(hmac!SHA256(nada, nada)) == "b613679a0814d9ec772f95d778c35fc5ff1697c493715653c6c712144292c5ad"); | |
b4c522fa IB |
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 | ||
5fee5ec3 IB |
343 | assert(hex(data.hmac!MD5 (key)) == "80070713463e7749b90c2dc24911e275"); |
344 | assert(hex(data.hmac!SHA1 (key)) == "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9"); | |
345 | assert(hex(data.hmac!SHA256(key)) == "f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8"); | |
b4c522fa | 346 | |
5fee5ec3 IB |
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"); | |
b4c522fa IB |
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 | } |