1 /++
2 DAuth - Salted Hashed Password Library for D
3 Core package
4 +/
5 module dauth.core;
6 
7 import std.algorithm;
8 import std.array;
9 import ascii = std.ascii;
10 import std.base64;
11 import std.conv;
12 import std.digest.crc;
13 import std.digest.md;
14 import std.digest.ripemd;
15 import std.digest.sha;
16 import std.exception;
17 import std.functional;
18 import std.random;
19 import std.range;
20 import std.traits;
21 import std.typecons;
22 
23 import dauth.random : randomSalt;
24 import dauth.hashdrbg;
25 
26 version(DAuth_Unittest)
27 {
28 	version(DAuth_Unittest_Quiet) {} else
29 		version = Loud_Unittest;
30 
31 	version(Loud_Unittest)
32 		import std.stdio;
33 
34 	void unitlog(string str)
35 	{
36 		version(Loud_Unittest)
37 		{
38 			writeln("unittest DAuth: ", str);
39 			stdout.flush();
40 		}
41 	}
42 }
43 
44 version(DAuth_AllowWeakSecurity) {} else
45 {
46 	version = DisallowWeakSecurity;
47 }
48 
49 alias Salt = ubyte[]; /// Salt type
50 alias Salter(TDigest) = void delegate(ref TDigest, Password, Salt); /// Convenience alias for salter delegates.
51 alias DefaultCryptoRand = HashDRBGStream!(SHA512, "DAuth"); /// Default is Hash_DRBG using SHA-512
52 alias DefaultDigest = SHA512; /// Default is SHA-512
53 alias DefaultDigestClass = WrapperDigest!DefaultDigest; /// OO-style version of 'DefaultDigest'.
54 alias TokenBase64 = Base64Impl!('-', '_', '~'); /// Implementation of Base64 engine used for tokens.
55 
56 /++
57 Default implementation of 'digestCodeOfObj' for DAuth-style hash strings.
58 See 'Hash!(TDigest).toString' for more info.
59 +/
60 string defaultDigestCodeOfObj(Digest digest)
61 {
62 	if     (cast( CRC32Digest      )digest) return "CRC32";
63 	else if(cast( MD5Digest        )digest) return "MD5";
64 	else if(cast( RIPEMD160Digest  )digest) return "RIPEMD160";
65 	else if(cast( SHA1Digest       )digest) return "SHA1";
66 	else if(cast( SHA224Digest     )digest) return "SHA224";
67 	else if(cast( SHA256Digest     )digest) return "SHA256";
68 	else if(cast( SHA384Digest     )digest) return "SHA384";
69 	else if(cast( SHA512Digest     )digest) return "SHA512";
70 	else if(cast( SHA512_224Digest )digest) return "SHA512_224";
71 	else if(cast( SHA512_256Digest )digest) return "SHA512_256";
72 	else
73 		throw new UnknownDigestException("Unknown digest type");
74 }
75 
76 /++
77 Default implementation of 'digestFromCode' for DAuth-style hash strings.
78 See 'parseHash' for more info.
79 +/
80 Digest defaultDigestFromCode(string digestCode)
81 {
82 	switch(digestCode)
83 	{
84 	case "CRC32":      return new CRC32Digest();
85 	case "MD5":        return new MD5Digest();
86 	case "RIPEMD160":  return new RIPEMD160Digest();
87 	case "SHA1":       return new SHA1Digest();
88 	case "SHA224":     return new SHA224Digest();
89 	case "SHA256":     return new SHA256Digest();
90 	case "SHA384":     return new SHA384Digest();
91 	case "SHA512":     return new SHA512Digest();
92 	case "SHA512_224": return new SHA512_224Digest();
93 	case "SHA512_256": return new SHA512_256Digest();
94 	default:
95 		throw new UnknownDigestException("Unknown digest code");
96 	}
97 }
98 
99 /++
100 Default implementation of 'digestCodeOfObj' for Unix crypt-style hash strings.
101 See 'Hash!(TDigest).toString' for more info.
102 +/
103 string defaultDigestCryptCodeOfObj(Digest digest)
104 {
105 	if     (cast( MD5Digest    )digest) return "1";
106 	else if(cast( SHA256Digest )digest) return "5";
107 	else if(cast( SHA512Digest )digest) return "6";
108 	else
109 		throw new UnknownDigestException("Unknown digest type");
110 }
111 
112 /++
113 Default implementation of 'digestFromCode' for Unix crypt-style hash strings.
114 See 'parseHash' for more info.
115 +/
116 Digest defaultDigestFromCryptCode(string digestCode)
117 {
118 	switch(digestCode)
119 	{
120 	case "":   throw new UnknownDigestException(`Old crypt-DES not currently supported`);
121 	case "1":  return new MD5Digest();
122 	case "5":  return new SHA256Digest();
123 	case "6":  return new SHA512Digest();
124 	default:
125 		throw new UnknownDigestException("Unknown digest code");
126 	}
127 }
128 
129 /// Default salter for 'makeHash' and 'isSameHash'.
130 void defaultSalter(TDigest)(ref TDigest digest, Password password, Salt salt)
131 	if(isAnyDigest!TDigest)
132 {
133 	digest.put(cast(immutable(ubyte)[])salt);
134 	digest.put(password.data);
135 }
136 
137 /++
138 Note, this only checks Phobos's RNG's and digests, and only by type. This
139 works on a blacklist basis - it blindly accepts any Phobos-compatible RNG
140 or digest it does not know about. This is only supplied as a convenience. It
141 is always your own responsibility to select an appropriate algorithm for your
142 own needs.
143 
144 And yes, unfortunately, this does currently rule out all RNG's and digests
145 currently in Phobos (as of v2.065). They are all known to be fairly weak
146 for password-hashing purposes, even SHA1 which despite being heavily used
147 has known security flaws.
148 
149 For random number generators, you should use a CPRNG (cryptographically secure
150 pseudorandom number generator):
151     $(LINK http://en.wikipedia.org/wiki/Cryptographically_secure_pseudo-random_number_generator )
152 
153 For digests, you should use one of the SHA-2 algorithms (for example, SHA512)
154 or, better yet, an established "key stretching" algorithm
155 ( $(LINK http://en.wikipedia.org/wiki/Key_stretching#History) ), intended
156 for password hashing. These contain deliberate inefficiencies that cannot be
157 optimized away even with massive parallelization (such as a GPU cluster). These
158 are NOT too inefficient to use for even high-traffic authentication, but they
159 do thwart the parallelized brute force attacks that algorithms used for
160 streaming data encryption, such as SHA, are increasingly susceptible to.
161     $(LINK https://crackstation.net/hashing-security.htm)
162 +/
163 bool isKnownWeak(T)() if(isDigest!T || isSomeRandom!T)
164 {
165 	return
166 		is(T == CRC32) ||
167 		is(T == MD5) ||
168 		is(T == RIPEMD160) ||
169 		is(T == SHA1) ||
170 
171 		// Requires to-be-released DMD 2.066:
172 		//__traits(isSame, TemplateOf!T, LinearCongruentialEngine) ||
173 		//__traits(isSame, TemplateOf!T, MersenneTwisterEngine) ||
174 		//__traits(isSame, TemplateOf!T, XorshiftEngine);
175 		is(T == MinstdRand0) ||
176 		is(T == MinstdRand) ||
177 		is(T == Mt19937) ||
178 		is(T == Xorshift32) ||
179 		is(T == Xorshift64) ||
180 		is(T == Xorshift96) ||
181 		is(T == Xorshift128) ||
182 		is(T == Xorshift160) ||
183 		is(T == Xorshift192) ||
184 		is(T == Xorshift);
185 }
186 
187 ///ditto
188 bool isKnownWeak(T)(T digest) if(is(T : Digest))
189 {
190 	return
191 		cast(CRC32Digest)digest ||
192 		cast(MD5Digest)digest ||
193 		cast(RIPEMD160Digest)digest ||
194 		cast(SHA1Digest)digest;
195 }
196 
197 private void validateStrength(T)() if(isDigest!T || isSomeRandom!T)
198 {
199 	version(DisallowWeakSecurity)
200 	{
201 		static if(isKnownWeak!T())
202 		{
203 			pragma(msg, "ERROR: "~T.stringof~" - "~KnownWeakException.message);
204 			static assert(false);
205 		}
206 	}
207 }
208 
209 private void validateStrength(Digest digest)
210 {
211 	version(DisallowWeakSecurity)
212 	{
213 		enforce(!isKnownWeak(digest),
214 			new KnownWeakException(defaultDigestCodeOfObj(digest)));
215 	}
216 }
217 
218 /++
219 Thrown whenever a digest type cannot be determined.
220 For example, when the provided (or default) 'digestCodeOfObj' or 'digestFromCode'
221 delegates fail to find a match. Or when passing isSameHash a
222 Hash!Digest with a null 'digest' member (which prevents it from determining
223 the correct digest to match with).
224 +/
225 class UnknownDigestException : Exception
226 {
227 	this(string msg) { super(msg); }
228 }
229 
230 /++
231 Thrown when a known-weak algortihm or setting it attempted, UNLESS
232 compiled with '-version=DAuth_AllowWeakSecurity'
233 +/
234 class KnownWeakException : Exception
235 {
236 	static enum message =
237 		"This is known to be weak for salted password hashing. "~
238 		"If you understand and accept the risks, you can force DAuth "~
239 		"to allow it with -version=DAuth_AllowWeakSecurity";
240 
241 	this(string algoName)
242 	{
243 		super(algoName ~ " - " ~ message);
244 	}
245 }
246 
247 /++
248 Like std.digest.digest.isDigest, but also accepts OO-style digests
249 (ie. classes deriving from interface std.digest.digest.Digest)
250 +/
251 template isAnyDigest(TDigest)
252 {
253 	enum isAnyDigest =
254 		isDigest!TDigest ||
255 		is(TDigest : Digest);
256 }
257 
258 version(DAuth_Unittest)
259 unittest
260 {
261 	struct Foo {}
262 	static assert(isAnyDigest!SHA1);
263 	static assert(isAnyDigest!SHA1Digest);
264 	static assert(isAnyDigest!SHA256);
265 	static assert(isAnyDigest!SHA256Digest);
266 	static assert(!isAnyDigest!Foo);
267 	static assert(!isAnyDigest!Object);
268 }
269 
270 /++
271 Like std.digest.digest.DigestType, but also accepts OO-style digests
272 (ie. classes deriving from interface std.digest.digest.Digest)
273 +/
274 template AnyDigestType(TDigest)
275 {
276 	static assert(isAnyDigest!TDigest,
277 		TDigest.stringof ~ " is not a template-style or OO-style digest (fails isAnyDigest!T)");
278 
279 	static if(isDigest!TDigest)
280 		alias AnyDigestType = DigestType!TDigest;
281 	else
282 		alias AnyDigestType = ubyte[];
283 }
284 
285 version(DAuth_Unittest)
286 unittest
287 {
288 	struct Foo {}
289 	static assert( is(AnyDigestType!SHA1 == ubyte[20]) );
290 	static assert( is(AnyDigestType!SHA1Digest == ubyte[]) );
291 	static assert( is(AnyDigestType!SHA512 == ubyte[64]) );
292 	static assert( is(AnyDigestType!SHA512Digest == ubyte[]) );
293 	static assert( !is(AnyDigestType!Foo) );
294 	static assert( !is(AnyDigestType!Object) );
295 }
296 
297 /// Tests if the type is an instance of struct Hash(some digest)
298 template isHash(T)
299 {
300 	enum isHash = is( Hash!(TemplateArgsOf!(T)[0]) == T );
301 }
302 
303 version(DAuth_Unittest)
304 unittest
305 {
306 	struct Foo {}
307 	struct Bar(T) { T digest; }
308 
309 	static assert( isHash!(Hash!SHA1) );
310 	static assert( isHash!(Hash!SHA1Digest) );
311 	static assert( isHash!(Hash!SHA512) );
312 	static assert( isHash!(Hash!SHA512Digest) );
313 
314 	static assert( !isHash!Foo              );
315 	static assert( !isHash!(Bar!int)        );
316 	static assert( !isHash!(Bar!Object)     );
317 	static assert( !isHash!(Bar!SHA1)       );
318 	static assert( !isHash!(Bar!SHA1Digest) );
319 }
320 
321 /// Retreive the digest type of a struct Hash(some digest)
322 template DigestOf(T) if(isHash!T)
323 {
324 	alias DigestOf = TemplateArgsOf!(T)[0];
325 }
326 
327 version(DAuth_Unittest)
328 unittest
329 {
330 	static assert(is( DigestOf!(Hash!SHA1  ) == SHA1  ));
331 	static assert(is( DigestOf!(Hash!SHA512) == SHA512));
332 	static assert(is( DigestOf!(Hash!Digest) == Digest));
333 }
334 
335 string getDigestCode(TDigest)(string delegate(Digest) digestCodeOfObj, TDigest digest)
336 	if(isAnyDigest!TDigest)
337 {
338 	static if(is(TDigest : Digest))
339 		return digestCodeOfObj(digest);
340 	else
341 	{
342 		auto digestObj = new WrapperDigest!TDigest();
343 		return digestCodeOfObj(digestObj);
344 	}
345 }
346 
347 /++
348 A reference-counted type for passwords. The memory containing the password
349 is automatically zeroed-out when there are no more references or when
350 a new password is assigned.
351 
352 If you keep any direct references to Password.data, be aware it may get cleared.
353 
354 Create a Password via functions 'toPassword' or 'dupPassword'.
355 
356 The payload is a private struct that supports the following:
357 
358 ---------------------------------------------------------
359 	@property ubyte[] data(): Retrieve the actual plaintext password
360 
361 	@property size_t length() const: Retrieve the password length
362 
363 	void opAssign(PasswordData rhs): Assignment
364 
365 	void opAssign(ubyte[] rhs): Assignment
366 
367 	~this(): Destructor
368 ---------------------------------------------------------
369 +/
370 alias Password = RefCounted!PasswordData;
371 
372 /// Payload of Password
373 private struct PasswordData
374 {
375 	private ubyte[] _data;
376 
377 	@property ubyte[] data()
378 	{
379 		return _data;
380 	}
381 
382 	@property size_t length() const
383 	{
384 		return _data.length;
385 	}
386 
387 	void opAssign(PasswordData rhs)
388 	{
389 		opAssign(rhs._data);
390 	}
391 
392 	void opAssign(ubyte[] rhs)
393 	{
394 		clear();
395 		this._data = rhs;
396 	}
397 
398 	~this()
399 	{
400 		clear();
401 	}
402 
403 	private void clear()
404 	{
405 		_data[] = 0;
406 	}
407 }
408 
409 /++
410 Constructs a Password from a ubyte[].
411 Mainly provided for syntactic consistency with 'toPassword(char[])'.
412 +/
413 Password toPassword(ubyte[] password)
414 {
415 	return Password(password);
416 }
417 
418 /++
419 Constructs a Password from a char[] so you don't have to cast to ubyte[],
420 and don't accidentally cast away immutability.
421 +/
422 Password toPassword(char[] password)
423 {
424 	return Password(cast(ubyte[])password);
425 }
426 
427 /++
428 This function exists as a convenience in case you need it, HOWEVER it's
429 recommended to design your code so you DON'T need to use this (use
430 toPassword instead):
431 
432 Using this to create a Password cannot protect the in-memory data of your
433 original string because a string's data is immutable (this function must
434 .dup the memory).
435 
436 While immutability usually improves safety, you should avoid ever storing
437 unhashed passwords in immutables because they cannot be reliably
438 zero-ed out.
439 +/
440 Password dupPassword(string password)
441 {
442 	return toPassword(password.dup);
443 }
444 
445 /++
446 Contains all the relevant information for a salted hash.
447 Note the digest type can be obtained via DigestOf!(SomeHashType).
448 +/
449 struct Hash(TDigest) if(isAnyDigest!TDigest)
450 {
451 	Salt salt; /// The salt that was used.
452 
453 	/++
454 	The hash of the salted password. To obtain a printable DB-friendly
455 	string, pass this to std.digest.digest.toHexString.
456 	+/
457 	AnyDigestType!TDigest hash;
458 
459 	/// The digest that was used for hashing.
460 	TDigest digest;
461 
462 	/++
463 	Encodes the digest, salt and hash into a convenient forward-compatible
464 	string format, ready for insertion into a database.
465 
466 	To support additional digests besides the built-in (Phobos's CRC32, MD5,
467 	RIPEMD160 and SHA), supply a custom delegate for digestCodeOfObj.
468 	Your custom digestCodeOfObj only needs to handle OO-style digests.
469 	As long as the OO-style digests were created using Phobos's
470 	WrapperDigest template, the template-style version will be handled
471 	automatically. You can defer to DAuth's defaultDigestCodeOfObj to
472 	handle the built-in digests.
473 
474 	Example:
475 	-------------------
476 	import std.digest.digest;
477 	import dauth;
478 
479 	struct BBQ42 {...}
480 	static assert(isDigest!BBQ42);
481 	alias BBQ42Digest = WrapperDigest!BBQ42;
482 
483 	string customDigestCodeOfObj(Digest digest)
484 	{
485 	    if     (cast(BBQ42Digest)digest) return "BBQ42";
486 	    else if(cast(FAQ17Digest)digest) return "FAQ17";
487 	    else
488 	        return defaultDigestCodeOfObj(digest);
489 	}
490 
491 	void doStuff(Hash!BBQ42 hash)
492 	{
493 	    writeln( hash.toString(&customDigestCodeOfObj) );
494 	}
495 	-------------------
496 
497 	Optional_Params:
498 	digestCodeOfObj - Default value is 'toDelegate(&defaultDigestCodeOfObj)'
499 	+/
500 	string toString(string delegate(Digest) digestCodeOfObj = toDelegate(&defaultDigestCodeOfObj))
501 	{
502 		Appender!string sink;
503 		toString(sink, digestCodeOfObj);
504 		return sink.data;
505 	}
506 
507 	///ditto
508 	void toString(Sink)(ref Sink sink,
509 		string delegate(Digest) digestCodeOfObj = toDelegate(&defaultDigestCodeOfObj))
510 		if(isOutputRange!(Sink, const(char)))
511 	{
512 		sink.put('[');
513 		sink.put(getDigestCode(digestCodeOfObj, digest));
514 		sink.put(']');
515 		Base64.encode(salt, sink);
516 		sink.put('$');
517 		Base64.encode(hash, sink);
518 	}
519 
520 	/++
521 	Just like toString, but instead of standard DAuth-style format, the
522 	output string is in the crypt(3)-style format.
523 
524 	The crypt(3) format does not support all hash types, and DAuth doesn't
525 	necessarily support all possible forms of crypt(3) hashes (although it
526 	does strive to support as many as possible).
527 
528 	DAuth currently supports crypt(3)-style format for MD5, SHA256 and
529 	SHA512 hashes. Other hashes (unless manually handled by a custom
530 	digestCodeOfObj) will cause an UnknownDigestException to be thrown.
531 
532 	The default digestCodeOfObj for this function is defaultDigestCryptCodeOfObj.
533 
534 	Optional_Params:
535 	digestCodeOfObj - Default value is 'toDelegate(&defaultDigestCryptCodeOfObj)'
536 
537 	See also: $(LINK https://en.wikipedia.org/wiki/Crypt_%28C%29)
538 	+/
539 	string toCryptString(string delegate(Digest) digestCodeOfObj = toDelegate(&defaultDigestCryptCodeOfObj))
540 	{
541 		Appender!string sink;
542 		toCryptString(sink, digestCodeOfObj);
543 		return sink.data;
544 	}
545 
546 	///ditto
547 	void toCryptString(Sink)(ref Sink sink,
548 		string delegate(Digest) digestCodeOfObj = toDelegate(&defaultDigestCryptCodeOfObj))
549 		if(isOutputRange!(Sink, const(char)))
550 	{
551 		sink.put('$');
552 		sink.put(getDigestCode(digestCodeOfObj, digest));
553 		sink.put('$');
554 		Base64.encode(salt, sink);
555 		sink.put('$');
556 		Base64.encode(hash, sink);
557 	}
558 }
559 
560 /++
561 Generates a salted password using any Phobos-compatible digest, default being SHA-512.
562 
563 (Note: An established "key stretching" algorithm
564 ( $(LINK http://en.wikipedia.org/wiki/Key_stretching#History) ) would be an even
565 better choice of digest since they provide better protection against
566 highly-parallelized (ex: GPU) brute-force attacks. But SHA-512, as an SHA-2
567 algorithm, is still considered cryptographically secure.)
568 
569 Supports both template-style and OO-style digests. See the documentation of
570 std.digest.digest for details.
571 
572 Salt is optional. It will be generated at random if not provided.
573 
574 Normally, the salt and password are combined as (psuedocode) 'salt~password'.
575 There is no cryptographic benefit to combining the salt and password any
576 other way. However, if you need to support an alternate method for
577 compatibility purposes, you can do so by providing a custom salter delegate.
578 See the implementation of DAuth's defaultSalter to see how to do this.
579 
580 If using an OO-style Digest, then digest MUST be non-null. Otherwise,
581 an UnknownDigestException will be thrown.
582 
583 Optional_Params:
584 salt - Default value is 'randomSalt()'
585 
586 salter - Default value is 'toDelegate(&defaultSalter!TDigest)'
587 +/
588 Hash!TDigest makeHash(TDigest = DefaultDigest)
589 	(Password password, Salt salt = randomSalt(), Salter!TDigest salter = toDelegate(&defaultSalter!TDigest))
590 	if(isDigest!TDigest)
591 {
592 	validateStrength!TDigest();
593 	TDigest digest;
594 	return makeHashImpl!TDigest(digest, password, salt, salter);
595 }
596 
597 ///ditto
598 Hash!TDigest makeHash(TDigest = DefaultDigest)(Password password, Salter!TDigest salter)
599 	if(isDigest!TDigest)
600 {
601 	validateStrength!TDigest();
602 	TDigest digest;
603 	return makeHashImpl(digest, password, randomSalt(), salter);
604 }
605 
606 ///ditto
607 Hash!Digest makeHash()(Digest digest, Password password, Salt salt = randomSalt(),
608 	Salter!Digest salter = toDelegate(&defaultSalter!Digest))
609 {
610 	enforce(digest, new UnknownDigestException("digest was null, don't know what digest to use"));
611 	validateStrength(digest);
612 	return makeHashImpl!Digest(digest, password, salt, salter);
613 }
614 
615 ///ditto
616 Hash!Digest makeHash()(Digest digest, Password password, Salter!Digest salter)
617 {
618 	enforce(digest, new UnknownDigestException("digest was null, don't know what digest to use"));
619 	validateStrength(digest);
620 	return makeHashImpl!Digest(digest, password, randomSalt(), salter);
621 }
622 
623 private Hash!TDigest makeHashImpl(TDigest)
624 	(ref TDigest digest, Password password, Salt salt, Salter!TDigest salter)
625 	if(isAnyDigest!TDigest)
626 {
627 	Hash!TDigest ret;
628 	ret.digest = digest;
629 	ret.salt   = salt;
630 
631 	static if(isDigest!TDigest) // template-based digest
632 		ret.digest.start();
633 	else
634 		ret.digest.reset(); // OO-based digest
635 
636 	salter(ret.digest, password, salt);
637 	ret.hash = ret.digest.finish();
638 
639 	return ret;
640 }
641 
642 /++
643 Parses a string that was encoded by Hash.toString.
644 
645 Only OO-style digests are used since the digest is specified in the string
646 and therefore only known at runtime.
647 
648 Throws ConvException if the string is malformed.
649 
650 To support additional digests besides the built-in (Phobos's CRC32, MD5,
651 RIPEMD160 and SHA), supply a custom delegate for digestFromDAuthCode.
652 You can defer to DAuth's defaultDigestFromCode to handle the
653 built-in digests.
654 
655 Similarly, to extend crypt(3)-style to support additional digests beyond
656 DAuth's crypt(3) support, supply a custom delegate for digestFromCryptCode.
657 The default implementation is defaultDigestFromCryptCode.
658 
659 Example:
660 -------------------
661 import std.digest.digest;
662 import dauth;
663 
664 struct BBQ42 {...}
665 static assert(isDigest!BBQ42);
666 alias BBQ42Digest = WrapperDigest!BBQ42;
667 
668 Digest customDigestFromCode(string digestCode)
669 {
670     switch(digestCode)
671     {
672     case "BBQ42": return new BBQ42Digest();
673     case "FAQ17": return new FAQ17Digest();
674     default:
675         return defaultDigestFromCode(digestCode);
676     }
677 }
678 
679 void doStuff(string hashString)
680 {
681     auto hash = parseHash(hashString, &customDigestFromCode);
682 }
683 -------------------
684 
685 Optional_Params:
686 digestFromDAuthCode - Default value is 'toDelegate(&defaultDigestFromCode)'
687 
688 digestFromCryptCode - Default value is 'toDelegate(&defaultDigestFromCryptCode)'
689 +/
690 Hash!Digest parseHash(string str,
691 	Digest delegate(string) digestFromDAuthCode = toDelegate(&defaultDigestFromCode),
692 	Digest delegate(string) digestFromCryptCode = toDelegate(&defaultDigestFromCryptCode))
693 {
694 	enforce!ConvException(!str.empty);
695 	if(str[0] == '[')
696 		return parseDAuthHash(str, digestFromDAuthCode);
697 	else if(str[0] == '$' || str.length == 13)
698 		return parseCryptHash(str, digestFromCryptCode);
699 
700 	throw new ConvException("Hash string is neither valid DAuth-style nor crypt-style");
701 }
702 
703 ///ditto
704 Hash!Digest parseDAuthHash(string str,
705 	Digest delegate(string) digestFromDAuthCode = toDelegate(&defaultDigestFromCode))
706 {
707 	// No need to mess with UTF
708 	auto bytes = cast(immutable(ubyte)[]) str;
709 
710 	// Parse '['
711 	enforce!ConvException(!bytes.empty);
712 	enforce!ConvException(bytes.front == cast(ubyte)'[');
713 	bytes.popFront();
714 
715 	// Parse digest code
716 	auto splitRBracket = bytes.findSplit([']']);
717 	enforce!ConvException( !splitRBracket[0].empty && !splitRBracket[1].empty && !splitRBracket[2].empty );
718 	auto digestCode = splitRBracket[0];
719 	bytes = splitRBracket[2];
720 
721 	// Split salt and hash
722 	auto splitDollar = bytes.findSplit(['$']);
723 	enforce!ConvException( !splitDollar[0].empty && !splitDollar[1].empty && !splitDollar[2].empty );
724 	auto salt = splitDollar[0];
725 	auto hash = splitDollar[2];
726 
727 	// Construct Hash
728 	Hash!Digest result;
729 	result.salt   = Base64.decode(salt);
730 	result.hash   = Base64.decode(hash);
731 	result.digest = digestFromDAuthCode(cast(string)digestCode);
732 
733 	return result;
734 }
735 
736 ///ditto
737 Hash!Digest parseCryptHash(string str,
738 	Digest delegate(string) digestFromCryptCode = toDelegate(&defaultDigestFromCryptCode))
739 {
740 	// No need to mess with UTF
741 	auto bytes = cast(immutable(ubyte)[]) str;
742 
743 	enforce!ConvException(!bytes.empty);
744 
745 	// Old crypt-DES style?
746 	if(bytes[0] != cast(ubyte)'$' && bytes.length == 13)
747 	{
748 		auto salt = bytes[0..2];
749 		auto hash = bytes[2..$];
750 
751 		// Construct Hash
752 		Hash!Digest result;
753 		result.salt   = salt.dup;
754 		result.hash   = hash.dup;
755 		result.digest = digestFromCryptCode(null);
756 
757 		return result;
758 	}
759 
760 	// Parse initial '$'
761 	enforce!ConvException(bytes.front == cast(ubyte)'$');
762 	bytes.popFront();
763 
764 	// Split digest code, salt and hash
765 	auto parts = bytes.splitter('$').array();
766 	enforce!ConvException(parts.length == 3);
767 	auto digestCode = parts[0];
768 	auto salt       = parts[1];
769 	auto hash       = parts[2];
770 
771 	// Construct Hash
772 	Hash!Digest result;
773 	result.salt   = Base64.decode(salt);
774 	result.hash   = Base64.decode(hash);
775 	result.digest = digestFromCryptCode(cast(string)digestCode);
776 
777 	return result;
778 }
779 
780 /++
781 Validates a password against an existing salted hash.
782 
783 If sHash is a Hash!Digest, then sHash.digest MUST be non-null. Otherwise
784 this function will have no other way to determine what digest to match
785 against, and an UnknownDigestException will be thrown.
786 
787 Optional_Params:
788 salter - Default value is 'toDelegate(&defaultSalter!TDigest)'
789 
790 digest - Default value is 'new DefaultDigestClass()'
791 +/
792 bool isSameHash(TDigest = DefaultDigest)(Password password, Hash!TDigest sHash,
793 	Salter!TDigest salter = toDelegate(&defaultSalter!TDigest))
794 	if(isDigest!TDigest)
795 {
796 	auto testHash = makeHash!TDigest(password, sHash.salt, salter);
797 	return lengthConstantEquals(testHash.hash, sHash.hash);
798 }
799 
800 ///ditto
801 bool isSameHash(TDigest = Digest)(Password password, Hash!TDigest sHash,
802 	Salter!Digest salter = toDelegate(&defaultSalter!Digest))
803 	if(is(TDigest : Digest))
804 {
805 	Hash!Digest testHash;
806 
807 	if(sHash.digest)
808 		testHash = makeHash(sHash.digest, password, sHash.salt, salter);
809 	else
810 	{
811 		static if(is(TDigest == Digest))
812 			throw new UnknownDigestException("Cannot determine digest from a Hash!Digest with a null 'digest' member.");
813 		else
814 			testHash = makeHash(new TDigest(), password, sHash.salt, salter);
815 	}
816 
817 	return lengthConstantEquals(testHash.hash, sHash.hash);
818 }
819 
820 ///ditto
821 bool isSameHash(TDigest = DefaultDigest)
822 	(Password password, DigestType!TDigest hash, Salt salt,
823 		Salter!TDigest salter = toDelegate(&defaultSalter!TDigest))
824 	if(isDigest!TDigest)
825 {
826 	auto testHash = makeHash!TDigest(password, salt, salter);
827 	return lengthConstantEquals(testHash.hash, hash);
828 }
829 
830 ///ditto
831 bool isSameHash()(Password password,
832 	ubyte[] hash, Salt salt, Digest digest = new DefaultDigestClass(),
833 	Salter!Digest salter = toDelegate(&defaultSalter!Digest))
834 {
835 	auto testHash = makeHash(digest, password, salt, salter);
836 	return lengthConstantEquals(testHash.hash, hash);
837 }
838 
839 ///ditto
840 bool isSameHash()(Password password,
841 	ubyte[] hash, Salt salt, Salter!Digest salter)
842 {
843 	auto testHash = makeHash(new DefaultDigestClass(), password, salt, salter);
844 	return lengthConstantEquals(testHash.hash, hash);
845 }
846 
847 /++
848 Alias for backwards compatibility.
849 
850 isPasswordCorrect will become deprecated in a future version. Use isSameHash instead.
851 +/
852 alias isPasswordCorrect = isSameHash;
853 
854 version(DAuth_Unittest)
855 unittest
856 {
857     import std.conv : hexString;
858 	// For validity of sanity checks, these sha/md5 and base64 strings
859 	// were NOT generated using Phobos.
860 	auto plainText1        = dupPassword("hello world");
861 	enum md5Hash1          = cast(ubyte[16])hexString!"5eb63bbbe01eeed093cb22bb8f5acdc3";
862 	enum md5Hash1Base64    = "XrY7u+Ae7tCTyyK7j1rNww==";
863 	enum sha1Hash1         = cast(ubyte[20])hexString!"2aae6c35c94fcfb415dbe95f408b9ce91ee846ed";
864 	enum sha1Hash1Base64   = "Kq5sNclPz7QV2+lfQIuc6R7oRu0=";
865 	enum sha512Hash1       = cast(ubyte[64])hexString!"309ecc489c12d6eb4cc40f50c902f2b4d0ed77ee511a7c7a9bcd3ca86d4cd86f989dd35bc5ff499670da34255b45b0cfd830e81f605dcf7dc5542e93ae9cd76f";
866 	enum sha512Hash1Base64 = "MJ7MSJwS1utMxA9QyQLytNDtd+5RGnx6m808qG1M2G+YndNbxf9JlnDaNCVbRbDP2DDoH2Bdz33FVC6TrpzXbw==";
867 
868 	auto plainText2        = dupPassword("some salt");
869 	enum md5Hash2          = cast(ubyte[16])hexString!"befbc24b5c6a74591c0d8e6397b8a398";
870 	enum md5Hash2Base64    = "vvvCS1xqdFkcDY5jl7ijmA==";
871 	enum sha1Hash2         = cast(ubyte[20])hexString!"78bc8b0e186b0aa698f12dc27736b492e4dacfc8";
872 	enum sha1Hash2Base64   = "eLyLDhhrCqaY8S3Cdza0kuTaz8g=";
873 	enum sha512Hash2       = cast(ubyte[64])hexString!"637246608760dc79f00d3ad4fd26c246bb217e10f811cdbf6fe602c3981e98b8cadacadc452808ae393ac46e8a7e967aa99711d7fd7ed6c055264787f8043693";
874 	enum sha512Hash2Base64 = "Y3JGYIdg3HnwDTrU/SbCRrshfhD4Ec2/b+YCw5gemLjK2srcRSgIrjk6xG6KfpZ6qZcR1/1+1sBVJkeH+AQ2kw==";
875 
876 	unitlog("Sanity checking unittest's data");
877 	assert(md5Of(plainText1.data) == md5Hash1);
878 	assert(md5Of(plainText2.data) == md5Hash2);
879 	assert(sha1Of(plainText1.data) == sha1Hash1);
880 	assert(sha1Of(plainText2.data) == sha1Hash2);
881 	assert(sha512Of(plainText1.data) == sha512Hash1);
882 	assert(sha512Of(plainText2.data) == sha512Hash2);
883 	assert(Base64.encode(md5Hash1) == md5Hash1Base64);
884 	assert(Base64.encode(md5Hash2) == md5Hash2Base64);
885 	assert(Base64.encode(sha1Hash1) == sha1Hash1Base64);
886 	assert(Base64.encode(sha1Hash2) == sha1Hash2Base64);
887 	assert(Base64.encode(sha512Hash1) == sha512Hash1Base64);
888 	assert(Base64.encode(sha512Hash2) == sha512Hash2Base64);
889 
890 	unitlog("Testing Hash.toString");
891 	Hash!SHA1 result1;
892 	result1.hash = cast(AnyDigestType!SHA1) sha1Hash1;
893 	result1.salt = cast(Salt)               sha1Hash2;
894 	assert( result1.toString() == text("[SHA1]", sha1Hash2Base64, "$", sha1Hash1Base64) );
895 
896 	Hash!MD5 result1_md5;
897 	result1_md5.hash = cast(AnyDigestType!MD5) md5Hash1;
898 	result1_md5.salt = cast(Salt)              md5Hash2;
899 	assert( result1_md5.toString() == text("[MD5]", md5Hash2Base64, "$", md5Hash1Base64) );
900 
901 	Hash!SHA512 result1_512;
902 	result1_512.hash = cast(AnyDigestType!SHA512) sha512Hash1;
903 	result1_512.salt = cast(Salt)                 sha512Hash2;
904 	assert( result1_512.toString() == text("[SHA512]", sha512Hash2Base64, "$", sha512Hash1Base64) );
905 
906 	unitlog("Testing Hash.toString - crypt(3)");
907 	assertThrown!UnknownDigestException( result1.toCryptString() );
908 	assert( result1_md5.toCryptString() == text("$1$", md5Hash2Base64,    "$", md5Hash1Base64) );
909 	assert( result1_512.toCryptString() == text("$6$", sha512Hash2Base64, "$", sha512Hash1Base64) );
910 
911 	unitlog("Testing makeHash([digest,] pass, salt [, salter])");
912 	void altSalter(TDigest)(ref TDigest digest, Password password, Salt salt)
913 	{
914 		// Reverse order
915 		digest.put(password.data);
916 		digest.put(cast(immutable(ubyte)[])salt);
917 	}
918 
919 	auto result2          = makeHash!SHA1(plainText1, cast(Salt)sha1Hash2[]);
920 	auto result2AltSalter = makeHash!SHA1(plainText1, cast(Salt)sha1Hash2[], &altSalter!SHA1);
921 	auto result3          = makeHash(new SHA1Digest(), plainText1, cast(Salt)sha1Hash2[]);
922 	auto result3AltSalter = makeHash(new SHA1Digest(), plainText1, cast(Salt)sha1Hash2[], &altSalter!Digest);
923 
924 	assert(result2.salt       == result3.salt);
925 	assert(result2.hash       == result3.hash);
926 	assert(result2.toString() == result3.toString());
927 	assert(result2.toString() == makeHash!SHA1(plainText1, cast(Salt)sha1Hash2[]).toString());
928 	assert(result2.salt == result1.salt);
929 
930 	assert(result2AltSalter.salt       == result3AltSalter.salt);
931 	assert(result2AltSalter.hash       == result3AltSalter.hash);
932 	assert(result2AltSalter.toString() == result3AltSalter.toString());
933 	assert(result2AltSalter.toString() == makeHash!SHA1(plainText1, cast(Salt)sha1Hash2[], &altSalter!SHA1).toString());
934 
935 	assert(result2.salt       == result2AltSalter.salt);
936 	assert(result2.hash       != result2AltSalter.hash);
937 	assert(result2.toString() != result2AltSalter.toString());
938 
939 	auto result2_512          = makeHash!SHA512(plainText1, cast(Salt)sha512Hash2[]);
940 	auto result2_512AltSalter = makeHash!SHA512(plainText1, cast(Salt)sha512Hash2[], &altSalter!SHA512);
941 	auto result3_512          = makeHash(new SHA512Digest(), plainText1, cast(Salt)sha512Hash2[]);
942 	auto result3_512AltSalter = makeHash(new SHA512Digest(), plainText1, cast(Salt)sha512Hash2[], &altSalter!Digest);
943 
944 	assert(result2_512.salt       == result3_512.salt);
945 	assert(result2_512.hash       == result3_512.hash);
946 	assert(result2_512.toString() == result3_512.toString());
947 	assert(result2_512.toString() == makeHash!SHA512(plainText1, cast(Salt)sha512Hash2[]).toString());
948 	assert(result2_512.salt == result1_512.salt);
949 
950 	assert(result2_512AltSalter.salt       == result3_512AltSalter.salt);
951 	assert(result2_512AltSalter.hash       == result3_512AltSalter.hash);
952 	assert(result2_512AltSalter.toString() == result3_512AltSalter.toString());
953 	assert(result2_512AltSalter.toString() == makeHash!SHA512(plainText1, cast(Salt)sha512Hash2[], &altSalter!SHA512).toString());
954 
955 	assert(result2_512.salt       == result2_512AltSalter.salt);
956 	assert(result2_512.hash       != result2_512AltSalter.hash);
957 	assert(result2_512.toString() != result2_512AltSalter.toString());
958 
959 	assertThrown!UnknownDigestException( makeHash(cast(SHA1Digest)null, plainText1, cast(Salt)sha1Hash2[]) );
960 	assertThrown!UnknownDigestException( makeHash(cast(Digest)null,     plainText1, cast(Salt)sha1Hash2[]) );
961 
962 	unitlog("Testing makeHash(pass)");
963 	import dauth.random : randomPassword;
964 	auto resultRand1 = makeHash!SHA1(randomPassword());
965 	auto resultRand2 = makeHash!SHA1(randomPassword());
966 
967 	assert(resultRand1.salt != result1.salt);
968 
969 	assert(resultRand1.salt != resultRand2.salt);
970 	assert(resultRand1.hash != resultRand2.hash);
971 
972 	unitlog("Testing parseHash()");
973 	auto result2Parsed = parseDAuthHash( result2_512.toString() );
974 	assert(result2_512.salt       == result2Parsed.salt);
975 	assert(result2_512.hash       == result2Parsed.hash);
976 	assert(result2_512.toString() == result2Parsed.toString());
977 
978 	assert(makeHash(result2Parsed.digest, plainText1, result2Parsed.salt) == result2Parsed);
979 	assertThrown!ConvException(parseDAuthHash( result2_512.toCryptString() ));
980 	assert(parseHash( result2_512.toString() ).salt            == parseDAuthHash( result2_512.toString() ).salt);
981 	assert(parseHash( result2_512.toString() ).hash            == parseDAuthHash( result2_512.toString() ).hash);
982 	assert(parseHash( result2_512.toString() ).toString()      == parseDAuthHash( result2_512.toString() ).toString());
983 	assert(parseHash( result2_512.toString() ).toCryptString() == parseDAuthHash( result2_512.toString() ).toCryptString());
984 
985 	unitlog("Testing parseHash() - crypt(3)");
986 	auto result2ParsedCrypt = parseCryptHash( result2_512.toCryptString() );
987 	assert(result2_512.salt       == result2ParsedCrypt.salt);
988 	assert(result2_512.hash       == result2ParsedCrypt.hash);
989 	assert(result2_512.toString() == result2ParsedCrypt.toString());
990 
991 	assert(makeHash(result2ParsedCrypt.digest, plainText1, result2ParsedCrypt.salt) == result2ParsedCrypt);
992 	assertThrown!ConvException(parseCryptHash( result2_512.toString() ));
993 	assert(parseHash( result2_512.toCryptString() ).salt            == parseCryptHash( result2_512.toCryptString() ).salt);
994 	assert(parseHash( result2_512.toCryptString() ).hash            == parseCryptHash( result2_512.toCryptString() ).hash);
995 	assert(parseHash( result2_512.toCryptString() ).toString()      == parseCryptHash( result2_512.toCryptString() ).toString());
996 	assert(parseHash( result2_512.toCryptString() ).toCryptString() == parseCryptHash( result2_512.toCryptString() ).toCryptString());
997 
998 	auto desCryptHash = "sa5JEXtYx/rm6";
999 	assertThrown!UnknownDigestException( parseHash(desCryptHash) );
1000 	assert(collectExceptionMsg( parseHash(desCryptHash) ).canFind("DES"));
1001 
1002 	unitlog("Testing isSameHash");
1003 	assert(isSameHash     (plainText1, result2));
1004 	assert(isSameHash!SHA1(plainText1, result2.hash, result2.salt));
1005 	assert(isSameHash     (plainText1, result2.hash, result2.salt, new SHA1Digest()));
1006 
1007 	assert(isSameHash!SHA1(plainText1, result2AltSalter, &altSalter!SHA1));
1008 	assert(isSameHash!SHA1(plainText1, result2AltSalter.hash, result2AltSalter.salt, &altSalter!SHA1));
1009 	assert(isSameHash     (plainText1, result2AltSalter.hash, result2AltSalter.salt, new SHA1Digest(), &altSalter!Digest));
1010 
1011 	assert(!isSameHash     (dupPassword("bad pass"), result2));
1012 	assert(!isSameHash!SHA1(dupPassword("bad pass"), result2.hash, result2.salt));
1013 	assert(!isSameHash     (dupPassword("bad pass"), result2.hash, result2.salt, new SHA1Digest()));
1014 
1015 	assert(!isSameHash!SHA1(dupPassword("bad pass"), result2AltSalter, &altSalter!SHA1));
1016 	assert(!isSameHash!SHA1(dupPassword("bad pass"), result2AltSalter.hash, result2AltSalter.salt, &altSalter!SHA1));
1017 	assert(!isSameHash     (dupPassword("bad pass"), result2AltSalter.hash, result2AltSalter.salt, new SHA1Digest(), &altSalter!Digest));
1018 
1019 	Hash!SHA1Digest ooHashSHA1Digest;
1020 	ooHashSHA1Digest.salt = result2.salt;
1021 	ooHashSHA1Digest.hash = result2.hash;
1022 	ooHashSHA1Digest.digest = new SHA1Digest();
1023 	assert( isSameHash(plainText1, ooHashSHA1Digest) );
1024 	ooHashSHA1Digest.digest = null;
1025 	assert( isSameHash(plainText1, ooHashSHA1Digest) );
1026 
1027 	Hash!Digest ooHashDigest;
1028 	ooHashDigest.salt = result2.salt;
1029 	ooHashDigest.hash = result2.hash;
1030 	ooHashDigest.digest = new SHA1Digest();
1031 	assert( isSameHash(plainText1, ooHashDigest) );
1032 	ooHashDigest.digest = null;
1033 	assertThrown!UnknownDigestException( isSameHash(plainText1, ooHashDigest) );
1034 
1035 	assert( isSameHash(plainText1, parseHash(result2.toString())) );
1036 
1037 	auto wrongSalt = result2;
1038 	wrongSalt.salt = wrongSalt.salt[4..$-1];
1039 
1040 	assert(!isSameHash     (plainText1, wrongSalt));
1041 	assert(!isSameHash!SHA1(plainText1, wrongSalt.hash, wrongSalt.salt));
1042 	assert(!isSameHash     (plainText1, wrongSalt.hash, wrongSalt.salt, new SHA1Digest()));
1043 
1044 	Hash!MD5 wrongDigest;
1045 	wrongDigest.salt = result2.salt;
1046 	wrongDigest.hash = cast(ubyte[16])result2.hash[0..16];
1047 
1048 	assert(!isSameHash    (plainText1, wrongDigest));
1049 	assert(!isSameHash!MD5(plainText1, wrongDigest.hash, wrongDigest.salt));
1050 	assert(!isSameHash    (plainText1, wrongDigest.hash, wrongDigest.salt, new MD5Digest()));
1051 }
1052 
1053 /++
1054 Compare two arrays in "length-constant" time. This thwarts timing-based
1055 attacks by guaranteeing all comparisons (of a given length) take the same
1056 amount of time.
1057 
1058 See the section "Why does the hashing code on this page compare the hashes in
1059 "length-constant" time?" at:
1060     $(LINK https://crackstation.net/hashing-security.htm)
1061 +/
1062 bool lengthConstantEquals(ubyte[] a, ubyte[] b)
1063 {
1064 	auto diff = a.length ^ b.length;
1065 	for(int i = 0; i < a.length && i < b.length; i++)
1066 		diff |= a[i] ^ b[i];
1067 
1068 	return diff == 0;
1069 }
1070 
1071 // Borrowed from Phobos (TemplateArgsOf only exists in DMD 2.066 and up).
1072 package template DAuth_TemplateArgsOf(alias T : Base!Args, alias Base, Args...)
1073 {
1074 	alias DAuth_TemplateArgsOf = Args;
1075 }
1076 package template DAuth_TemplateArgsOf(T : Base!Args, alias Base, Args...)
1077 {
1078 	alias DAuth_TemplateArgsOf = Args;
1079 }
1080 static assert(is( DAuth_TemplateArgsOf!( Hash!SHA1   )[0] == SHA1   ));
1081 static assert(is( DAuth_TemplateArgsOf!( Hash!Digest )[0] == Digest ));
1082 
1083 private struct dummy(T) {}
1084 static if(!is(std.traits.TemplateArgsOf!(dummy!int)))
1085 	private alias TemplateArgsOf = DAuth_TemplateArgsOf;