1 module dcrypt.crypto.pbe.scrypt;
2 
3 import std.range;
4 import std.parallelism;
5 
6 import dcrypt.crypto.engines.salsa;
7 import dcrypt.crypto.digests.sha2;
8 import dcrypt.crypto.pbe.pbkdf2;
9 import dcrypt.bitmanip;
10 
11 
12 /// generate a 256 bit key
13 unittest {
14 	ubyte[32] key;
15 	scrypt(key, cast(const(ubyte)[])"password", cast(const(ubyte)[])"salt", 123, 1, 1);
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 = new ubyte[64];
23 
24 	key.scrypt(cast(const(ubyte)[])"",cast(const(ubyte)[])"", 16, 1, 1);
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 	scrypt(key, cast(const(ubyte)[])"password",cast(const(ubyte)[])"NaCl", 1024, 8, 16);
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 	scrypt(key, cast(const(ubyte)[])"pleaseletmein",cast(const(ubyte)[])"SodiumChloride",
41 		16384, 8, 1);
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 	//	scrypt(key, cast(const(ubyte)[])"pleaseletmein",cast(const(ubyte)[])"SodiumChloride",
51 	//		1048576, 8, 1);
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 // TODO Validate arguments
64 ///
65 /// implementation of https://www.tarsnap.com/scrypt/scrypt.pdf
66 /// 		
67 /// Params:
68 /// output = Output buffer for derived key. Buffer length defines the key length. Lenght < 2^32.
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 /// 
75 public void scrypt(ubyte[] output, in ubyte[] pass, in ubyte[] salt, in uint N, in uint r, in uint p)
76 in {
77 	assert(p <= ((1L<<32)-1)*32/(r * 128), "parallelization parameter p too large");
78 	assert(output.length < 1L<<32, "dkLen must be smaller than 2^32");
79 }
80 body {
81 
82 	MFCrypt(output, pass, salt, N, r, p);
83 	
84 }
85 
86 private:
87 
88 // TODO Validate arguments
89 ///
90 /// implementation of https://www.tarsnap.com/scrypt/scrypt.pdf
91 /// 		
92 /// Params:
93 /// output = output buffer.
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 /// Returns: Derived key.
102 /// 
103 @safe
104 void MFCrypt(ubyte[] output, in ubyte[] pass, in ubyte[] salt, uint N, uint r, uint p)
105 in {
106 	assert(p <= ((1L<<32)-1)*32/(r * 128), "parallelization parameter p too large");
107 	assert(output.length < 1L<<32, "dkLen must be smaller than 2^32");
108 }
109 body {
110 	uint MFLenBytes = r * 128;
111 	ubyte[] bytes = new ubyte[p * MFLenBytes];
112 	SingleIterationPBKDF2(pass, salt, bytes);
113 	
114 	size_t BLen = bytes.length >>> 2;
115 	uint[] B = new uint[BLen];
116 	
117 	// wipe data on exit
118 	scope (exit) {
119 		bytes[] = 0;
120 		B[] = 0;
121 	}
122 	
123 	fromLittleEndian(bytes, B);
124 	
125 	uint MFLenWords = MFLenBytes >>> 2;
126 	
127 	
128 	if(p > 1) {
129 		// do parallel computations
130 		parallSMix(B, MFLenWords, N, r);
131 	} else {
132 		// don't use parallelism
133 		foreach(chunk; chunks(B, MFLenWords)) {
134 			SMix(chunk, N, r);
135 		}
136 	}
137 
138 	toLittleEndian(B, bytes);
139 	
140 	SingleIterationPBKDF2(pass, bytes, output);
141 }
142 
143 @trusted
144 void parallSMix(uint[] B, uint MFLenWords, uint N, uint r) {
145 	// do parallel computations
146 	foreach(chunk; parallel(chunks(B, MFLenWords))) {
147 		SMix(chunk, N, r);
148 	}
149 }
150 
151 void SingleIterationPBKDF2(in ubyte[] P, in ubyte[] S, ubyte[] output)
152 {
153 	pbkdf2!SHA256(output, P, S, 1);
154 }
155 
156 void SMix(uint[] B, in uint N, in uint r) pure nothrow
157 {
158 	uint BCount = r * 32;
159 
160 	uint[16] blockX1;
161 	uint[16] blockX2;
162 	uint[] blockY = new uint[BCount];
163 	
164 	uint[] X = new uint[BCount];
165 	uint[][] V = new uint[][N];
166 
167 	// wipe data on exit
168 	scope (exit) {
169 		foreach(ref v;V) {
170 			v[] = 0;
171 		}
172 		X[] = 0;
173 		blockX1[] = 0;
174 		blockX2[] = 0;
175 		blockY[] = 0;
176 	}
177 
178 	X[] = B[0..BCount];
179 
180 	for (uint i = 0; i < N; ++i)
181 	{
182 		V[i] = X.dup;
183 		BlockMix(X, blockX1, blockX2, blockY, r);
184 	}
185 	
186 	uint mask = N - 1;
187 	for (uint i = 0; i < N; ++i)
188 	{
189 		uint j = X[BCount - 16] & mask;
190 		X[] ^= V[j][];
191 		BlockMix(X, blockX1, blockX2, blockY, r);
192 	}
193 
194 	B[0..BCount] = X[];
195 	
196 }
197 
198 void BlockMix(uint[] B, uint[] X1, uint[] X2, uint[] Y, int r) pure nothrow @nogc
199 body {
200 
201 	X1[0..16] = B[$-16..$];
202 	
203 	size_t BOff = 0, YOff = 0, halfLen = B.length >>> 1;
204 	
205 	for (int i = 2 * r; i > 0; --i)
206 	{
207 		X2[] = B[BOff..$] ^ X1[];
208 
209 		Salsa20.block!8(X2, X1);
210 
211 		Y[YOff..YOff+16] = X1[0..16];
212 		
213 		YOff = halfLen + BOff - YOff;
214 		BOff += 16;
215 	}
216 
217 	B[0..Y.length] = Y[];
218 }