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