1 module secured.kdf;
2 
3 import std.typecons;
4 import std.format;
5 
6 import deimos.openssl.evp;
7 import secured.openssl;
8 
9 import secured.hash;
10 import secured.random;
11 import secured.util;
12 
13 public struct KdfResult {
14     public ubyte[] salt;
15     public ubyte[] key;
16 }
17 
18 @safe public KdfResult pbkdf2(string password, uint iterations = 1_000_000) {
19     KdfResult result;
20     result.salt = random(getHashLength(HashAlgorithm.SHA2_384));
21     result.key = pbkdf2_ex(password, result.salt, HashAlgorithm.SHA2_384, getHashLength(HashAlgorithm.SHA2_384), iterations);
22     return result;
23 }
24 
25 @safe public bool pbkdf2_verify(KdfResult test, string password, uint iterations = 1_000_000) {
26     ubyte[] key = pbkdf2_ex(password, test.salt, HashAlgorithm.SHA2_384, getHashLength(HashAlgorithm.SHA2_384), iterations);
27     return constantTimeEquality(test.key, key);
28 }
29 
30 @trusted public ubyte[] pbkdf2_ex(string password, const ubyte[] salt, HashAlgorithm func, uint outputLen, uint iterations)
31 {
32     if (salt.length != getHashLength(func)) {
33         throw new CryptographicException(format("The PBKDF2 salt must be %s bytes in length.", getHashLength(func)));
34     }
35     if (outputLen > getHashLength(func)) {
36         throw new CryptographicException(format("The PBKDF2 output length must be less than or equal to %s bytes in length.", getHashLength(func)));
37     }
38 
39     ubyte[] output = new ubyte[outputLen];
40     if(PKCS5_PBKDF2_HMAC(password.ptr, cast(int)password.length, salt.ptr, cast(int)salt.length, iterations, getOpenSSLHashAlgorithm(func), outputLen, output.ptr) == 0) {
41         throw new CryptographicException("Unable to execute PBKDF2 hash function.");
42     }
43     return output;
44 }
45 
46 @safe public bool pbkdf2_verify_ex(const ubyte[] test, string password, const ubyte[] salt, HashAlgorithm func, uint outputLen, uint iterations) {
47     ubyte[] key = pbkdf2_ex(password, salt, func, outputLen, iterations);
48     return constantTimeEquality(test, key);
49 }
50 
51 unittest
52 {
53     import std.datetime.stopwatch;
54     import std.digest;
55     import std.stdio;
56 
57     writeln("Testing PBKDF2 Basic Methods:");
58 
59     //Test basic methods
60     auto sw = StopWatch(AutoStart.no);
61     sw.start();
62     auto result = pbkdf2("password");
63     sw.stop();
64     writefln("PBKDF2 took %sms for 1,000,000 iterations", sw.peek.total!"msecs");
65 
66     assert(result.key.length == 48);
67     assert(pbkdf2_verify(result, "password"));
68     writeln(toHexString!(LetterCase.lower)(result.key));
69 
70     //Test extended methods
71     ubyte[64] salt = [ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
72                        0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
73                        0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
74                        0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
75 
76     ubyte[] key = pbkdf2_ex("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", salt, HashAlgorithm.SHA2_512, 64, 100000);
77     assert(pbkdf2_verify_ex(key, "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", salt, HashAlgorithm.SHA2_512, 64, 100000));
78     writeln(toHexString!(LetterCase.lower)(key));
79 }
80 
81 unittest
82 {
83     import std.digest;
84     import std.stdio;
85 
86     writeln("Testing PBKDF2 Extended with Defaults:");
87 
88     ubyte[48] key = [ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
89                       0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
90                       0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
91 
92     ubyte[] vec1 = pbkdf2_ex("", key, HashAlgorithm.SHA2_384, 48, 25000);
93     ubyte[] vec2 = pbkdf2_ex("abc", key, HashAlgorithm.SHA2_384, 48, 25000);
94     ubyte[] vec3 = pbkdf2_ex("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", key, HashAlgorithm.SHA2_384, 48, 25000);
95 
96     writeln(toHexString!(LetterCase.lower)(vec1));
97     writeln(toHexString!(LetterCase.lower)(vec2));
98     writeln(toHexString!(LetterCase.lower)(vec3));
99 
100     assert(toHexString!(LetterCase.lower)(vec1) == "b0ddf56b90903d638ec8d07a4205ba2bcfa944955d553e1ef3f91cba84e8e3bde9db7c8ccf14df26f8305fc8634572f9");
101     assert(toHexString!(LetterCase.lower)(vec2) == "b0a5e09a38bee3eb2b84d477d5259ef7bebf0e48d9512178f7e26cc330278ff45417d47d84db06a12b8ea49377a7c7cb");
102     assert(toHexString!(LetterCase.lower)(vec3) == "d1aacafea3a9fdf3ee6236b1b45527974ea01539b4a7cc493bba56e15e14d520b2834d7bf22b83bb5c21c4bccb423be2");
103 }
104 
105 unittest
106 {
107     import std.digest;
108     import std.stdio;
109 
110     writeln("Testing PBKDF2 Extended with Custom Iterations:");
111 
112     ubyte[48] key = [0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
113                      0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
114                      0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
115 
116     ubyte[] vec1 = pbkdf2_ex("", key, HashAlgorithm.SHA2_384, 48, 150000);
117     ubyte[] vec2 = pbkdf2_ex("abc", key, HashAlgorithm.SHA2_384, 48, 150000);
118     ubyte[] vec3 = pbkdf2_ex("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", key, HashAlgorithm.SHA2_384, 48, 150000);
119 
120     writeln(toHexString!(LetterCase.lower)(vec1));
121     writeln(toHexString!(LetterCase.lower)(vec2));
122     writeln(toHexString!(LetterCase.lower)(vec3));
123 
124     assert(toHexString!(LetterCase.lower)(vec1) == "babdcbbf4ff89367ed223d2edd06ef5473ac9cdc827783ed0b4b5eafd9e4097beb2ef66d6fc92d24dbf4b86aa51b4a0f");
125     assert(toHexString!(LetterCase.lower)(vec2) == "8894348ccea06d79f80382ae7d4434c0f2ef41f871d936604f426518ab23bde4410fddce6dad943c95de75dbece9b54a");
126     assert(toHexString!(LetterCase.lower)(vec3) == "fba55e91818c35b1e4cc753fbd01a6cd138c49da472b58b2d7c4860ba39a3dd9032f8f641aadcd74a819361ed27c9a0f");
127 }
128 
129 unittest
130 {
131     import std.digest;
132     import std.stdio;
133 
134     writeln("Testing PBKDF2 Extended with Custom Output Length:");
135 
136     ubyte[48] key = [ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
137                       0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
138                       0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
139 
140     ubyte[] vec1 = pbkdf2_ex("", key, HashAlgorithm.SHA2_384, 32, 25000);
141     ubyte[] vec2 = pbkdf2_ex("abc", key, HashAlgorithm.SHA2_384, 32, 25000);
142     ubyte[] vec3 = pbkdf2_ex("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", key, HashAlgorithm.SHA2_384, 32, 25000);
143 
144     writeln(toHexString!(LetterCase.lower)(vec1));
145     writeln(toHexString!(LetterCase.lower)(vec2));
146     writeln(toHexString!(LetterCase.lower)(vec3));
147 
148     assert(toHexString!(LetterCase.lower)(vec1) == "b0ddf56b90903d638ec8d07a4205ba2bcfa944955d553e1ef3f91cba84e8e3bd");
149     assert(toHexString!(LetterCase.lower)(vec2) == "b0a5e09a38bee3eb2b84d477d5259ef7bebf0e48d9512178f7e26cc330278ff4");
150     assert(toHexString!(LetterCase.lower)(vec3) == "d1aacafea3a9fdf3ee6236b1b45527974ea01539b4a7cc493bba56e15e14d520");
151 }
152 
153 @safe public KdfResult hkdf(const ubyte[] key, ulong outputLen) {
154     KdfResult result;
155     result.salt = random(getHashLength(HashAlgorithm.SHA2_384));
156     result.key = hkdf_ex(key, result.salt, string.init, outputLen, HashAlgorithm.SHA2_384);
157     return result;
158 }
159 
160 @trusted public ubyte[] hkdf_ex(const ubyte[] key, const ubyte[] salt, string info, ulong outputLen, HashAlgorithm func) {
161     if (key.length == 0) {
162         throw new CryptographicException("HKDF key cannot be an empty array.");
163     }
164 
165     EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, null);
166     scope(exit) {
167         if(pctx !is null) {
168             EVP_PKEY_CTX_free(pctx);
169         }
170     }
171 
172     if (EVP_PKEY_derive_init(pctx) <= 0) {
173         throw new CryptographicException("Unable to create HKDF function.");
174     }
175     
176     if (EVP_PKEY_CTX_set_hkdf_md(pctx, getOpenSSLHashAlgorithm(func)) <= 0) {
177         throw new CryptographicException("Unable to create HKDF hash function.");
178     }
179 
180     if (salt.length != 0 && EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt) <= 0) {
181         throw new CryptographicException("Unable to set HKDF salt.");
182     }
183 
184     if (info.length != 0 && EVP_PKEY_CTX_add1_hkdf_info(pctx, info) <= 0) {
185         throw new CryptographicException("Unable to set HKDF info.");
186     }
187 
188     if (EVP_PKEY_CTX_set1_hkdf_key(pctx, key) <= 0) {
189         throw new CryptographicException("Unable to set HKDF key.");
190     }
191 
192     ubyte[] keyMaterial = new ubyte[outputLen];
193     if (EVP_PKEY_derive(pctx, keyMaterial.ptr, &outputLen) <= 0) {
194         throw new CryptographicException("Unable to generate the requested key material.");
195     }
196 
197     return keyMaterial;
198 }
199 
200 unittest
201 {
202     import std.digest;
203     import std.stdio;
204 
205     writeln("Testing HKDF Extended with Defaults:");
206 
207     ubyte[48] salt = [ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
208                        0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
209                        0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
210 
211     ubyte[] vec2 = hkdf_ex(cast(ubyte[])"abc", salt, "", 64, HashAlgorithm.SHA2_224);
212     ubyte[] vec3 = hkdf_ex(cast(ubyte[])"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", salt, "test", 64, HashAlgorithm.SHA2_224);
213 
214     writeln(toHexString!(LetterCase.lower)(vec2));
215     writeln(toHexString!(LetterCase.lower)(vec3));
216 
217     assert(toHexString!(LetterCase.lower)(vec2) == "0fcc4d227bb180f4a2631da9bf158203ced36a73752d1f6fb05be764cbd13460556e6ddd69b0d3b2cdf08457a18253811e38d8059177e5dc22b5b52a6b1cb30a");
218     assert(toHexString!(LetterCase.lower)(vec3) == "d05e4ba15e07095b8b6dc3abbdde3f790fb4c1d6146e93e12312fbf54b5a1aff4c9c9108046fc390f2bef5fbcbf44d57ac05732525ccbf0a856821fe178f47c2");
219 }
220 
221 @safe public KdfResult scrypt(string password) {
222     KdfResult result;
223     result.salt = random(32);
224     result.key = scrypt_ex(password, result.salt, 1_048_576, 8, 1, 1_074_790_400, 64);
225     return result;
226 }
227 
228 @safe public KdfResult scrypt(const ubyte[] password) {
229     KdfResult result;
230     result.salt = random(32);
231     result.key = scrypt_ex(password, result.salt, 1_048_576, 8, 1, 1_074_790_400, 64);
232     return result;
233 }
234 
235 @trusted public ubyte[] scrypt_ex(string password, const ubyte[] salt, ulong n, ulong r, ulong p, ulong maxMemory, ulong length) {
236     import std.string;
237     return scrypt_ex(cast(ubyte[])password.representation, salt, n, r, p, maxMemory, length);
238 }
239 
240 @trusted public ubyte[] scrypt_ex(const ubyte[] password, const ubyte[] salt, ulong n, ulong r, ulong p, ulong maxMemory, ulong length) {
241     ubyte[] hash = new ubyte[length];
242 
243     if (EVP_PBE_scrypt((cast(char[])password).ptr, password.length, salt.ptr, salt.length, n, r, p, maxMemory, hash.ptr, length) <= 0) {
244         throw new CryptographicException("Unable to calculate SCrypt hash.");
245     }
246 
247     return hash;
248 }
249 
250 unittest
251 {
252     import std.digest;
253     import std.stdio;
254 
255     writeln("Testing SCrypt Extended with Defaults:");
256 
257     ubyte[48] salt = [ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
258                        0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF,
259                        0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF ];
260 
261     ubyte[] vec2 = scrypt_ex("abc", salt, 1_048_576, 8, 1, 1_074_790_400, 64);
262     ubyte[] vec3 = scrypt_ex("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", salt, 1_048_576, 8, 1, 1_074_790_400, 64);
263 
264     writeln(toHexString!(LetterCase.lower)(vec2));
265     writeln(toHexString!(LetterCase.lower)(vec3));
266 
267     assert(toHexString!(LetterCase.lower)(vec2) == "134fca5087e04c2a79e0ea2c793660f19d466db74a069e1f2e4da2b177d51402501bd39ffc592b9419ec0280cc17dca7af8df54f836179d69a4b9e9f6b9467fd");
268     assert(toHexString!(LetterCase.lower)(vec3) == "45397ec370eb31f3155ad162d83ec165ff8e363bc4e03c1c61c5a31ad17d0dac51d9e8911f32e9b588adf284a9de24561483dbaf0ea519b6a29ecae77eab5b90");
269 }