1 module kdf.pbkdf2; 2 3 import std.digest.sha : SHA1; 4 import std.digest.digest : isDigest, digestLength; 5 6 /** 7 * Returns a binary digest for the PBKDF2 hash algorithm of `data` with the given `salt`. 8 * It iterates `iterations` time and produces a key of `dkLen` bytes. 9 * By default SHA-1 is used as hash function. 10 * 11 * Params: 12 * data = data to hash 13 * salt = salt to use to hash data 14 * iterations = number of iterations to create hash with 15 * dkLen = intended length of the derived key, at most (2^32 - 1) * hLen 16 * 17 * Authors: T. Chaloupka 18 */ 19 auto pbkdf2(H = SHA1)(in ubyte[] data, in ubyte[] salt, uint iterations = 4096, uint dkLen = 256) 20 if (isDigest!H) 21 in 22 { 23 import std.exception; 24 enforce(dkLen < (2^32 - 1) * digestLength!H, "Derived key too long"); 25 } 26 body 27 { 28 auto f(PRF)(PRF prf, in ubyte[] salt, uint c, uint block) 29 { 30 import std.bitmanip : nativeToBigEndian; 31 32 auto res = prf.put(salt ~ nativeToBigEndian(block)).finish(); 33 auto prev = res; 34 foreach(i; 1..c) 35 { 36 prev = prf.put(prev).finish(); 37 foreach(n, ref r; res) r ^= prev[n]; 38 } 39 40 return res; 41 } 42 43 import std.digest.hmac; 44 import std.range : iota; 45 46 alias digestLength!H hLen; 47 48 auto hmac = HMAC!H(data); 49 auto l = cast(uint)((dkLen + hLen - 1)/ hLen); 50 51 uint idx; 52 ubyte[] res = new ubyte[l * hLen]; 53 foreach(block; iota(1, l+1)) 54 { 55 res[idx..idx+hLen] = f(hmac, salt, iterations, block); 56 idx += hLen; 57 } 58 59 return res[0..dkLen]; 60 } 61 62 unittest 63 { 64 import std..string : representation; 65 import std.format : format; 66 import std.digest.digest : toHexString, LetterCase; 67 import std.range : repeat, take; 68 import std.array; 69 import std.digest.sha; 70 71 // Test vectors from rfc6070 72 73 auto res = pbkdf2("password".representation, "salt".representation, 1, 20).toHexString!(LetterCase.lower); 74 assert(res == "0c60c80f961f0e71f3a9b524af6012062fe037a6"); 75 76 res = pbkdf2("password".representation, "salt".representation, 2, 20).toHexString!(LetterCase.lower); 77 assert(res == "ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957"); 78 79 res = pbkdf2("password".representation, "salt".representation, 4096, 20).toHexString!(LetterCase.lower); 80 assert(res == "4b007901b765489abead49d926f721d065a429c1"); 81 82 //Takes too long so it s versioned out.. 83 version(LongTests) 84 { 85 res = pbkdf2("password".representation, "salt".representation, 16_777_216, 20).toHexString!(LetterCase.lower); 86 assert(res == "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984"); 87 } 88 89 res = pbkdf2("passwordPASSWORDpassword".representation, "saltSALTsaltSALTsaltSALTsaltSALTsalt".representation, 4096, 25).toHexString!(LetterCase.lower); 90 assert(res == "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038"); 91 92 res = pbkdf2("pass\0word".representation, "sa\0lt".representation, 4096, 16).toHexString!(LetterCase.lower); 93 assert(res == "56fa6aa75548099dcc37d7f03425e0c3"); 94 95 // Test vectors from Crypt-PBKDF2 96 97 res = pbkdf2("password".representation, "ATHENA.MIT.EDUraeburn".representation, 1, 16).toHexString!(LetterCase.lower); 98 assert(res == "cdedb5281bb2f801565a1122b2563515"); 99 100 res = pbkdf2("password".representation, "ATHENA.MIT.EDUraeburn".representation, 1, 32).toHexString!(LetterCase.lower); 101 assert(res == "cdedb5281bb2f801565a1122b25635150ad1f7a04bb9f3a333ecc0e2e1f70837"); 102 103 res = pbkdf2("password".representation, "ATHENA.MIT.EDUraeburn".representation, 2, 16).toHexString!(LetterCase.lower); 104 assert(res == "01dbee7f4a9e243e988b62c73cda935d"); 105 106 res = pbkdf2("password".representation, "ATHENA.MIT.EDUraeburn".representation, 2, 32).toHexString!(LetterCase.lower); 107 assert(res == "01dbee7f4a9e243e988b62c73cda935da05378b93244ec8f48a99e61ad799d86"); 108 109 res = pbkdf2("password".representation, "ATHENA.MIT.EDUraeburn".representation, 1200, 32).toHexString!(LetterCase.lower); 110 assert(res == "5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13"); 111 112 res = pbkdf2((cast(ubyte)'X').repeat.take(64).array, "pass phrase equals block size".representation, 1200, 32).toHexString!(LetterCase.lower); 113 assert(res == "139c30c0966bc32ba55fdbf212530ac9c5ec59f1a452f5cc9ad940fea0598ed1"); 114 115 res = pbkdf2((cast(ubyte)'X').repeat.take(65).array, "pass phrase exceeds block size".representation, 1200, 32).toHexString!(LetterCase.lower); 116 assert(res == "9ccad6d468770cd51b10e6a68721be611a8b4d282601db3b36be9246915ec82a"); 117 118 // Test vectors for SHA256 119 120 res = pbkdf2!SHA256("password".representation, "salt".representation, 1, 32).toHexString!(LetterCase.lower); 121 assert(res == "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b"); 122 123 res = pbkdf2!SHA256("password".representation, "salt".representation, 2, 32).toHexString!(LetterCase.lower); 124 assert(res == "ae4d0c95af6b46d32d0adff928f06dd02a303f8ef3c251dfd6e2d85a95474c43"); 125 126 res = pbkdf2!SHA256("password".representation, "salt".representation, 4096, 32).toHexString!(LetterCase.lower); 127 assert(res == "c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a"); 128 129 //Takes too long so it s versioned out.. 130 version(LongTests) 131 { 132 res = pbkdf2!SHA256("password".representation, "salt".representation, 16_777_216, 32).toHexString!(LetterCase.lower); 133 assert(res == "cf81c66fe8cfc04d1f31ecb65dab4089f7f179e89b3b0bcb17ad10e3ac6eba46"); 134 } 135 136 res = pbkdf2!SHA256("passwordPASSWORDpassword".representation, "saltSALTsaltSALTsaltSALTsaltSALTsalt".representation, 4096, 40).toHexString!(LetterCase.lower); 137 assert(res == "348c89dbcbd32b2f32d814b8116e84cf2b17347ebc1800181c4e2a1fb8dd53e1c635518c7dac47e9"); 138 139 res = pbkdf2!SHA256("pass\0word".representation, "sa\0lt".representation, 4096, 16).toHexString!(LetterCase.lower); 140 assert(res == "89b69d0516f829893c696226650a8687"); 141 }