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 ));