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