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 }