1 module viva.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 viva.scrypt.crypto;
11 import std.string : indexOf;
12 import std.exception : enforce;
13 import std.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 = genRandomSalt(),
41     size_t scrypt_outputlen = SCRYPT_OUTPUTLEN_DEFAULT, ulong N = SCRYPT_N_DEFAULT, uint r = SCRYPT_R_DEFAULT,
42     uint p = SCRYPT_P_DEFAULT) @trusted {
43     ubyte[] outpw = new ubyte[scrypt_outputlen];
44     libscrypt_scrypt(cast(ubyte*)password.ptr, password.length, cast(ubyte*)salt.ptr, salt.length, N, r, p, outpw.ptr, outpw.length);
45     
46     return toHexString(outpw).idup ~ TERMINATOR ~ salt ~ TERMINATOR ~ to!string(scrypt_outputlen) ~ TERMINATOR ~ to!string(N) ~ TERMINATOR ~ to!string(r) ~ TERMINATOR ~ to!string(p);
47 }
48 
49 /**
50   * Some info regarding the params
51   * hash: An already hashed version of your password, for example fetched from a database
52   * password: The password you wish to see if it matches
53   */
54 bool checkScryptPasswordHash(string hash, string password) @trusted {
55     auto params = hash.splitter(TERMINATOR).array[1 .. $];
56     enforce(params.length == 5, "invalid hash string, does not meet requirements");
57     return genScryptPasswordHash(password, params[0], to!size_t(params[1]), to!ulong(params[2]), to!uint(params[3]), to!uint(params[4])) == hash;
58 }