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 }