1 module scrypt.password;
2 
3 /*
4  * Copyright (C) 2013 Isak Andersson (BitPuffin@lavabit.com)
5  * 
6  * Distributed under the terms of the zlib/libpng license
7  * See LICENSE.txt in project root for more info
8  */
9 
10 import scrypt.crypto_scrypt;
11 import std.string : indexOf;
12 import std.exception : enforce;
13 import std.digest.digest : toHexString;
14 import std.uuid : randomUUID;
15 import std.algorithm : splitter;
16 import std.array: array;
17 import std.conv: to;
18 
19 enum SCRYPT_N_DEFAULT = 16384;
20 enum SCRYPT_R_DEFAULT = 8;
21 enum SCRYPT_P_DEFAULT = 1;
22 enum SCRYPT_OUTPUTLEN_DEFAULT = 90;
23 private enum TERMINATOR = '$'; 
24 
25 /// Takes no parameters, returns a random UUID in string form
26 string genRandomSalt() {
27     return randomUUID().toString();
28 }
29 
30 /**
31   * Some info regarding the params
32   * password: The password you want to hash, for example "my password"
33   * salt: The salt you want to use when hashing your password, for example generateRandomSalt();
34   * scrypt_outputlen: the length of the output string containing your hashed password from scrypt. Reccomended value is 90. Note, the actual output won't be 90 since it's a sha1 digest
35   * N: General work factor, iteration count. Must be power of two. Recommended value for passwords: 2^14 and 2^20 for sensitive stuff
36   * r: Blocksize for underlying hash. Reccommended value is 8
37   * p: parallelization factor. Reccomended value is 1
38   * If you want to you can use SCRYPT_N_DEFAULT, SCRYPT_R_DEFAULT, SCRYPT_P_DEFAULT, SCRYPT_OUTPUTLEN_DEFAULT as default params
39   */
40 string genScryptPasswordHash(string password, string salt, size_t scrypt_outputlen, ulong N, uint r, uint p) {
41     ubyte[] outpw = new ubyte[scrypt_outputlen];
42     crypto_scrypt(cast(ubyte*)password.ptr, password.length, cast(ubyte*)salt.ptr, salt.length, N, r, p, outpw.ptr, outpw.length);
43     
44     return toHexString(outpw).idup ~ TERMINATOR ~ salt ~ TERMINATOR ~ to!string(scrypt_outputlen) ~ TERMINATOR ~ to!string(N) ~ TERMINATOR ~ to!string(r) ~ TERMINATOR ~ to!string(p);
45 }
46 
47 /**
48   * Some info regarding the params
49   * hash: An already hashed version of your password, for example fetched from a database
50   * password: The password you wish to see if it matches
51   */
52 bool checkScryptPasswordHash(string hash, string password) {
53     auto params = hash.splitter(TERMINATOR).array[1 .. $];
54     enforce(params.length == 5, "invalid hash string, does not meet requirements");
55     return genScryptPasswordHash(password, params[0], to!size_t(params[1]), to!ulong(params[2]), to!uint(params[3]), to!uint(params[4])) == hash;
56 }
57