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 }