1 /**
2 	Ulrich Drepper's SHA-based crypt(3) algorithms, as specified here:
3 	https://akkadia.org/drepper/SHA-crypt.txt
4 
5 	The main exports are the `SHA256Crypt` and `SHA512Crypt` structs.  Their `genSalt()` static members take an optional count of rounds.  More rounds means more work in computing the hash — both for friendly applications and password bruteforcers.  The default (as in the spec) is 5000 rounds.
6 */
7 module passwd.sha;
8 
9 @safe:
10 
11 import std.algorithm.mutation : swap;
12 import std.digest.sha;
13 import std.range.primitives;
14 import std..string : representation;
15 import std.traits : ReturnType;
16 public import std.typecons : Flag, No, Yes;
17 import std.utf : byCodeUnit;
18 
19 import passwd.exception;
20 import passwd.util;
21 
22 /// Implementation of Ulrich Drepper's SHA256-based crypt algorithm
23 alias SHA256Crypt = SHACrypt!(SHA256, sha256crypt_output_swaps, "$5$");
24 /// Implementation of Ulrich Drepper's SHA512-based crypt algorithm
25 alias SHA512Crypt = SHACrypt!(SHA512, sha512crypt_output_swaps, "$6$");
26 
27 package:
28 
29 /// Routines for SHA-based crypt(3) algorithms
30 struct SHACrypt(Hasher, alias output_swaps, string algo_id)
31 {
32 	// Example: $5$rounds=10000$saltstringsaltst$3xv.VbSHBb41AL9AvLeujZkZRBAwqFMz2.opqey6IcA
33 	/// Maximum length needed for output of genSalt()
34 	enum kMaxSaltStrLength = "$x$rounds=999999999$".length + kSHACryptSaltBytes * 8 / 6;
35 	/// Maximum length needed for output of crypt()
36 	enum kMaxCryptStrLength = kMaxSaltStrLength + 1 + kDigestLength * 8 / 6;
37 
38 	/// Generate a good salt for this algorithm
39 	static string genSalt(size_t num_rounds=kDefaultSHACryptRounds)
40 	{
41 		import std.array : appender;
42 		auto ret_app = appender!string;
43 		ret_app.genSHACryptSalt(algo_id, num_rounds);
44 		return ret_app[];
45 	}
46 
47 	/// Generate a good salt for this algorithm and write to an output range
48 	static void genSalt(Out)(ref Out output, size_t num_rounds=kDefaultSHACryptRounds) if (isOutputRange!(Out, char))
49 	{
50 		output.genSHACryptSalt(algo_id, num_rounds);
51 	}
52 
53 	/// Hash password and write full crypt(3) string or just encoded digest to an output range
54 	static void crypt(Out)(const(char)[] password, ref Out output, ref const(CryptPieces) salt_data, Flag!"writeSalt" write_salt = Yes.writeSalt) if (isOutputRange!(Out, char))
55 	{
56 		if (write_salt) salt_data.writeSHACryptSalt(output);
57 		auto digest = digestOf(password, salt_data);
58 		digest.cryptB64Encode(output);
59 	}
60 
61 	private:
62 
63 	/// Length in bytes of a binary digest
64 	enum kDigestLength = ReturnType!(Hasher.finish).length;
65 
66 	/// Generate only the raw binary output of this hashing algorithm
67 	static ubyte[kDigestLength] digestOf(const(char)[] password, ref const(CryptPieces) salt_data)
68 	{
69 		return shaCryptHash!(Hasher, output_swaps)(password, salt_data);
70 	}
71 }
72 
73 private:
74 
75 enum kDefaultSHACryptRounds = 5000;
76 enum kMinSHACryptRounds = 1000;
77 enum kMaxSHACryptRounds = 999_999_999;
78 enum kMaxSHACryptSaltValueLength = 16;
79 enum kSHACryptSaltBytes = 12;
80 
81 /// Format existing salt data and write to an output range
82 void writeSHACryptSalt(Out)(ref const(CryptPieces) salt_data, ref Out output) if (isOutputRange!(Out, char))
83 {
84 	output.put('$');
85 	foreach (char c; salt_data.algo_id) output.put(c);
86 	output.put('$');
87 	if (!salt_data.params.empty)
88 	{
89 		import std.conv : toChars;
90 		auto num_rounds = getNumRounds(salt_data.params);
91 		foreach (char c; "rounds=") output.put(c);
92 		foreach (char c; toChars(num_rounds)) output.put(c);
93 		output.put('$');
94 	}
95 	const(char)[] salt_txt = salt_data.salt_txt;
96 	if (salt_txt.length > kMaxSHACryptSaltValueLength) salt_txt = salt_txt[0..kMaxSHACryptSaltValueLength];
97 	foreach (char c; salt_txt) output.put(c);
98 	output.put('$');
99 }
100 
101 /// Generate a new salt string
102 void genSHACryptSalt(Out)(ref Out output, string algo_id, size_t num_rounds) if (isOutputRange!(Out, char))
103 {
104 	foreach (char c; algo_id) output.put(c);
105 
106 	if (num_rounds != kDefaultSHACryptRounds)
107 	{
108 		import std.conv : toChars;
109 		foreach (char c; "rounds=") output.put(c);
110 		foreach (char c; toChars(num_rounds)) output.put(c);
111 		output.put('$');
112 	}
113 
114 	ubyte[kSHACryptSaltBytes] random_buf;
115 	fillSecureRandom(random_buf[]);
116 	random_buf.cryptB64Encode(output);
117 }
118 
119 /**
120 	Parse the params field from the salt string to get the number of rounds (or return default value)
121 
122 	According to the spec, the number of rounds silently clips to the min/max values.
123 */
124 size_t getNumRounds(const(char)[] params_str) pure
125 {
126 	import std.algorithm.searching : startsWith;
127 	import std.utf : byCodeUnit;
128 	size_t num_rounds = kDefaultSHACryptRounds;
129 	if (params_str.startsWith("rounds="))
130 	{
131 		params_str = params_str["rounds=".length..$];
132 		num_rounds = 0;
133 		static assert (kMinSHACryptRounds * 10 < size_t.max, "Integer overflow possible");
134 		while (!params_str.empty && params_str[0] >= '0' && params_str[0] <= '9')
135 		{
136 			num_rounds *= 10;
137 			num_rounds += params_str[0] - '0';
138 			if (num_rounds > kMaxSHACryptRounds) num_rounds = kMaxSHACryptRounds;
139 			params_str.popFront();
140 		}
141 		if (num_rounds < kMinSHACryptRounds) num_rounds = kMinSHACryptRounds;
142 	}
143 	enforce!ValueException(params_str.empty, "Field contains = character but can't be parsed as SHA crypt parameters");
144 	return num_rounds;
145 }
146 
147 ///
148 unittest
149 {
150 	assert (getNumRounds("") == kDefaultSHACryptRounds);
151 	assert (getNumRounds("rounds=4242") == 4242);
152 	assert (getNumRounds("rounds=0") == kMinSHACryptRounds);
153 	assert (getNumRounds("rounds=2") == kMinSHACryptRounds);
154 	assert (getNumRounds("rounds=1000000000000000000000000000000000000") == kMaxSHACryptRounds);
155 }
156 
157 /// The heavy lifting of hashing the password to a binary digest
158 ReturnType!(Hasher.finish) shaCryptHash(Hasher, alias output_swaps)(const(char)[] password, ref const(CryptPieces) salt_data)
159 {
160 	alias Digest = ReturnType!(Hasher.finish);
161 	auto num_rounds = getNumRounds(salt_data.params);
162 	auto salt_bytes = salt_data.salt_txt.representation;
163 	auto password_bytes = password.representation;
164 
165 	if (salt_bytes.length > kMaxSHACryptSaltValueLength) salt_bytes = salt_bytes[0..kMaxSHACryptSaltValueLength];
166 
167 	// Numbers correspond to steps in Ulrich Drepper's spec
168 	// 1
169 	Hasher hasher_out;
170 	scope (exit) secureWipe(hasher_out);
171 	// 2
172 	hasher_out.put(password_bytes);
173 	// 3
174 	hasher_out.put(salt_bytes);
175 
176 	Digest digest_out, digest_b;
177 	scope (exit) secureWipe(digest_b);
178 	{
179 		// 4, 5, 6
180 		Hasher hasher_b = hasher_out;
181 		scope (exit) secureWipe(hasher_b);
182 		// 7
183 		hasher_b.put(password_bytes);
184 		// 8
185 		digest_b = hasher_b.finish();
186 	}
187 
188 	// 9, 10
189 	hasher_out.stretchPut(digest_b, password_bytes.length);
190 
191 	// 11
192 	for (size_t j = password_bytes.length; j; j >>= 1)
193 	{
194 		if (j & 1)
195 		{
196 			// 11a
197 			hasher_out.put(digest_b[]);
198 		}
199 		else
200 		{
201 			// 11b
202 			hasher_out.put(password_bytes);
203 		}
204 	}
205 
206 	// 12
207 	digest_out = hasher_out.finish();
208 
209 	Digest digest_dp;
210 	scope (exit) secureWipe(digest_dp);
211 	{
212 		// 13
213 		Hasher hasher_dp;
214 		scope (exit) secureWipe(hasher_dp);
215 		// 14
216 		foreach (_; 0..password_bytes.length) hasher_dp.put(password_bytes);
217 		// 1
218 		digest_dp = hasher_dp.finish();
219 	}
220 
221 	Digest digest_ds;
222 	scope (exit) secureWipe(digest_ds);
223 	{
224 		// 17
225 		Hasher hasher_ds;
226 		scope (exit) secureWipe(hasher_ds);
227 		// 18
228 		foreach (_; 0..16+digest_out[0])
229 		{
230 			hasher_ds.put(salt_bytes);
231 		}
232 		// 19
233 		digest_ds = hasher_ds.finish();
234 	}
235 
236 	// 21
237 	Hasher hasher_c;
238 	scope(exit) secureWipe(hasher_c);
239 	foreach (j; 0..num_rounds)
240 	{
241 		// 21a
242 		if (j & 1)
243 		{
244 			// 21b
245 			stretchPut(hasher_c, digest_dp, password_bytes.length);
246 		}
247 		else
248 		{
249 			// 21c
250 			hasher_c.put(digest_out);
251 		}
252 
253 		// 21d
254 		if (j % 3 != 0) stretchPut(hasher_c, digest_ds, salt_bytes.length);
255 		// 21e
256 		if (j % 7 != 0) stretchPut(hasher_c, digest_dp, password_bytes.length);
257 		if (j & 1)
258 		{
259 			// 21f
260 			hasher_c.put(digest_out);
261 		}
262 		else
263 		{
264 			// 21g
265 			stretchPut(hasher_c, digest_dp, password_bytes.length);
266 		}
267 		digest_out = hasher_c.finish();
268 		hasher_c.start();
269 	}
270 
271 	// The output gets permuted as a final flourish
272 	foreach (j; 0..output_swaps.length)
273 	{
274 		swap(digest_out[j], digest_out[output_swaps[j]]);
275 	}
276 
277 	return digest_out;
278 }
279 
280 immutable(size_t[]) sha256crypt_output_swaps = permSwapDecomposition([
281 	20, 10, 0,
282 	11, 1, 21,
283 	2, 22, 12,
284 	23, 13, 3,
285 	14, 4, 24,
286 	5, 25, 15,
287 	26, 16, 6,
288 	17, 7, 27,
289 	8, 28, 18,
290 	29, 19, 9,
291 	30, 31
292 ]);
293 
294 immutable(size_t[]) sha512crypt_output_swaps = permSwapDecomposition([
295 	42, 21, 0,
296 	1, 43, 22,
297 	23, 2, 44,
298 	45, 24, 3,
299 	4, 46, 25,
300 	26, 5, 47,
301 	48, 27, 6,
302 	7, 49, 28,
303 	29, 8, 50,
304 	51, 30, 9,
305 	10, 52, 31,
306 	32, 11, 53,
307 	54, 33, 12,
308 	13, 55, 34,
309 	35, 14, 56,
310 	57, 36, 15,
311 	16, 58, 37,
312 	38, 17, 59,
313 	60, 39, 18,
314 	19, 61, 40,
315 	41, 20, 62,
316 	63
317 ]);