1 module 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 = 1000, uint dkLen = 24)
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 	//Works, but takes too long..
83 //	res = pbkdf2("password".representation, "salt".representation, 16777216, 20).toHexString!(LetterCase.lower);
84 //	assert(res == "eefe3d61cd4da4e4e9945b3d6ba2158c2634e984");
85 
86 	res = pbkdf2("passwordPASSWORDpassword".representation, "saltSALTsaltSALTsaltSALTsaltSALTsalt".representation, 4096, 25).toHexString!(LetterCase.lower);
87 	assert(res == "3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038");
88 
89 	res = pbkdf2("pass\0word".representation, "sa\0lt".representation, 4096, 16).toHexString!(LetterCase.lower);
90 	assert(res == "56fa6aa75548099dcc37d7f03425e0c3");
91 
92 	// Test vectors from Crypt-PBKDF2
93 
94 	res = pbkdf2("password".representation, "ATHENA.MIT.EDUraeburn".representation, 1, 16).toHexString!(LetterCase.lower);
95 	assert(res == "cdedb5281bb2f801565a1122b2563515");
96 
97 	res = pbkdf2("password".representation, "ATHENA.MIT.EDUraeburn".representation, 1, 32).toHexString!(LetterCase.lower);
98 	assert(res == "cdedb5281bb2f801565a1122b25635150ad1f7a04bb9f3a333ecc0e2e1f70837");
99 
100 	res = pbkdf2("password".representation, "ATHENA.MIT.EDUraeburn".representation, 2, 16).toHexString!(LetterCase.lower);
101 	assert(res == "01dbee7f4a9e243e988b62c73cda935d");
102 
103 	res = pbkdf2("password".representation, "ATHENA.MIT.EDUraeburn".representation, 2, 32).toHexString!(LetterCase.lower);
104 	assert(res == "01dbee7f4a9e243e988b62c73cda935da05378b93244ec8f48a99e61ad799d86");
105 
106 	res = pbkdf2("password".representation, "ATHENA.MIT.EDUraeburn".representation, 1200, 32).toHexString!(LetterCase.lower);
107 	assert(res == "5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13");
108 
109 	res = pbkdf2((cast(ubyte)'X').repeat.take(64).array, "pass phrase equals block size".representation, 1200, 32).toHexString!(LetterCase.lower);
110 	assert(res == "139c30c0966bc32ba55fdbf212530ac9c5ec59f1a452f5cc9ad940fea0598ed1");
111 
112 	res = pbkdf2((cast(ubyte)'X').repeat.take(65).array, "pass phrase exceeds block size".representation, 1200, 32).toHexString!(LetterCase.lower);
113 	assert(res == "9ccad6d468770cd51b10e6a68721be611a8b4d282601db3b36be9246915ec82a");
114 
115 	// Test vectors for SHA256
116 
117 	res = pbkdf2!SHA256("password".representation, "salt".representation, 1, 32).toHexString!(LetterCase.lower);
118 	assert(res == "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b");
119 
120 	res = pbkdf2!SHA256("password".representation, "salt".representation, 2, 32).toHexString!(LetterCase.lower);
121 	assert(res == "ae4d0c95af6b46d32d0adff928f06dd02a303f8ef3c251dfd6e2d85a95474c43");
122 
123 	res = pbkdf2!SHA256("password".representation, "salt".representation, 4096, 32).toHexString!(LetterCase.lower);
124 	assert(res == "c5e478d59288c841aa530db6845c4c8d962893a001ce4e11a4963873aa98134a");
125 
126 	//Works, but takes too long..
127 //	res = pbkdf2!SHA256("password".representation, "salt".representation, 16777216, 32).toHexString!(LetterCase.lower);
128 //	assert(res == "cf81c66fe8cfc04d1f31ecb65dab4089f7f179e89b3b0bcb17ad10e3ac6eba46");
129 
130 	res = pbkdf2!SHA256("passwordPASSWORDpassword".representation, "saltSALTsaltSALTsaltSALTsaltSALTsalt".representation, 4096, 40).toHexString!(LetterCase.lower);
131 	assert(res == "348c89dbcbd32b2f32d814b8116e84cf2b17347ebc1800181c4e2a1fb8dd53e1c635518c7dac47e9");
132 
133 	res = pbkdf2!SHA256("pass\0word".representation, "sa\0lt".representation, 4096, 16).toHexString!(LetterCase.lower);
134 	assert(res == "89b69d0516f829893c696226650a8687");
135 }