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 }