1 module chloride.password;
2 
3 import chloride.core;
4 import chloride.random : randomArray;
5 
6 import std.array : uninitializedArray;
7 import std.algorithm.mutation : copy, fill;
8 import std.exception: assumeUnique;
9 import std.string : fromStringz, toStringz;
10 
11 import sodium.crypto_pwhash_scryptsalsa208sha256;
12 
13 alias Salt = ubyte[crypto_pwhash_scryptsalsa208sha256_SALTBYTES];
14 
15 alias PwStringBytes = crypto_pwhash_scryptsalsa208sha256_STRBYTES;
16 
17 /**
18  * Struct containing configuration for password hashing. Specifically the parameters
19  * to control the amount of CPU and memory required.
20  */
21 struct PwHashConfig {
22     ulong opslimit = crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE;
23     size_t memlimit = crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE;
24 }
25 
26 /**
27  * Password hashing config suitable for interactive use.
28  */
29 enum interactivePwHashConfig = PwHashConfig();
30 
31 /**
32  * Password hashing config suitable for highly sensitive data
33  */
34 enum sensitivePwHashConfig = PwHashConfig(
35     crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE,
36     crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE);
37 
38 void hashPasswordBuffer(ubyte[] out_, in char[] password, in Salt salt, PwHashConfig config) {
39     int result = crypto_pwhash_scryptsalsa208sha256(
40         out_.ptr, out_.length,
41         password.ptr, password.length,
42         salt.ptr, config.opslimit, config.memlimit);
43     enforceSodium(result == 0);
44 }
45 
46 /**
47  * Hash a password with a salt. Returns the hash as a `ubyte[]`.
48  */
49 ubyte[] hashPassword(in char[] password, in Salt salt, PwHashConfig config, size_t length) {
50     ubyte[] hash = uninitializedArray!(ubyte[])(length);
51     hashPasswordBuffer(hash, password, salt, config);
52     return hash;
53 }
54 
55 /**
56  * Create a string that can be used to store a password safely.
57  * It includes the hash, the salt, and information about the CPU and
58  * memory limits used to compute it.
59  */
60 string passwordStorageString(string password, PwHashConfig config) {
61     char[PwStringBytes] out_ = void;
62     int result = crypto_pwhash_scryptsalsa208sha256_str(
63         out_, password.ptr, password.length,
64         config.opslimit, config.memlimit);
65     enforceSodium(result == 0);
66     return fromStringz(out_.ptr).idup;
67 }
68 
69 /**
70  * Verify a password with a hash and salt
71  */
72 bool verifyPassword(in ubyte[] hash, string password, in Salt salt, PwHashConfig config) {
73     import core.memory : GC;
74 
75     auto hashAttempt = uninitializedArray!(ubyte[])(hash.length);
76     scope(exit) {
77         GC.free(hashAttempt.ptr);
78     }
79     hashPasswordBuffer(hashAttempt, password, salt, config);
80     return hashAttempt == hash;
81 }
82 
83 /**
84  * Verify a password against a storage string obtained from `passwordStorageString`
85  */
86 bool verifyPassword(string password, string storageString) {
87     char[PwStringBytes] data = void;
88     assert(storageString.length < data.length);
89     auto tail = storageString.copy(data[]);
90     tail[0] = '\0';
91     int result = crypto_pwhash_scryptsalsa208sha256_str_verify(data,
92                                                               password.ptr, password.length);
93     return result == 0;
94 }
95 
96 /**
97  * Generate a salt suitable for hashing passwords.
98  */
99 alias makeSalt = randomArray!Salt;
100 
101 ///
102 unittest {
103     auto salt = makeSalt();
104     auto hash = hashPassword("password", salt, interactivePwHashConfig, 32);
105     assert(verifyPassword(hash, "password", salt, interactivePwHashConfig));
106 }
107 
108 ///
109 unittest {
110     import std.stdio;
111     auto storageString = passwordStorageString("password", interactivePwHashConfig);
112     writeln("Hashed Password: ", storageString);
113     assert(verifyPassword("password", storageString));
114 
115 }