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 }