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 }