dumb_crypto/
scrypt.rs

1//! # Scrypt
2//!
3//! Implementation of scrypt key derivation algorithm according to
4//! [RFC][rfc].
5//!
6//! [rfc]: https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-03
7//!
8
9use crate::pbkdf2::pbkdf2_sha256;
10use crate::salsa20::{salsa20, BLOCK_SIZE as SALSA_BLOCK_SIZE};
11
12const SALSA_ROUNDS: usize = 4;
13const PBKDF2_ROUNDS: usize = 1;
14const BLOCK_SIZE: usize = 64;
15
16use std::error::Error;
17use std::fmt;
18use std::fmt::Display;
19
20type Block = Vec<u8>;
21
22#[derive(Debug, PartialEq)]
23pub enum ScryptError {
24    RIsTooSmall,
25    NIsTooSmall,
26    NIsNotAPowerOfTwo,
27    PIsTooSmall,
28}
29
30impl Display for ScryptError {
31    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
32        write!(f, "ScryptError: {}", self.description())
33    }
34}
35
36impl Error for ScryptError {
37    fn description(&self) -> &str {
38        match self {
39            ScryptError::RIsTooSmall => "`r` must be larger than 1",
40            ScryptError::NIsTooSmall => "`n` must be larger than 1",
41            ScryptError::NIsNotAPowerOfTwo => "`n` must be a power of two",
42            ScryptError::PIsTooSmall => "`p` must be larger than 1",
43        }
44    }
45}
46
47///
48/// Main scrypt structure.
49///
50/// Usage:
51/// ```rust
52/// extern crate dumb_crypto;
53///
54/// use::dumb_crypto::scrypt::Scrypt;
55///
56/// let scrypt = Scrypt::new(1, 128, 1);
57///
58/// let mut out: [u8; 8] = [0; 8];
59///
60/// scrypt.derive(b"passphrase", b"salt", &mut out).unwrap();
61///
62/// assert_eq!(out.to_vec(), vec![
63///     79, 35, 225, 99, 145, 145, 172, 245,
64/// ]);
65/// ```
66///
67pub struct Scrypt {
68    r: usize,
69    n: usize,
70    p: usize,
71}
72
73fn block_xor(a: &[u8], b: &[u8]) -> Block {
74    a.iter().zip(b.iter()).map(|(a, b)| a ^ b).collect()
75}
76
77fn integerify(x: &[Block]) -> u64 {
78    let last = &x[x.len() - 1];
79    let tail = &last[(last.len() - SALSA_BLOCK_SIZE)..];
80
81    u64::from(tail[0])
82        | (u64::from(tail[1]) << 8)
83        | (u64::from(tail[2]) << 16)
84        | (u64::from(tail[3]) << 24)
85        | (u64::from(tail[4]) << 32)
86        | (u64::from(tail[5]) << 40)
87        | (u64::from(tail[6]) << 48)
88        | (u64::from(tail[7]) << 56)
89}
90
91impl Scrypt {
92    ///
93    /// Create new instance of Scrypt.
94    ///
95    /// Arguments:
96    /// - `r` Block size parameter, must be larger than 1
97    /// - `n` CPU/Memory cost parameter, must be larger than 1,
98    ///       a power of 2 and less than 2 ^ (16 * r)
99    /// - `p` Parallelization parameter, a positive integer
100    ///       less than or equal to (2^32-1) / (4 * r)
101    ///       where hLen is 32 and MFlen is 128 * r.
102    ///
103    pub fn new(r: usize, n: usize, p: usize) -> Self {
104        Self { r, n, p }
105    }
106
107    fn block_mix(self: &Scrypt, b: &[Block]) -> Vec<Block> {
108        //
109        // Algorithm scryptBlockMix
110        //
111        // Parameters:
112        //          r       Block size parameter.
113        //
114        // Input:
115        //          B[0], ..., B[2 * r - 1]
116        //                 Input vector of 2 * r 64-octet blocks.
117        //
118        // Output:
119        //          B'[0], ..., B'[2 * r - 1]
120        //                  Output vector of 2 * r 64-octet blocks.
121        //
122        // Steps:
123        //
124        //   1. X = B[2 * r - 1]
125        //
126        //   2. for i = 0 to 2 * r - 1 do
127        //        T = X xor B[i]
128        //        X = Salsa (T)
129        //        Y[i] = X
130        //      end for
131        //
132        //   3. B' = (Y[0], Y[2], ..., Y[2 * r - 2],
133        //            Y[1], Y[3], ..., Y[2 * r - 1])
134        //
135
136        // Step 1
137        let mut x = b[2 * self.r - 1].clone();
138
139        // Step 2
140        let mut y: Vec<Block> = Vec::with_capacity(2 * self.r);
141
142        for b_elem in b.iter() {
143            let t = block_xor(&x, b_elem);
144            salsa20(&t, SALSA_ROUNDS, &mut x);
145            y.push(x.clone());
146        }
147
148        // Step 3
149        let mut bs_head: Vec<Block> = Vec::with_capacity(2 * self.r);
150        let mut bs_tail: Vec<Block> = Vec::with_capacity(self.r);
151        for (i, y_elem) in y.into_iter().enumerate() {
152            if i % 2 == 0 {
153                bs_head.push(y_elem);
154            } else {
155                bs_tail.push(y_elem);
156            }
157        }
158        bs_head.append(&mut bs_tail);
159        bs_head
160    }
161
162    fn ro_mix(self: &Scrypt, b: Vec<Block>) -> Vec<Block> {
163        //
164        // Algorithm scryptROMix
165        //
166        //   Input:
167        //            r       Block size parameter.
168        //            B       Input octet vector of length 128 * r octets.
169        //            N       CPU/Memory cost parameter, must be larger than 1,
170        //                    a power of 2 and less than 2^(128 * r / 8).
171        //
172        //   Output:
173        //            B'      Output octet vector of length 128 * r octets.
174        //
175        //   Steps:
176        //
177        //     1. X = B
178        //
179        //     2. for i = 0 to N - 1 do
180        //          V[i] = X
181        //          X = scryptBlockMix (X)
182        //        end for
183        //
184        //     3. for i = 0 to N - 1 do
185        //          j = Integerify (X) mod N
186        //                 where Integerify (B[0] ... B[2 * r - 1]) is defined
187        //                 as the result of interpreting B[2 * r - 1] as a
188        //                 little-endian integer.
189        //          T = X xor V[j]
190        //          X = scryptBlockMix (T)
191        //        end for
192        //
193        //     4. B' = X
194        //
195
196        // Step 1
197        let mut x = b;
198
199        // Step 2
200        let mut v: Vec<Vec<Block>> = Vec::with_capacity(self.n);
201        for _i in 0..self.n {
202            let t = self.block_mix(&x);
203            v.push(x);
204            x = t;
205        }
206
207        // Step 3
208        for _i in 0..self.n {
209            let j = (integerify(&x) as usize) % self.n;
210            let t: Vec<Block> = x
211                .iter()
212                .zip(v[j].iter())
213                .map(|(x_block, v_block)| block_xor(x_block, v_block))
214                .collect();
215            x = self.block_mix(&t);
216        }
217
218        x
219    }
220
221    ///
222    /// Derive secret string using `passphrase` and `salt`.
223    ///
224    pub fn derive(
225        self: &Scrypt,
226        passphrase: &[u8],
227        salt: &[u8],
228        out: &mut [u8],
229    ) -> Result<(), ScryptError> {
230        if self.r < 1 {
231            return Err(ScryptError::RIsTooSmall);
232        }
233
234        if self.n < 1 {
235            return Err(ScryptError::NIsTooSmall);
236        }
237
238        if ((self.n - 1) & self.n) != 0 {
239            return Err(ScryptError::NIsNotAPowerOfTwo);
240        }
241
242        if self.p < 1 {
243            return Err(ScryptError::PIsTooSmall);
244        }
245
246        //
247        //   Algorithm scrypt
248        //
249        //   Input:
250        //            P       Passphrase, an octet string.
251        //            S       Salt, an octet string.
252        //            N       CPU/Memory cost parameter, must be larger than 1,
253        //                    a power of 2 and less than 2^(128 * r / 8).
254        //            r       Block size parameter.
255        //            p       Parallelization parameter, a positive integer
256        //                    less than or equal to ((2^32-1) * hLen) / MFLen
257        //                    where hLen is 32 and MFlen is 128 * r.
258        //            dkLen   Intended output length in octets of the derived
259        //                    key; a positive integer less than or equal to
260        //                    (2^32 - 1) * hLen where hLen is 32.
261        //
262        //   Output:
263        //            DK      Derived key, of length dkLen octets.
264        //
265        //   Steps:
266        //
267        //     1. B[0] || B[1] || ... || B[p - 1] =
268        //          PBKDF2-HMAC-SHA256 (P, S, 1, p * 128 * r)
269        //
270        //     2. for i = 0 to p - 1 do
271        //          B[i] = scryptROMix (r, B[i], N)
272        //        end for
273        //
274        //     3. DK = PBKDF2-HMAC-SHA256 (P, B[0] || B[1] || ... || B[p - 1],
275        //                                 1, dkLen)
276        //
277
278        // Step 1
279        let mut raw_b: Vec<u8> = vec![0; self.p * 2 * BLOCK_SIZE * self.r];
280        pbkdf2_sha256(passphrase, salt, PBKDF2_ROUNDS, &mut raw_b);
281
282        let mut b: Vec<Vec<Block>> = raw_b
283            .chunks_exact(2 * BLOCK_SIZE * self.r)
284            .map(|chunk| {
285                chunk
286                    .chunks_exact(BLOCK_SIZE)
287                    .map(|sub_chunk| sub_chunk.to_vec())
288                    .collect()
289            })
290            .collect();
291
292        // Step 2
293        b = b.into_iter().map(|elem| self.ro_mix(elem)).collect();
294
295        // Step 3
296        let b_salt: Vec<u8> = b.into_iter().flatten().flatten().collect();
297        pbkdf2_sha256(passphrase, &b_salt, PBKDF2_ROUNDS, out);
298
299        Ok(())
300    }
301}
302
303#[cfg(test)]
304mod tests {
305    use super::*;
306
307    // https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-03#page-8
308
309    fn check_mix(r: usize, input: &[Block], expected: &[Block]) {
310        let s = Scrypt::new(r, 1, 1);
311        assert_eq!(s.block_mix(input), expected);
312    }
313
314    #[test]
315    fn it_should_compute_block_mix_for_vec0() {
316        check_mix(
317            1,
318            &[
319                vec![
320                    0xf7, 0xce, 0x0b, 0x65, 0x3d, 0x2d, 0x72, 0xa4, 0x10, 0x8c, 0xf5, 0xab, 0xe9,
321                    0x12, 0xff, 0xdd, 0x77, 0x76, 0x16, 0xdb, 0xbb, 0x27, 0xa7, 0x0e, 0x82, 0x04,
322                    0xf3, 0xae, 0x2d, 0x0f, 0x6f, 0xad, 0x89, 0xf6, 0x8f, 0x48, 0x11, 0xd1, 0xe8,
323                    0x7b, 0xcc, 0x3b, 0xd7, 0x40, 0x0a, 0x9f, 0xfd, 0x29, 0x09, 0x4f, 0x01, 0x84,
324                    0x63, 0x95, 0x74, 0xf3, 0x9a, 0xe5, 0xa1, 0x31, 0x52, 0x17, 0xbc, 0xd7,
325                ],
326                vec![
327                    0x89, 0x49, 0x91, 0x44, 0x72, 0x13, 0xbb, 0x22, 0x6c, 0x25, 0xb5, 0x4d, 0xa8,
328                    0x63, 0x70, 0xfb, 0xcd, 0x98, 0x43, 0x80, 0x37, 0x46, 0x66, 0xbb, 0x8f, 0xfc,
329                    0xb5, 0xbf, 0x40, 0xc2, 0x54, 0xb0, 0x67, 0xd2, 0x7c, 0x51, 0xce, 0x4a, 0xd5,
330                    0xfe, 0xd8, 0x29, 0xc9, 0x0b, 0x50, 0x5a, 0x57, 0x1b, 0x7f, 0x4d, 0x1c, 0xad,
331                    0x6a, 0x52, 0x3c, 0xda, 0x77, 0x0e, 0x67, 0xbc, 0xea, 0xaf, 0x7e, 0x89,
332                ],
333            ],
334            &[
335                vec![
336                    0xa4, 0x1f, 0x85, 0x9c, 0x66, 0x08, 0xcc, 0x99, 0x3b, 0x81, 0xca, 0xcb, 0x02,
337                    0x0c, 0xef, 0x05, 0x04, 0x4b, 0x21, 0x81, 0xa2, 0xfd, 0x33, 0x7d, 0xfd, 0x7b,
338                    0x1c, 0x63, 0x96, 0x68, 0x2f, 0x29, 0xb4, 0x39, 0x31, 0x68, 0xe3, 0xc9, 0xe6,
339                    0xbc, 0xfe, 0x6b, 0xc5, 0xb7, 0xa0, 0x6d, 0x96, 0xba, 0xe4, 0x24, 0xcc, 0x10,
340                    0x2c, 0x91, 0x74, 0x5c, 0x24, 0xad, 0x67, 0x3d, 0xc7, 0x61, 0x8f, 0x81,
341                ],
342                vec![
343                    0x20, 0xed, 0xc9, 0x75, 0x32, 0x38, 0x81, 0xa8, 0x05, 0x40, 0xf6, 0x4c, 0x16,
344                    0x2d, 0xcd, 0x3c, 0x21, 0x07, 0x7c, 0xfe, 0x5f, 0x8d, 0x5f, 0xe2, 0xb1, 0xa4,
345                    0x16, 0x8f, 0x95, 0x36, 0x78, 0xb7, 0x7d, 0x3b, 0x3d, 0x80, 0x3b, 0x60, 0xe4,
346                    0xab, 0x92, 0x09, 0x96, 0xe5, 0x9b, 0x4d, 0x53, 0xb6, 0x5d, 0x2a, 0x22, 0x58,
347                    0x77, 0xd5, 0xed, 0xf5, 0x84, 0x2c, 0xb9, 0xf1, 0x4e, 0xef, 0xe4, 0x25,
348                ],
349            ],
350        );
351    }
352
353    fn check_ro_mix(r: usize, n: usize, input: &[Block], expected: &[Block]) {
354        let s = Scrypt::new(r, n, 1);
355        assert_eq!(s.ro_mix(input.to_vec()), expected);
356    }
357
358    #[test]
359    fn it_should_compute_ro_mix_for_vec0() {
360        check_ro_mix(
361            1,
362            16,
363            &[
364                vec![
365                    0xf7, 0xce, 0x0b, 0x65, 0x3d, 0x2d, 0x72, 0xa4, 0x10, 0x8c, 0xf5, 0xab, 0xe9,
366                    0x12, 0xff, 0xdd, 0x77, 0x76, 0x16, 0xdb, 0xbb, 0x27, 0xa7, 0x0e, 0x82, 0x04,
367                    0xf3, 0xae, 0x2d, 0x0f, 0x6f, 0xad, 0x89, 0xf6, 0x8f, 0x48, 0x11, 0xd1, 0xe8,
368                    0x7b, 0xcc, 0x3b, 0xd7, 0x40, 0x0a, 0x9f, 0xfd, 0x29, 0x09, 0x4f, 0x01, 0x84,
369                    0x63, 0x95, 0x74, 0xf3, 0x9a, 0xe5, 0xa1, 0x31, 0x52, 0x17, 0xbc, 0xd7,
370                ],
371                vec![
372                    0x89, 0x49, 0x91, 0x44, 0x72, 0x13, 0xbb, 0x22, 0x6c, 0x25, 0xb5, 0x4d, 0xa8,
373                    0x63, 0x70, 0xfb, 0xcd, 0x98, 0x43, 0x80, 0x37, 0x46, 0x66, 0xbb, 0x8f, 0xfc,
374                    0xb5, 0xbf, 0x40, 0xc2, 0x54, 0xb0, 0x67, 0xd2, 0x7c, 0x51, 0xce, 0x4a, 0xd5,
375                    0xfe, 0xd8, 0x29, 0xc9, 0x0b, 0x50, 0x5a, 0x57, 0x1b, 0x7f, 0x4d, 0x1c, 0xad,
376                    0x6a, 0x52, 0x3c, 0xda, 0x77, 0x0e, 0x67, 0xbc, 0xea, 0xaf, 0x7e, 0x89,
377                ],
378            ],
379            &[
380                vec![
381                    0x79, 0xcc, 0xc1, 0x93, 0x62, 0x9d, 0xeb, 0xca, 0x04, 0x7f, 0x0b, 0x70, 0x60,
382                    0x4b, 0xf6, 0xb6, 0x2c, 0xe3, 0xdd, 0x4a, 0x96, 0x26, 0xe3, 0x55, 0xfa, 0xfc,
383                    0x61, 0x98, 0xe6, 0xea, 0x2b, 0x46, 0xd5, 0x84, 0x13, 0x67, 0x3b, 0x99, 0xb0,
384                    0x29, 0xd6, 0x65, 0xc3, 0x57, 0x60, 0x1f, 0xb4, 0x26, 0xa0, 0xb2, 0xf4, 0xbb,
385                    0xa2, 0x00, 0xee, 0x9f, 0x0a, 0x43, 0xd1, 0x9b, 0x57, 0x1a, 0x9c, 0x71,
386                ],
387                vec![
388                    0xef, 0x11, 0x42, 0xe6, 0x5d, 0x5a, 0x26, 0x6f, 0xdd, 0xca, 0x83, 0x2c, 0xe5,
389                    0x9f, 0xaa, 0x7c, 0xac, 0x0b, 0x9c, 0xf1, 0xbe, 0x2b, 0xff, 0xca, 0x30, 0x0d,
390                    0x01, 0xee, 0x38, 0x76, 0x19, 0xc4, 0xae, 0x12, 0xfd, 0x44, 0x38, 0xf2, 0x03,
391                    0xa0, 0xe4, 0xe1, 0xc4, 0x7e, 0xc3, 0x14, 0x86, 0x1f, 0x4e, 0x90, 0x87, 0xcb,
392                    0x33, 0x39, 0x6a, 0x68, 0x73, 0xe8, 0xf9, 0xd2, 0x53, 0x9a, 0x4b, 0x8e,
393                ],
394            ],
395        );
396    }
397
398    // https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-03#page-10
399
400    #[test]
401    fn it_should_compute_scrypt_for_vec0() {
402        let s = Scrypt::new(1, 16, 1);
403
404        let mut out: [u8; 64] = [0; 64];
405        s.derive(b"", b"", &mut out)
406            .expect("derivation to not fail");
407        assert_eq!(
408            out.to_vec(),
409            vec![
410                0x77, 0xd6, 0x57, 0x62, 0x38, 0x65, 0x7b, 0x20, 0x3b, 0x19, 0xca, 0x42, 0xc1, 0x8a,
411                0x04, 0x97, 0xf1, 0x6b, 0x48, 0x44, 0xe3, 0x07, 0x4a, 0xe8, 0xdf, 0xdf, 0xfa, 0x3f,
412                0xed, 0xe2, 0x14, 0x42, 0xfc, 0xd0, 0x06, 0x9d, 0xed, 0x09, 0x48, 0xf8, 0x32, 0x6a,
413                0x75, 0x3a, 0x0f, 0xc8, 0x1f, 0x17, 0xe8, 0xd3, 0xe0, 0xfb, 0x2e, 0x0d, 0x36, 0x28,
414                0xcf, 0x35, 0xe2, 0x0c, 0x38, 0xd1, 0x89, 0x06
415            ]
416        );
417    }
418
419    #[test]
420    fn it_should_compute_scrypt_for_vec1() {
421        let s = Scrypt::new(8, 1024, 16);
422
423        let mut out: [u8; 64] = [0; 64];
424        s.derive(b"password", b"NaCl", &mut out)
425            .expect("derivation to not fail");
426        assert_eq!(
427            out.to_vec(),
428            vec![
429                0xfd, 0xba, 0xbe, 0x1c, 0x9d, 0x34, 0x72, 0x00, 0x78, 0x56, 0xe7, 0x19, 0x0d, 0x01,
430                0xe9, 0xfe, 0x7c, 0x6a, 0xd7, 0xcb, 0xc8, 0x23, 0x78, 0x30, 0xe7, 0x73, 0x76, 0x63,
431                0x4b, 0x37, 0x31, 0x62, 0x2e, 0xaf, 0x30, 0xd9, 0x2e, 0x22, 0xa3, 0x88, 0x6f, 0xf1,
432                0x09, 0x27, 0x9d, 0x98, 0x30, 0xda, 0xc7, 0x27, 0xaf, 0xb9, 0x4a, 0x83, 0xee, 0x6d,
433                0x83, 0x60, 0xcb, 0xdf, 0xa2, 0xcc, 0x06, 0x40
434            ]
435        );
436    }
437
438    #[test]
439    fn it_should_compute_scrypt_for_vec2() {
440        let s = Scrypt::new(8, 16384, 1);
441
442        let mut out: [u8; 64] = [0; 64];
443        s.derive(b"pleaseletmein", b"SodiumChloride", &mut out)
444            .expect("derivation to not fail");
445        assert_eq!(
446            out.to_vec(),
447            vec![
448                0x70, 0x23, 0xbd, 0xcb, 0x3a, 0xfd, 0x73, 0x48, 0x46, 0x1c, 0x06, 0xcd, 0x81, 0xfd,
449                0x38, 0xeb, 0xfd, 0xa8, 0xfb, 0xba, 0x90, 0x4f, 0x8e, 0x3e, 0xa9, 0xb5, 0x43, 0xf6,
450                0x54, 0x5d, 0xa1, 0xf2, 0xd5, 0x43, 0x29, 0x55, 0x61, 0x3f, 0x0f, 0xcf, 0x62, 0xd4,
451                0x97, 0x05, 0x24, 0x2a, 0x9a, 0xf9, 0xe6, 0x1e, 0x85, 0xdc, 0x0d, 0x65, 0x1e, 0x40,
452                0xdf, 0xcf, 0x01, 0x7b, 0x45, 0x57, 0x58, 0x87
453            ]
454        );
455    }
456
457    #[test]
458    fn it_should_compute_scrypt_for_vec3() {
459        let s = Scrypt::new(8, 1_048_576, 1);
460
461        let mut out: [u8; 64] = [0; 64];
462        s.derive(b"pleaseletmein", b"SodiumChloride", &mut out)
463            .expect("derivation to not fail");
464        assert_eq!(
465            out.to_vec(),
466            vec![
467                0x21, 0x01, 0xcb, 0x9b, 0x6a, 0x51, 0x1a, 0xae, 0xad, 0xdb, 0xbe, 0x09, 0xcf, 0x70,
468                0xf8, 0x81, 0xec, 0x56, 0x8d, 0x57, 0x4a, 0x2f, 0xfd, 0x4d, 0xab, 0xe5, 0xee, 0x98,
469                0x20, 0xad, 0xaa, 0x47, 0x8e, 0x56, 0xfd, 0x8f, 0x4b, 0xa5, 0xd0, 0x9f, 0xfa, 0x1c,
470                0x6d, 0x92, 0x7c, 0x40, 0xf4, 0xc3, 0x37, 0x30, 0x40, 0x49, 0xe8, 0xa9, 0x52, 0xfb,
471                0xcb, 0xf4, 0x5c, 0x6f, 0xa7, 0x7a, 0x41, 0xa4
472            ]
473        );
474    }
475}