1 module scrypt; 2 3 import std.string : indexOf; 4 import std.exception; 5 import std.digest.digest : toHexString; 6 import std.uuid : randomUUID; 7 import std.algorithm : splitter; 8 import std.array: array; 9 import std.conv: to; 10 11 import random; 12 13 uint SCRYPT_RAND_LEN = 32; 14 ulong SCRYPT_N_DEFAULT = 16384; 15 uint SCRYPT_R_DEFAULT = 8; 16 uint SCRYPT_P_DEFAULT = 1; 17 size_t SCRYPT_OUTPUTLEN_DEFAULT = 128; 18 19 bool SCRYPT_DEBUG = false; 20 21 public class ScryptException : Exception 22 { 23 this(string message) {super(message);} 24 } 25 26 public class SodiumChloride { 27 public string na; 28 public ubyte[] cl; 29 30 this(uint len) { 31 try { 32 ubyte[] randomNum = scryptRNG(len); 33 34 this.na = randomNum.toHexString(); 35 this.cl = cast(ubyte[])this.na; 36 } catch (ScryptRNGException rng) { 37 throw new ScryptException(rng.msg); 38 } 39 } 40 41 this() { 42 this(SCRYPT_RAND_LEN); 43 } 44 45 this(string str) { 46 this.na = str.idup; 47 this.cl = cast(ubyte[])this.na; 48 } 49 } 50 51 ubyte[] hex_to_ubyteArray(string hexnum) { 52 import std.conv: parse; 53 import std.array: array; 54 import std.range: chunks; 55 import std.algorithm: map; 56 57 ubyte[] bytes = hexnum.chunks(2) 58 .map!(twoDigits => twoDigits.parse!ubyte(16)).array(); 59 60 return bytes; 61 } 62 63 string to_hex(ubyte[] bytes) { 64 import std.digest.digest; 65 66 return bytes.toHexString(); 67 } 68 69 ubyte[] generatePassword(string password) { 70 SodiumChloride salt = new SodiumChloride(); 71 72 return generatePassword(password, salt.cl); 73 } 74 75 ubyte[] generatePassword(string password, ubyte[] salt) { 76 ubyte[] outpw = new ubyte[SCRYPT_OUTPUTLEN_DEFAULT]; 77 libscrypt_scrypt(cast(ubyte*)password.ptr, password.length, cast(ubyte*)salt.ptr, salt.length, SCRYPT_N_DEFAULT, SCRYPT_R_DEFAULT, SCRYPT_P_DEFAULT, outpw.ptr, outpw.length); 78 79 return outpw ~ salt; 80 } 81 82 ubyte[] crypto_scrypt(string password, ubyte[] salt, ulong N, uint r, uint p, ulong L) { 83 ubyte[] outpw = new ubyte[L]; 84 libscrypt_scrypt(cast(ubyte*)password.ptr, 85 password.length, 86 cast(ubyte*)salt.ptr, 87 salt.length, 88 N, 89 r, 90 p, 91 outpw.ptr, 92 outpw.length); 93 94 return outpw ~ salt; 95 } 96 97 bool checkPassword(ubyte[] hash, string password) { 98 import std.conv; 99 import std.stdio; 100 101 bool ret = false; 102 103 if (SCRYPT_OUTPUTLEN_DEFAULT > hash.length) return ret; 104 105 auto salt = hash[SCRYPT_OUTPUTLEN_DEFAULT..$]; 106 if(SCRYPT_DEBUG) writeln("---- > found salt : " ~ to!string(salt)); 107 auto checkHash = generatePassword(password, salt); 108 if(SCRYPT_DEBUG) writeln("---- > hash : " ~ to!string(hash)); 109 if(SCRYPT_DEBUG) writeln("---- > chkh : " ~ to!string(checkHash)); 110 if (checkHash == hash) ret = true; 111 if(SCRYPT_DEBUG) writeln("---- > returning : " ~ to!string(ret)); 112 return ret; 113 } 114 115 bool checkPassword(ubyte[] hash, string password, ulong N, uint r, uint p, ulong L) { 116 import std.conv; 117 import std.stdio; 118 119 bool ret = false; 120 121 if (L > hash.length) return ret; 122 123 auto salt = hash[L..$]; 124 if(SCRYPT_DEBUG) writeln("---- > found salt : " ~ to!string(salt) ~ "(" ~ cast(string)salt ~ ")"); 125 auto checkHash = crypto_scrypt(password, salt,N,r,p,L); 126 if(SCRYPT_DEBUG) writeln("---- > hash : " ~ to!string(hash)); 127 if(SCRYPT_DEBUG) writeln("---- > chkh : " ~ to!string(checkHash)); 128 if (checkHash == hash) ret = true; 129 if(SCRYPT_DEBUG) writeln("---- > returning : " ~ to!string(ret)); 130 return ret; 131 } 132 133 unittest { 134 import std.stdio; 135 136 SCRYPT_DEBUG = true; 137 138 // 139 // unit test #1 140 // 141 writeln("+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+="); 142 writeln("+ test #1: generatePassword"); 143 writeln("+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+="); 144 auto input = "test"; 145 auto hash = generatePassword(input); 146 writeln("*** > input: " ~ input); 147 writeln("*** > scrypt: " ~ to!string(hash)); 148 writeln("*** > scrypt Hex: " ~ hash.toHexString()); 149 assert(hash !is null); 150 151 writeln("*** test: checkPassword"); 152 assert(checkPassword(hash, input)); 153 writeln("*** > passed"); 154 155 writeln("*** test: checkPassword (mismatch)"); 156 assert(!checkPassword(hash, "not-test")); 157 writeln("*** > passed"); 158 159 // 160 //unit test #2 161 // 162 writeln("\n\n+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+="); 163 writeln("+ test #2: convert to hex string and back!"); 164 writeln("+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+="); 165 166 string hex = scrypt.to_hex(hash); 167 ubyte[] back_to_hash = scrypt.hex_to_ubyteArray(hex); 168 169 writeln("*** > orignal hash: " ~ to!string(hash)); 170 writeln("*** > to_hex: " ~ hex); 171 writeln("*** > back to hash: " ~ to!string(back_to_hash)); 172 assert(hash == back_to_hash); 173 writeln("*** > passed"); 174 175 // 176 // unit test #3 177 // 178 writeln("\n\n+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+="); 179 writeln("+ test #3: crypto_scrypt with static salt"); 180 writeln("+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+="); 181 SodiumChloride salt = new SodiumChloride("test"); 182 auto password = "password"; 183 auto L = SCRYPT_OUTPUTLEN_DEFAULT; 184 auto N = SCRYPT_N_DEFAULT; 185 auto r = SCRYPT_R_DEFAULT; 186 auto p = SCRYPT_P_DEFAULT; 187 auto hash2 = crypto_scrypt(password, salt.cl, N, r, p, L); 188 189 writeln("*** > salt: " ~ salt.na); 190 writeln("*** > password: " ~ input); 191 writeln("*** > L: " ~ to!string(L)); 192 writeln("*** > N: " ~ to!string(N)); 193 writeln("*** > R: " ~ to!string(r)); 194 writeln("*** > P: " ~ to!string(p)); 195 writeln("*** > scrypt: " ~ to!string(hash2)); 196 writeln("*** > scrypt Hex: " ~ scrypt.to_hex(hash2)); 197 assert(hash2 !is null); 198 199 200 writeln("*** test: checkPassword"); 201 assert(checkPassword(hash2, password,N,r,p,L)); 202 writeln("*** > passed"); 203 204 writeln("*** test: checkPassword (mismatch)"); 205 assert(!checkPassword(hash2, "aBogusPassword",N,r,p,L)); 206 writeln("*** > passed"); 207 208 // 209 // unit test #4 210 // 211 writeln("\n\n+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+="); 212 writeln("+ test #4: crypto_scrypt with random salt and Length=512"); 213 writeln("+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+="); 214 salt = new SodiumChloride(); 215 password = "password"; 216 L = 512; 217 N = SCRYPT_N_DEFAULT; 218 r = SCRYPT_R_DEFAULT; 219 p = SCRYPT_P_DEFAULT; 220 hash2 = crypto_scrypt(password, salt.cl, N, r, p, L); 221 222 writeln("*** > salt: " ~ salt.na); 223 writeln("*** > password: " ~ input); 224 writeln("*** > L: " ~ to!string(L)); 225 writeln("*** > N: " ~ to!string(N)); 226 writeln("*** > R: " ~ to!string(r)); 227 writeln("*** > P: " ~ to!string(p)); 228 writeln("*** > scrypt: " ~ to!string(hash2)); 229 writeln("*** > scrypt Hex: " ~ to_hex(hash2)); 230 assert(hash2 !is null); 231 232 writeln("*** test: checkPassword"); 233 assert(checkPassword(hash2, password,N,r,p,L)); 234 writeln("*** > passed"); 235 236 writeln("*** test: checkPassword (mismatch)"); 237 assert(!checkPassword(hash2, "aBogusPassword",N,r,p,L)); 238 writeln("*** > passed"); 239 } 240 241 242 private: 243 244 //import std.c.stdio; 245 import core.stdc.stdio; 246 247 alias ubyte uint8_t; 248 alias ulong uint64_t; 249 alias uint uint32_t; 250 251 extern (C): 252 253 /** 254 * libscrypt_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 greater than 1. 259 * 260 * Return 0 on success; or -1 on error. 261 */ 262 int libscrypt_scrypt(const uint8_t *, size_t, const uint8_t *, size_t, uint64_t, 263 uint32_t, uint32_t, uint8_t *, size_t); 264