1 /** 2 * License: 3 * Copyright (c) 2015, Tomáš Chaloupka 4 * 5 * Boost Software License 1.0 (BSL-1.0) 6 * 7 * Permission is hereby granted, free of charge, to any person or organization obtaining a copy 8 * of the software and accompanying documentation covered by this license (the "Software") to use, 9 * reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative 10 * works of the Software, and to permit third-parties to whom the Software is furnished to do so, 11 * all subject to the following: 12 * 13 * The copyright notices in the Software and this entire statement, including the above license 14 * grant, this restriction and the following disclaimer, must be included in all copies of the Software, 15 * in whole or in part, and all derivative works of the Software, unless such copies or derivative works 16 * are solely in the form of machine-executable object code generated by a source language processor. 17 * 18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 19 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 20 * PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE 21 * DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, 22 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 * OTHER DEALINGS IN THE SOFTWARE. 24 */ 25 module tagion.crypto.pbkdf2; 26 27 import std.digest.sha : SHA1; 28 29 static if (__VERSION__ >= 2080) 30 import std.digest : isDigest, digestLength; 31 else 32 import std.digest.digest : isDigest, digestLength; 33 34 /** 35 * Returns a binary digest for the PBKDF2 hash algorithm of `data` with the given `salt`. 36 * It iterates `iterations` time and produces a key of `dkLen` bytes. 37 * By default SHA-1 is used as hash function. 38 * 39 * Params: 40 * data = data to hash 41 * salt = salt to use to hash data 42 * iterations = number of iterations to create hash with 43 * dkLen = intended length of the derived key 44 * 45 * Authors: T. Chaloupka 46 */ 47 @safe 48 ubyte[] pbkdf2(H = SHA1)(in ubyte[] data, in ubyte[] salt, uint iterations = 4096, uint dkLen = 256) pure nothrow 49 if (isDigest!H) { 50 auto f(PRF)(PRF prf, in ubyte[] salt, const uint c, const uint block) { 51 import std.bitmanip : nativeToBigEndian; 52 53 auto res = prf.put(salt ~ nativeToBigEndian(block)).finish(); 54 auto prev = res; 55 foreach (i; 1 .. c) { 56 prev = prf.put(prev).finish(); 57 foreach (n, ref r; res) { 58 r ^= prev[n]; 59 } 60 } 61 62 return res; 63 } 64 65 import std.digest.hmac; 66 import std.range : iota; 67 68 alias hLen = digestLength!H; 69 70 auto hmac = HMAC!H(data); 71 auto l = cast(uint)((dkLen + hLen - 1) / hLen); 72 73 uint idx; 74 ubyte[] res = new ubyte[l * hLen]; 75 foreach (block; iota(1, l + 1)) { 76 res[idx .. idx + hLen] = f(hmac, salt, iterations, block); 77 idx += hLen; 78 } 79 80 return res[0 .. dkLen]; 81 } 82 83 @safe 84 unittest { 85 import std.string : representation; 86 import std.format : format; 87 88 static if (__VERSION__ >= 2080) 89 import std.digest : toHexString, LetterCase; 90 else 91 import std.digest.digest : toHexString, LetterCase; 92 import std.range : repeat, take; 93 import std.array; 94 import std.digest.sha; 95 96 // Test vectors from rfc6070 97 98 auto res = pbkdf2("password".representation, "salt".representation, 1, 20).toHexString!(LetterCase.lower); 99 assert(res == "0c60c80f961f0e71f3a9b524af6012062fe037a6"); 100 101 res = pbkdf2("password".representation, "salt".representation, 2, 20).toHexString!(LetterCase.lower); 102 assert(res == "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"); 103 104 res = pbkdf2("password".representation, "salt".representation, 4096, 20).toHexString!(LetterCase.lower); 105 assert(res == "4b007901b765489abead49d926f721d065a429c1"); 106 107 //Takes too long so it s versioned out.. 108 version (LongTests) { 109 res = pbkdf2("password".representation, "salt".representation, 16_777_216, 20).toHexString!(LetterCase.lower); 110 assert(res == "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"); 111 } 112 113 res = pbkdf2("passwordPASSWORDpassword".representation, "saltSALTsaltSALTsaltSALTsaltSALTsalt".representation, 4096, 25) 114 .toHexString!(LetterCase.lower); 115 assert(res == "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"); 116 117 res = pbkdf2("pass\0word".representation, "sa\0lt".representation, 4096, 16).toHexString!(LetterCase.lower); 118 assert(res == "56fa6aa75548099dcc37d7f03425e0c3"); 119 120 // Test vectors from Crypt-PBKDF2 121 122 res = pbkdf2("password".representation, "ATHENA.MIT.EDUraeburn".representation, 1, 16).toHexString!(LetterCase.lower); 123 assert(res == "cdedb5281bb2f801565a1122b2563515"); 124 125 res = pbkdf2("password".representation, "ATHENA.MIT.EDUraeburn".representation, 1, 32).toHexString!(LetterCase.lower); 126 assert(res == "cdedb5281bb2f801565a1122b25635150ad1f7a04bb9f3a333ecc0e2e1f70837"); 127 128 res = pbkdf2("password".representation, "ATHENA.MIT.EDUraeburn".representation, 2, 16).toHexString!(LetterCase.lower); 129 assert(res == "01dbee7f4a9e243e988b62c73cda935d"); 130 131 res = pbkdf2("password".representation, "ATHENA.MIT.EDUraeburn".representation, 2, 32).toHexString!(LetterCase.lower); 132 assert(res == "01dbee7f4a9e243e988b62c73cda935da05378b93244ec8f48a99e61ad799d86"); 133 134 res = pbkdf2("password".representation, "ATHENA.MIT.EDUraeburn".representation, 1200, 32).toHexString!(LetterCase 135 .lower); 136 assert(res == "5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13"); 137 138 res = pbkdf2((cast(ubyte) 'X').repeat.take(64).array, "pass phrase equals block size".representation, 1200, 32) 139 .toHexString!(LetterCase.lower); 140 assert(res == "139c30c0966bc32ba55fdbf212530ac9c5ec59f1a452f5cc9ad940fea0598ed1"); 141 142 res = pbkdf2((cast(ubyte) 'X').repeat.take(65).array, "pass phrase exceeds block size".representation, 1200, 32) 143 .toHexString!(LetterCase.lower); 144 assert(res == "9ccad6d468770cd51b10e6a68721be611a8b4d282601db3b36be9246915ec82a"); 145 146 // Test vectors for SHA256 147 148 res = pbkdf2!SHA256("password".representation, "salt".representation, 1, 32).toHexString!(LetterCase.lower); 149 assert(res == "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b"); 150 151 res = pbkdf2!SHA256("password".representation, "salt".representation, 2, 32).toHexString!(LetterCase.lower); 152 assert(res == "ae4d0c95af6b46d32d0adff928f06dd02a303f8ef3c251dfd6e2d85a95474c43"); 153 154 res = pbkdf2!SHA256("password".representation, "salt".representation, 4096, 32).toHexString!(LetterCase.lower); 155 assert(res == "c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a"); 156 157 //Takes too long so it s versioned out.. 158 version (LongTests) { 159 res = pbkdf2!SHA256("password".representation, "salt".representation, 16_777_216, 32).toHexString!(LetterCase 160 .lower); 161 assert(res == "cf81c66fe8cfc04d1f31ecb65dab4089f7f179e89b3b0bcb17ad10e3ac6eba46"); 162 } 163 164 res = pbkdf2!SHA256("passwordPASSWORDpassword".representation, "saltSALTsaltSALTsaltSALTsaltSALTsalt".representation, 4096, 40) 165 .toHexString!(LetterCase.lower); 166 assert(res == "348c89dbcbd32b2f32d814b8116e84cf2b17347ebc1800181c4e2a1fb8dd53e1c635518c7dac47e9"); 167 168 res = pbkdf2!SHA256("pass\0word".representation, "sa\0lt".representation, 4096, 16).toHexString!(LetterCase.lower); 169 assert(res == "89b69d0516f829893c696226650a8687"); 170 } 171 172 @safe 173 unittest { 174 /// pbkdf2 SHA512 test-sample taken from 175 /// https://stackoverflow.com/questions/15593184/pbkdf2-hmac-sha-512-test-vectors 176 import std.digest.sha; // : SHA512; 177 import std.stdio; 178 import std.string : representation; 179 180 alias pbkdf2_sha256 = pbkdf2!SHA512; 181 auto password = "password".representation; 182 auto salt = "salt".representation; 183 { 184 /** 185 * Input: 186 * P = "password" 187 * S = "salt" 188 * c = 1 189 * dkLen = 64 190 * 191 * Output: 192 * DK = 86 7f 70 cf 1a de 02 cf 193 * f3 75 25 99 a3 a5 3d c4 194 * af 34 c7 a6 69 81 5a e5 195 * d5 13 55 4e 1c 8c f2 52 196 * c0 2d 47 0a 28 5a 05 01 197 * ba d9 99 bf e9 43 c0 8f 198 * 05 02 35 d7 d6 8b 1d a5 199 * 5e 63 f7 3b 60 a5 7f ce 200 */ 201 immutable(ubyte[]) expected = [ 202 0x86, 0x7f, 0x70, 0xcf, 0x1a, 0xde, 0x02, 0xcf, 203 0xf3, 0x75, 0x25, 0x99, 0xa3, 0xa5, 0x3d, 0xc4, 204 0xaf, 0x34, 0xc7, 0xa6, 0x69, 0x81, 0x5a, 0xe5, 205 0xd5, 0x13, 0x55, 0x4e, 0x1c, 0x8c, 0xf2, 0x52, 206 0xc0, 0x2d, 0x47, 0x0a, 0x28, 0x5a, 0x05, 0x01, 207 0xba, 0xd9, 0x99, 0xbf, 0xe9, 0x43, 0xc0, 0x8f, 208 0x05, 0x02, 0x35, 0xd7, 0xd6, 0x8b, 0x1d, 0xa5, 209 0x5e, 0x63, 0xf7, 0x3b, 0x60, 0xa5, 0x7f, 0xce, 210 ]; 211 const count = 1; 212 const result = 213 pbkdf2_sha256(password, salt, count, 64); 214 assert(result == expected); 215 } 216 { 217 /** 218 * Input: 219 * P = "password" 220 * S = "salt" 221 * c = 2 222 * dkLen = 64 223 * 224 * Output: 225 * DK = e1 d9 c1 6a a6 81 70 8a 226 * 45 f5 c7 c4 e2 15 ce b6 227 * 6e 01 1a 2e 9f 00 40 71 228 * 3f 18 ae fd b8 66 d5 3c 229 * f7 6c ab 28 68 a3 9b 9f 230 * 78 40 ed ce 4f ef 5a 82 231 * be 67 33 5c 77 a6 06 8e 232 * 04 11 27 54 f2 7c cf 4e 233 */ 234 immutable(ubyte[]) expected = [ 235 0xe1, 0xd9, 0xc1, 0x6a, 0xa6, 0x81, 0x70, 0x8a, 236 0x45, 0xf5, 0xc7, 0xc4, 0xe2, 0x15, 0xce, 0xb6, 237 0x6e, 0x01, 0x1a, 0x2e, 0x9f, 0x00, 0x40, 0x71, 238 0x3f, 0x18, 0xae, 0xfd, 0xb8, 0x66, 0xd5, 0x3c, 239 0xf7, 0x6c, 0xab, 0x28, 0x68, 0xa3, 0x9b, 0x9f, 240 0x78, 0x40, 0xed, 0xce, 0x4f, 0xef, 0x5a, 0x82, 241 0xbe, 0x67, 0x33, 0x5c, 0x77, 0xa6, 0x06, 0x8e, 242 0x04, 0x11, 0x27, 0x54, 0xf2, 0x7c, 0xcf, 0x4e, 243 ]; 244 const count = 2; 245 const result = 246 pbkdf2_sha256(password, salt, count, 64); 247 assert(result == expected); 248 } 249 { 250 /* 251 * Input: 252 * P = "password" 253 * S = "salt" 254 * c = 4096 255 * dkLen = 64 256 * 257 * Output: 258 * DK = d1 97 b1 b3 3d b0 14 3e 259 * 01 8b 12 f3 d1 d1 47 9e 260 * 6c de bd cc 97 c5 c0 f8 261 * 7f 69 02 e0 72 f4 57 b5 262 * 14 3f 30 60 26 41 b3 d5 263 * 5c d3 35 98 8c b3 6b 84 264 * 37 60 60 ec d5 32 e0 39 265 * b7 42 a2 39 43 4a f2 d5 266 */ 267 immutable(ubyte[]) expected = [ 268 0xd1, 0x97, 0xb1, 0xb3, 0x3d, 0xb0, 0x14, 0x3e, 269 0x01, 0x8b, 0x12, 0xf3, 0xd1, 0xd1, 0x47, 0x9e, 270 0x6c, 0xde, 0xbd, 0xcc, 0x97, 0xc5, 0xc0, 0xf8, 271 0x7f, 0x69, 0x02, 0xe0, 0x72, 0xf4, 0x57, 0xb5, 272 0x14, 0x3f, 0x30, 0x60, 0x26, 0x41, 0xb3, 0xd5, 273 0x5c, 0xd3, 0x35, 0x98, 0x8c, 0xb3, 0x6b, 0x84, 274 0x37, 0x60, 0x60, 0xec, 0xd5, 0x32, 0xe0, 0x39, 275 0xb7, 0x42, 0xa2, 0x39, 0x43, 0x4a, 0xf2, 0xd5, 276 ]; 277 const count = 4096; 278 const result = 279 pbkdf2_sha256(password, salt, count, 64); 280 assert(result == expected); 281 282 } 283 { 284 285 /** 286 * Input: 287 * P = "passwordPASSWORDpassword" 288 * S = "saltSALTsaltSALTsaltSALTsaltSALTsalt" 289 * c = 4096 290 * dkLen = 64 291 * 292 * 293 * Output: 294 * DK = 8c 05 11 f4 c6 e5 97 c6 295 * ac 63 15 d8 f0 36 2e 22 296 * 5f 3c 50 14 95 ba 23 b8 297 * 68 c0 05 17 4d c4 ee 71 298 * 11 5b 59 f9 e6 0c d9 53 299 * 2f a3 3e 0f 75 ae fe 30 300 * 22 5c 58 3a 18 6c d8 2b 301 * d4 da ea 97 24 a3 d3 b8 302 */ 303 immutable(ubyte[]) expected = [ 304 0x8c, 0x05, 0x11, 0xf4, 0xc6, 0xe5, 0x97, 0xc6, 305 0xac, 0x63, 0x15, 0xd8, 0xf0, 0x36, 0x2e, 0x22, 306 0x5f, 0x3c, 0x50, 0x14, 0x95, 0xba, 0x23, 0xb8, 307 0x68, 0xc0, 0x05, 0x17, 0x4d, 0xc4, 0xee, 0x71, 308 0x11, 0x5b, 0x59, 0xf9, 0xe6, 0x0c, 0xd9, 0x53, 309 0x2f, 0xa3, 0x3e, 0x0f, 0x75, 0xae, 0xfe, 0x30, 310 0x22, 0x5c, 0x58, 0x3a, 0x18, 0x6c, 0xd8, 0x2b, 311 0xd4, 0xda, 0xea, 0x97, 0x24, 0xa3, 0xd3, 0xb8, 312 ]; 313 password = "passwordPASSWORDpassword".representation; 314 salt = "saltSALTsaltSALTsaltSALTsaltSALTsalt".representation; 315 const count = 4096; 316 const result = 317 pbkdf2_sha256(password, salt, count, 64); 318 assert(result == expected); 319 } 320 }