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 }