nacl/scrypt/
scrypt.rs

1// Copyright(c) 2018, 2021 3NSoft Inc.
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU Lesser General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11// GNU Lesser General Public License for more details.
12//
13// You should have received a copy of the GNU Lesser General Public License
14// along with this program.  If not, see <http://www.gnu.org/licenses/>.
15
16#![allow(non_snake_case)]
17
18use crate::util::ops::{ incr, add2 };
19
20use super::sha256::pbkdf2_sha256;
21use crate::util::{ Error, make_conf_error };
22
23/// Analog of le32dec in lib/util/sysendian.h
24#[inline]
25fn le32dec(p: &[u8]) -> u32 {
26	(p[0] as u32) + ((p[1] as u32) << 8) +
27	((p[2] as u32) << 16) + ((p[3] as u32) << 24)
28}
29
30/// Analog of le32enc in lib/util/sysendian.h
31#[inline]
32fn le32enc(p: &mut [u8], x: u32) {
33	p[0] = (x & 0xff) as u8;
34	p[1] = ((x >> 8) & 0xff) as u8;
35	p[2] = ((x >> 16) & 0xff) as u8;
36	p[3] = ((x >> 24) & 0xff) as u8;
37}
38
39/// Analog of macro used in salsa20_8 in lib/crypto/crypto_scrypt-ref.c
40macro_rules! R {
41	($a: expr, $b: expr) => ((($a) << ($b)) | (($a) >> (32 - ($b))))
42}
43
44/// salsa20_8(B):
45/// Apply the salsa20/8 core to the provided block.
46/// Analog of salsa20_8 in lib/crypto/crypto_scrypt-ref.c
47fn salsa20_8(B: &mut [u8]) {
48
49	/* Convert little-endian values in. */
50	let mut B32: [u32; 16] = [0; 16];
51	for i in 0..16 {
52		B32[i] = le32dec(&B[i*4..4+i*4]);
53	}
54
55	/* Compute x = doubleround^4(B32). */
56	let mut x: [u32; 16] = [0; 16];
57	for i in 0..16 {
58		x[i] = B32[i];
59	}
60	for _ in 0..4 {
61		/* Operate on columns. */
62		x[ 4] ^= R!(add2!(x[ 0],x[12]), 7);  x[ 8] ^= R!(add2!(x[ 4],x[ 0]), 9);
63		x[12] ^= R!(add2!(x[ 8],x[ 4]),13);  x[ 0] ^= R!(add2!(x[12],x[ 8]),18);
64
65		x[ 9] ^= R!(add2!(x[ 5],x[ 1]), 7);  x[13] ^= R!(add2!(x[ 9],x[ 5]), 9);
66		x[ 1] ^= R!(add2!(x[13],x[ 9]),13);  x[ 5] ^= R!(add2!(x[ 1],x[13]),18);
67
68		x[14] ^= R!(add2!(x[10],x[ 6]), 7);  x[ 2] ^= R!(add2!(x[14],x[10]), 9);
69		x[ 6] ^= R!(add2!(x[ 2],x[14]),13);  x[10] ^= R!(add2!(x[ 6],x[ 2]),18);
70
71		x[ 3] ^= R!(add2!(x[15],x[11]), 7);  x[ 7] ^= R!(add2!(x[ 3],x[15]), 9);
72		x[11] ^= R!(add2!(x[ 7],x[ 3]),13);  x[15] ^= R!(add2!(x[11],x[ 7]),18);
73
74		/* Operate on rows. */
75		x[ 1] ^= R!(add2!(x[ 0],x[ 3]), 7);  x[ 2] ^= R!(add2!(x[ 1],x[ 0]), 9);
76		x[ 3] ^= R!(add2!(x[ 2],x[ 1]),13);  x[ 0] ^= R!(add2!(x[ 3],x[ 2]),18);
77
78		x[ 6] ^= R!(add2!(x[ 5],x[ 4]), 7);  x[ 7] ^= R!(add2!(x[ 6],x[ 5]), 9);
79		x[ 4] ^= R!(add2!(x[ 7],x[ 6]),13);  x[ 5] ^= R!(add2!(x[ 4],x[ 7]),18);
80
81		x[11] ^= R!(add2!(x[10],x[ 9]), 7);  x[ 8] ^= R!(add2!(x[11],x[10]), 9);
82		x[ 9] ^= R!(add2!(x[ 8],x[11]),13);  x[10] ^= R!(add2!(x[ 9],x[ 8]),18);
83
84		x[12] ^= R!(add2!(x[15],x[14]), 7);  x[13] ^= R!(add2!(x[12],x[15]), 9);
85		x[14] ^= R!(add2!(x[13],x[12]),13);  x[15] ^= R!(add2!(x[14],x[13]),18);
86	}
87
88	/* Compute B32 = B32 + x. */
89	for i in 0..16 {
90		incr!( B32[i], x[i] );
91	}
92
93	/* Convert little-endian values out. */
94	for i in 0..16 {
95		le32enc(&mut B[4*i..4+4*i], B32[i]);
96	}
97}
98
99/// Analog of blkcpy in lib/crypto/crypto_scrypt-ref.c
100fn blkcpy(dest: &mut [u8], src: &[u8], len: usize) {
101	for i in 0..len {
102		dest[i] = src[i];
103	}
104}
105
106/// Analog of blkxor in lib/crypto/crypto_scrypt-ref.c
107fn blkxor(dest: &mut [u8], src: &[u8], len: usize) {
108	for i in 0..len {
109		dest[i] ^= src[i];
110	}
111}
112
113/// blockmix_salsa8(B, Y, r):
114/// Compute B = BlockMix_{salsa20/8, r}(B).  The input B must be 128r bytes in
115/// length; the temporary space Y must also be the same size.
116/// Analog of blockmix_salsa8 in lib/crypto/crypto_scrypt-ref.c
117fn blockmix_salsa8(B: &mut [u8], Y: &mut [u8], r: usize) {
118	let mut X: [u8; 64] = [0; 64];
119
120	/* 1: X <-- B_{2r - 1} */
121	blkcpy(&mut X, &B[(2 * r - 1) * 64 ..], 64);
122
123	/* 2: for i = 0 to 2r - 1 do */
124	for i in 0..2*r {
125		/* 3: X <-- H(X \xor B_i) */
126		blkxor(&mut X, &B[i * 64 ..], 64);
127		salsa20_8(&mut X);
128
129		/* 4: Y_i <-- X */
130		blkcpy(&mut Y[i * 64 ..], &X, 64);
131	}
132
133	/* 6: B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */
134	for i in 0..r {
135		blkcpy(&mut B[i * 64 ..], &Y[(i * 2) * 64 ..], 64);
136	}
137	for i in 0..r {
138		blkcpy(&mut B[(i + r) * 64 ..], &Y[(i * 2 + 1) * 64 ..], 64);
139	}
140}
141
142/// Analog of le64dec in lib/util/sysendian.h
143#[inline]
144fn le64dec(p: &[u8]) -> u64 {
145	(p[0] as u64) + ((p[1] as u64) << 8) +
146	((p[2] as u64) << 16) + ((p[3] as u64) << 24) +
147	((p[4] as u64) << 32) + ((p[5] as u64) << 40) +
148	((p[6] as u64) << 48) + ((p[7] as u64) << 56)
149}
150
151/// integerify(B, r):
152/// Return the result of parsing B_{2r-1} as a little-endian integer.
153/// Analog of integerify in lib/crypto/crypto_scrypt-ref.c
154fn integerify(B: &[u8], r: usize) -> u64 {
155	let X = &B[(2 * r - 1) * 64 ..];
156	le64dec(X)
157}
158
159struct ProgressIndicator<'a> {
160	completed: u32,
161	delta_percent: u32,
162	delta_n: u32,
163	progress_cb: &'a dyn Fn(u32) -> (),
164}
165
166impl ProgressIndicator<'_> {
167
168	fn new<'a>(
169		N: u32, p: u32, start_percent: u32, progress_cb: &'a dyn Fn(u32) -> ()
170	) -> ProgressIndicator {
171		(progress_cb)(start_percent);
172		let total_n = 2*N*p;
173		let total_percent = 100 - start_percent;
174		let (delta_n, delta_percent) = if total_n < total_percent {
175			(1, total_percent/total_n)
176		} else {
177			(total_n/total_percent, 1)
178		};
179		ProgressIndicator {
180			completed: start_percent,
181			delta_n: delta_n,
182			delta_percent: delta_percent,
183			progress_cb: progress_cb,
184		}
185	}
186
187	fn addDelta(&mut self) -> () {
188		if self.completed <= 100 - self.delta_percent {
189			self.completed += self.delta_percent;
190			(self.progress_cb)(self.completed);
191		}
192	}
193
194}
195
196/// smix(B, r, N, V, XY):
197/// Compute B = SMix_r(B, N).  The input B must be 128r bytes in length; the
198/// temporary storage V must be 128rN bytes in length; the temporary storage
199/// X and Y must be 128r each or total 256r bytes in length.  The value N must
200/// be a power of 2.
201/// Analog of smix in lib/crypto/crypto_scrypt-ref.c
202fn smix(
203	B: &mut [u8], r: usize, N: usize, V: &mut [u8], X: &mut [u8], Y: &mut [u8],
204	progress: &mut ProgressIndicator
205) {
206	let mut i_for_progress_report = progress.delta_n;
207
208	/* 1: X <-- B */
209	blkcpy(X, B, 128 * r);
210
211	/* 2: for i = 0 to N - 1 do */
212	for i in 0..N {
213		/* 3: V_i <-- X */
214		blkcpy(&mut V[i * (128 * r) ..], X, 128 * r);
215
216		/* 4: X <-- H(X) */
217		blockmix_salsa8(X, Y, r);
218
219		if i as u32 == i_for_progress_report {
220			progress.addDelta();
221			i_for_progress_report += progress.delta_n;
222		}
223	}
224
225	i_for_progress_report = progress.delta_n;
226
227	/* 6: for i = 0 to N - 1 do */
228	for i in 0..N {
229		/* 7: j <-- Integerify(X) mod N */
230		let j = (integerify(X, r) as usize) & (N - 1);
231
232		/* 8: X <-- H(X \xor V_j) */
233		blkxor(X, &V[j * (128 * r) ..], 128 * r);
234		blockmix_salsa8(X, Y, r);
235
236		if i as u32 == i_for_progress_report {
237			progress.addDelta();
238			i_for_progress_report += progress.delta_n;
239		}
240	}
241
242	/* 10: B' <-- X */
243	blkcpy(B, X, 128 * r);
244}
245
246fn allocate_byte_array(len: usize) -> Vec<u8> {
247	let mut v: Vec<u8> = Vec::with_capacity(len);
248	unsafe {
249		v.set_len(len);
250	}
251	v
252}
253
254/// crypto_scrypt(passwd, passwdlen, salt, saltlen, N, r, p, buf, buflen):
255/// Compute scrypt(passwd[0 .. passwdlen - 1], salt[0 .. saltlen - 1], N, r,
256/// p, buflen) and write the result into buf.  The parameters r, p, and buflen
257/// must satisfy r * p < 2^30 and buflen <= (2^32 - 1) * 32.  The parameter N
258/// must be a power of 2.
259/// Return 0 on success; or -1 on error.
260/// 
261/// This rust implementation notes:
262/// - we asks for `logN` to enforce N being a power of 2.
263/// - we use standard memory allocation. When run in operating system,
264///   allocation calls return ok even on infinite amount, pushing error/panic
265///   later into the process that populates all of this area with random bytes.
266/// 
267pub fn scrypt(
268	passwd: &[u8], salt: &[u8], logN: u8, r: usize, p: usize, dk_len: usize,
269	progress_cb: &dyn Fn(u32) -> ()
270) -> Result<Vec<u8>, Error> {
271	// uint8_t * B;
272	// uint8_t * V;
273	// uint8_t * XY;
274	// uint32_t i;
275
276	/* Sanity-check parameters. */
277	if r * p >= R_TIMES_P_LIMIT {
278		return Err(make_conf_error("r * p is too big".to_string()));
279	}
280	if dk_len > DK_LEN_LIMIT {
281		return Err(make_conf_error("dkLen is too big".to_string()));
282	}
283	if logN > 31 {
284		return Err(make_conf_error("logN is too big".to_string()));
285	}
286
287	let N = pow_of_2(logN as u32);
288
289	/* Allocate memory. */
290	let mut B = allocate_byte_array(128 * r * p);
291	let mut X = allocate_byte_array(128 * r);
292	let mut Y = allocate_byte_array(128 * r);
293	let mut V = allocate_byte_array(128 * r * N);
294
295	/* 1: (B_0 ... B_{p-1}) <-- PBKDF2(P, S, 1, p * MFLen) */
296	pbkdf2_sha256(passwd, salt, 1, &mut B);
297	let mut progress = ProgressIndicator::new(
298		N as u32, p as u32, 3, progress_cb);
299
300	/* 2: for i = 0 to p - 1 do */
301	for i in 0..p {
302		/* 3: B_i <-- MF(B_i, N) */
303		smix(&mut B[i * 128 * r ..], r, N, &mut V, &mut X, &mut Y, &mut progress);
304	}
305
306	/* 5: DK <-- PBKDF2(P, B, 1, dkLen) */
307	let mut buf = allocate_byte_array(dk_len);
308	pbkdf2_sha256(passwd, &B, 1, &mut buf);
309	progress_cb(100);
310
311	/* Success! */
312	Ok(buf)
313}
314
315#[inline]
316const fn pow_of_2(power: u32) -> usize {
317	2usize.pow(power)
318}
319
320const R_TIMES_P_LIMIT: usize = pow_of_2(30);
321
322const DK_LEN_LIMIT: usize = if usize::MAX > 0xffffffff {
323	(pow_of_2(32) - 1) * 32
324} else { usize::MAX };
325
326#[cfg(test)]
327mod tests {
328
329	use::std::cell::Cell;
330	use std::mem;
331	use super::{ allocate_byte_array, scrypt };
332	use crate::util::verify::compare;
333
334	#[test]
335	fn show_funky_allocation_in_os() {
336		assert_eq!(8, mem::size_of::<usize>());
337		let x = allocate_byte_array(128);
338		assert!(x.capacity() == 128);
339		assert_eq!(x.len(), 128);
340		let gb = 1024*1024*1024 as usize;
341		let mut big = allocate_byte_array(gb);
342		assert_eq!(big.capacity(), gb);
343		assert_eq!(big.len(), gb);
344		big[gb-234] = 89;
345		big[gb-1] = 70;
346	}
347
348	/// Testing scrypt with logN==4, r==1, p==1.
349	/// See scrypt rfc https://tools.ietf.org/html/rfc7914
350	/// 
351	#[test]
352	fn with_logn_4_r_1_p_1() {
353		let P = "".as_bytes();
354		let S = "".as_bytes();
355		let logN: u8 = 4;
356		let r: usize = 1;
357		let p: usize = 1;
358		let expectation = [
359			0x77, 0xd6, 0x57, 0x62, 0x38, 0x65, 0x7b, 0x20, 0x3b, 0x19,
360			0xca, 0x42, 0xc1, 0x8a, 0x04, 0x97, 0xf1, 0x6b, 0x48, 0x44,
361			0xe3, 0x07, 0x4a, 0xe8, 0xdf, 0xdf, 0xfa, 0x3f, 0xed, 0xe2,
362			0x14, 0x42, 0xfc, 0xd0, 0x06, 0x9d, 0xed, 0x09, 0x48, 0xf8,
363			0x32, 0x6a, 0x75, 0x3a, 0x0f, 0xc8, 0x1f, 0x17, 0xe8, 0xd3,
364			0xe0, 0xfb, 0x2e, 0x0d, 0x36, 0x28, 0xcf, 0x35, 0xe2, 0x0c,
365			0x38, 0xd1, 0x89, 0x06 ];
366		let hlen = expectation.len();
367		let progress = Cell::new(0 as u32);
368		let h = scrypt(P, S, logN, r, p, hlen, & |p| {
369			assert!(p >= progress.get());
370			progress.set(p);
371			print!(" {}%", p);
372		}).unwrap();
373		assert!(compare(&h, &expectation));
374		assert!(progress.get() > 0);
375	}
376
377	/// Testing scrypt with logN==10, r==8, p==16.
378	/// See scrypt rfc https://tools.ietf.org/html/rfc7914
379	/// 
380	#[test]
381	fn with_logn_10_r_8_p_16() {
382		let P = "password".as_bytes();
383		let S = "NaCl".as_bytes();
384		let logN: u8 = 10;
385		let r: usize = 8;
386		let p: usize = 16;
387		let expectation = [
388			0xfd, 0xba, 0xbe, 0x1c, 0x9d, 0x34, 0x72, 0x00, 0x78, 0x56,
389			0xe7, 0x19, 0x0d, 0x01, 0xe9, 0xfe, 0x7c, 0x6a, 0xd7, 0xcb,
390			0xc8, 0x23, 0x78, 0x30, 0xe7, 0x73, 0x76, 0x63, 0x4b, 0x37,
391			0x31, 0x62, 0x2e, 0xaf, 0x30, 0xd9, 0x2e, 0x22, 0xa3, 0x88,
392			0x6f, 0xf1, 0x09, 0x27, 0x9d, 0x98, 0x30, 0xda, 0xc7, 0x27,
393			0xaf, 0xb9, 0x4a, 0x83, 0xee, 0x6d, 0x83, 0x60, 0xcb, 0xdf,
394			0xa2, 0xcc, 0x06, 0x40 ];
395		let hlen = expectation.len();
396		let progress = Cell::new(0 as u32);
397		let h = scrypt(P, S, logN, r, p, hlen, & |p| {
398			assert!(p >= progress.get());
399			progress.set(p);
400			print!(" {}%", p);
401		}).unwrap();
402		assert!(compare(&h, &expectation));
403		assert!(progress.get() > 0);
404	}
405
406	/// Testing scrypt with logN==14, r==8, p==1.
407	/// See scrypt rfc https://tools.ietf.org/html/rfc7914
408	/// 
409	#[test]
410	fn with_logn_14_r_8_p_1() {
411		let P = "pleaseletmein".as_bytes();
412		let S = "SodiumChloride".as_bytes();
413		let logN: u8 = 14;
414		let r: usize = 8;
415		let p: usize = 1;
416		let expectation = [
417			0x70, 0x23, 0xbd, 0xcb, 0x3a, 0xfd, 0x73, 0x48, 0x46, 0x1c,
418			0x06, 0xcd, 0x81, 0xfd, 0x38, 0xeb, 0xfd, 0xa8, 0xfb, 0xba,
419			0x90, 0x4f, 0x8e, 0x3e, 0xa9, 0xb5, 0x43, 0xf6, 0x54, 0x5d,
420			0xa1, 0xf2, 0xd5, 0x43, 0x29, 0x55, 0x61, 0x3f, 0x0f, 0xcf,
421			0x62, 0xd4, 0x97, 0x05, 0x24, 0x2a, 0x9a, 0xf9, 0xe6, 0x1e,
422			0x85, 0xdc, 0x0d, 0x65, 0x1e, 0x40, 0xdf, 0xcf, 0x01, 0x7b,
423			0x45, 0x57, 0x58, 0x87 ];
424		let hlen = expectation.len();
425		let progress = Cell::new(0 as u32);
426		let h = scrypt(P, S, logN, r, p, hlen, & |p| {
427			assert!(p >= progress.get());
428			progress.set(p);
429			print!(" {}%", p);
430		}).unwrap();
431		assert!(compare(&h, &expectation));
432		assert!(progress.get() > 0);
433	}
434
435	/// Testing scrypt with logN==20, r==8, p==1.
436	/// See scrypt rfc https://tools.ietf.org/html/rfc7914
437	/// 
438	#[test]
439	fn with_logn_20_r_8_p_1() {
440		let P = "pleaseletmein".as_bytes();
441		let S = "SodiumChloride".as_bytes();
442		let logN: u8 = 20;
443		let r: usize = 8;
444		let p: usize = 1;
445		let expectation = [
446			0x21, 0x01, 0xcb, 0x9b, 0x6a, 0x51, 0x1a, 0xae, 0xad, 0xdb,
447			0xbe, 0x09, 0xcf, 0x70, 0xf8, 0x81, 0xec, 0x56, 0x8d, 0x57,
448			0x4a, 0x2f, 0xfd, 0x4d, 0xab, 0xe5, 0xee, 0x98, 0x20, 0xad,
449			0xaa, 0x47, 0x8e, 0x56, 0xfd, 0x8f, 0x4b, 0xa5, 0xd0, 0x9f,
450			0xfa, 0x1c, 0x6d, 0x92, 0x7c, 0x40, 0xf4, 0xc3, 0x37, 0x30,
451			0x40, 0x49, 0xe8, 0xa9, 0x52, 0xfb, 0xcb, 0xf4, 0x5c, 0x6f,
452			0xa7, 0x7a, 0x41, 0xa4 ];
453		let hlen = expectation.len();
454		let progress = Cell::new(0 as u32);
455		let h = scrypt(P, S, logN, r, p, hlen, & |p| {
456			assert!(p >= progress.get());
457			progress.set(p);
458			print!(" {}%", p);
459		}).unwrap();
460		assert!(compare(&h, &expectation));
461		assert!(progress.get() > 0);
462	}
463
464}