1 module dcrypt.crypto.generators.scrypt;
2 
3 import std.range;
4 import std.parallelism;
5 
6 import dcrypt.crypto.engines.salsa20;
7 import dcrypt.crypto.digests.sha2;
8 import dcrypt.crypto.generators.pkcs5s2;
9 import dcrypt.util.pack;
10 
11 
12 /// generate a 256 bit key
13 unittest {
14 	ubyte[] key = scrypt(cast(const(ubyte)[])"password",cast(const(ubyte)[])"salt", 123, 1, 1, 32);
15 	assert(key.length == 32);
16 }
17 
18 /// generate keys and compare them with test vectors from
19 /// https://www.tarsnap.com/scrypt/scrypt.pdf
20 unittest {
21 
22 	ubyte[] key;
23 
24 	key = scrypt(cast(const(ubyte)[])"",cast(const(ubyte)[])"", 16, 1, 1, 64);
25 
26 	assert(key == x"
27 77 d6 57 62 38 65 7b 20 3b 19 ca 42 c1 8a 04 97
28 f1 6b 48 44 e3 07 4a e8 df df fa 3f ed e2 14 42
29 fc d0 06 9d ed 09 48 f8 32 6a 75 3a 0f c8 1f 17
30 e8 d3 e0 fb 2e 0d 36 28 cf 35 e2 0c 38 d1 89 06");
31 
32 	key = scrypt(cast(const(ubyte)[])"password",cast(const(ubyte)[])"NaCl", 1024, 8, 16, 64);
33 
34 	assert(key == x"
35 fd ba be 1c 9d 34 72 00 78 56 e7 19 0d 01 e9 fe
36 7c 6a d7 cb c8 23 78 30 e7 73 76 63 4b 37 31 62
37 2e af 30 d9 2e 22 a3 88 6f f1 09 27 9d 98 30 da
38 c7 27 af b9 4a 83 ee 6d 83 60 cb df a2 cc 06 40");
39 
40 	key = scrypt(cast(const(ubyte)[])"pleaseletmein",cast(const(ubyte)[])"SodiumChloride",
41 		16384, 8, 1, 64);
42 	
43 	assert(key == x"
44 70 23 bd cb 3a fd 73 48 46 1c 06 cd 81 fd 38 eb
45 fd a8 fb ba 90 4f 8e 3e a9 b5 43 f6 54 5d a1 f2
46 d5 43 29 55 61 3f 0f cf 62 d4 97 05 24 2a 9a f9
47 e6 1e 85 dc 0d 65 1e 40 df cf 01 7b 45 57 58 87");
48 
49 	//// !! this consumes 1GB of ram
50 	//	key = scrypt(cast(const(ubyte)[])"pleaseletmein",cast(const(ubyte)[])"SodiumChloride",
51 	//		1048576, 8, 1, 64);
52 	//	
53 	//	assert(key == cast(const(ubyte)[])x"
54 	//21 01 cb 9b 6a 51 1a ae ad db be 09 cf 70 f8 81
55 	//ec 56 8d 57 4a 2f fd 4d ab e5 ee 98 20 ad aa 47
56 	//8e 56 fd 8f 4b a5 d0 9f fa 1c 6d 92 7c 40 f4 c3
57 	//37 30 40 49 e8 a9 52 fb cb f4 5c 6f a7 7a 41 a4");
58 
59 }
60 
61 @safe
62 {
63 
64 	// TODO Validate arguments
65 	///
66 	/// implementation of https://www.tarsnap.com/scrypt/scrypt.pdf
67 	/// 		
68 	/// Params:
69 	/// pass = password
70 	/// salt = cryptographic salt
71 	/// N = CPU/memory cost parameter
72 	/// r = block size parameter
73 	/// p = parallelization parameter. p <= (2^32-1)*hashLen/MFLen
74 	/// dkLen = length in octets of derived key. dkLen < 2^32
75 	/// 
76 	@safe
77 	public ubyte[] scrypt(in ubyte[] pass, in ubyte[] salt, uint N, uint r, uint p, uint dkLen)
78 	in {
79 		assert(p <= ((1L<<32)-1)*32/(r * 128), "parallelization parameter p too large");
80 		assert(dkLen < 1L<<32, "dkLen must be smaller than 2^32");
81 	}
82 	body {
83 
84 		return MFCrypt(pass, salt, N, r, p, dkLen);
85 		
86 	}
87 
88 private:
89 	// TODO Validate arguments
90 	///
91 	/// implementation of https://www.tarsnap.com/scrypt/scrypt.pdf
92 	/// 		
93 	/// Params:
94 	/// pass = password
95 	/// salt = cryptographic salt
96 	/// N = CPU/memory cost parameter
97 	/// r = block size parameter
98 	/// p = parallelization parameter. p <= (2^32-1)*hashLen/MFLen
99 	/// dkLen = length in octets of derived key. dkLen < 2^32
100 	/// 
101 	@safe
102 	ubyte[] MFCrypt(in ubyte[] pass, in ubyte[] salt, uint N, uint r, uint p, uint dkLen)
103 	in {
104 		assert(p <= ((1L<<32)-1)*32/(r * 128), "parallelization parameter p too large");
105 		assert(dkLen < 1L<<32, "dkLen must be smaller than 2^32");
106 	}
107 	body {
108 		uint MFLenBytes = r * 128;
109 		
110 		ubyte[] bytes = SingleIterationPBKDF2(pass, salt, p * MFLenBytes);
111 		
112 		size_t BLen = bytes.length >>> 2;
113 		uint[] B = new uint[BLen];
114 		
115 		// wipe data on exit
116 		scope (exit) {
117 			bytes[] = 0;
118 			B[] = 0;
119 		}
120 		
121 		fromLittleEndian(bytes, B);
122 		
123 		uint MFLenWords = MFLenBytes >>> 2;
124 		
125 		
126 		if(p > 1) {
127 			// do parallel computations
128 			parallSMix(B, MFLenWords, N, r);
129 		} else {
130 			// don't use parallelism
131 			foreach(chunk; chunks(B, MFLenWords)) {
132 				SMix(chunk, N, r);
133 			}
134 		}
135 		
136 		toLittleEndian(B, bytes);
137 		
138 		return SingleIterationPBKDF2(pass, bytes, dkLen);
139 		
140 	}
141 
142 	@trusted
143 	void parallSMix(uint[] B, uint MFLenWords, uint N, uint r) {
144 		// do parallel computations
145 		foreach(chunk; parallel(chunks(B, MFLenWords))) {
146 			SMix(chunk, N, r);
147 		}
148 	}
149 
150 	ubyte[] SingleIterationPBKDF2(in ubyte[] P, in ubyte[] S, uint dkLen)
151 	{
152 		PKCS5S2ParametersGenerator!SHA256 pGen = new PKCS5S2ParametersGenerator!SHA256;
153 		pGen.init(P, S, 1);
154 		KeyParameter key = pGen.generateDerivedMacParameters(dkLen * 8);
155 		return key.getKey();
156 	}
157 	
158 	void SMix(uint[] B, in uint N, in uint r) pure nothrow
159 	{
160 		uint BCount = r * 32;
161 
162 		uint[16] blockX1;
163 		uint[16] blockX2;
164 		uint[] blockY = new uint[BCount];
165 		
166 		uint[] X = new uint[BCount];
167 		uint[][] V = new uint[][N];
168 
169 		// wipe data on exit
170 		scope (exit) {
171 			foreach(ref v;V) {
172 				v[] = 0;
173 			}
174 			X[] = 0;
175 			blockX1[] = 0;
176 			blockX2[] = 0;
177 			blockY[] = 0;
178 		}
179 
180 		X[] = B[0..BCount];
181 
182 		for (uint i = 0; i < N; ++i)
183 		{
184 			V[i] = X.dup;
185 			BlockMix(X, blockX1, blockX2, blockY, r);
186 		}
187 		
188 		uint mask = N - 1;
189 		for (uint i = 0; i < N; ++i)
190 		{
191 			uint j = X[BCount - 16] & mask;
192 			X[] ^= V[j][];
193 			BlockMix(X, blockX1, blockX2, blockY, r);
194 		}
195 
196 		B[0..BCount] = X[];
197 		
198 	}
199 	
200 	void BlockMix(uint[] B, uint[] X1, uint[] X2, uint[] Y, int r) pure nothrow @nogc
201 	body {
202 
203 		X1[0..16] = B[$-16..$];
204 		
205 		size_t BOff = 0, YOff = 0, halfLen = B.length >>> 1;
206 		
207 		for (int i = 2 * r; i > 0; --i)
208 		{
209 			X2[] = B[BOff..$] ^ X1[];
210 
211 			Salsa20.salsaCore!8(X2, X1);
212 
213 			Y[YOff..YOff+16] = X1[0..16];
214 			
215 			YOff = halfLen + BOff - YOff;
216 			BOff += 16;
217 		}
218 
219 		B[0..Y.length] = Y[];
220 	}
221 }