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 }