1 /++
2 My minimal interface to https://github.com/p-h-c/phc-winner-argon2
3
4 You must compile and install the C library separately.
5 +/
6 module arsd.argon2;
7
8 // a password length limitation might legit make sense here cuz of the hashing function can get slow
9
10 // it is conceivably useful to hash the password with a secret key before passing to this function,
11 // but I'm not going to do that automatically here just to keep this thin and simple.
12
13 import core.stdc.stdint;
14
15 pragma(lib, "argon2");
16
17 extern(C)
18 int argon2id_hash_encoded(
19 const uint32_t t_cost,
20 const uint32_t m_cost,
21 const uint32_t parallelism,
22 const void *pwd, const size_t pwdlen,
23 const void *salt, const size_t saltlen,
24 const size_t hashlen, char *encoded,
25 const size_t encodedlen);
26
27 extern(C)
28 int argon2id_verify(const char *encoded, const void *pwd,
29 const size_t pwdlen);
30
31 enum ARGON2_OK = 0;
32
33 /// Parameters to the argon2 function. Bigger numbers make it harder to
34 /// crack, but also take more resources for legitimate users too
35 /// (e.g. making logins and signups slower and more memory-intensive). Some
36 /// examples are provided. HighSecurity is about 3/4 second on my computer,
37 /// MediumSecurity about 1/3 second, LowSecurity about 1/10 second.
38 struct SecurityParameters {
39 uint cpuCost;
40 uint memoryCost; /// in KiB fyi
41 uint parallelism;
42 }
43
44 /// ditto
45 enum HighSecurity = SecurityParameters(8, 512_000, 8);
46 /// ditto
47 enum MediumSecurity = SecurityParameters(4, 256_000, 4);
48 /// ditto
49 enum LowSecurity = SecurityParameters(2, 128_000, 4);
50
51 /// Check's a user's provided password against the saved password, and returns true if they matched. Neither string can be empty.
52 bool verify(string savedPassword, string providedPassword) {
53 return argon2id_verify((savedPassword[$-1] == 0 ? savedPassword : (savedPassword ~ '\0')).ptr, providedPassword.ptr, providedPassword.length) == ARGON2_OK;
54 }
55
56 /// encode a password for secure storage. verify later with [verify]
57 string encode(string password, SecurityParameters params = MediumSecurity) {
58 char[256] buffer;
59 enum HASHLEN = 80;
60
61 import core.stdc.string;
62
63 ubyte[32] salt = void;
64
65 version(linux) {{
66 import core.sys.posix.unistd;
67 import core.sys.posix.fcntl;
68 int fd = open("/dev/urandom", O_RDONLY);
69 auto ret = read(fd, salt.ptr, salt.length);
70 assert(ret == salt.length);
71 close(fd);
72 }} else version(Windows) {{
73 // https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom
74 static assert(0);
75 }} else {
76 import std.random;
77 foreach(ref s; salt)
78 s = cast(ubyte) uniform(0, 256);
79
80 static assert(0, "csrng not implemented");
81 }
82
83 auto ret = argon2id_hash_encoded(
84 params.cpuCost,
85 params.memoryCost,
86 params.parallelism,
87 password.ptr, password.length,
88 salt.ptr, salt.length,
89 HASHLEN, // desired size of hash. I think this is fine being arbitrary
90 buffer.ptr,
91 buffer.length
92 );
93
94 if(ret != ARGON2_OK)
95 throw new Exception("wtf");
96
97 return buffer[0 .. strlen(buffer.ptr) + 1].idup;
98 }