1 /++ 2 DAuth - Salted Hashed Password Library for D 3 Hash_DRBG Cryptographic Random Number Generator 4 +/ 5 6 module dauth.hashdrbg; 7 8 import std.conv; 9 import std.exception; 10 import std.random; 11 import std.range; 12 import std.stdio; 13 import std.traits; 14 import std.typecons; 15 import std.typetuple; 16 17 import dauth.core; 18 19 import std.digest.sha; 20 21 // TemplateArgsOf only exists in Phobos of DMD 2.066 and up 22 private struct dummy(T) {} 23 static if(!is(std.traits.TemplateArgsOf!(dummy!int))) 24 private alias TemplateArgsOf = DAuth_TemplateArgsOf; 25 26 version(Windows) 27 { 28 import std.c.windows.windows; 29 import core.runtime; 30 } 31 32 /++ 33 Check if T is a stream-like random number generator. 34 35 Because std.stream is pending a full replacement, be aware that 36 stream-like random number generators currently use a temporary 37 design that may change once a new std.stream is available. 38 +/ 39 enum isRandomStream(T) = 40 is(typeof({ 41 static assert(T.isUniformRandomStream); 42 T t; 43 ubyte[] buf; 44 t.read(buf); 45 t.read(buf, No.PredictionResistance); 46 })); 47 48 static assert(isRandomStream!(SystemEntropyStream!())); 49 static assert(isRandomStream!(HashDRBGStream!())); 50 static assert(!isRandomStream!(SystemEntropy!uint)); 51 static assert(!isRandomStream!(HashDRBG!uint)); 52 static assert(!isRandomStream!uint); 53 static assert(!isRandomStream!File); 54 55 /++ 56 The underlying stream-like interface for SystemEntropy. 57 58 On Windows, pathToRandom and pathToStrongRandom must be null because Windows 59 uses a system call, not a file path, to retreive system entropy. 60 61 On Posix, pathToRandom must NOT be null. If pathToStrongRandom is null, 62 then pathToStrongRandom is assumed to be pathToRandom. 63 64 Because std.stream is pending a full replacement, be aware that 65 stream-like random number generators currently use a temporary 66 design that may change once a new std.stream is available. 67 68 Declaration: 69 ----------------------- 70 struct SystemEntropyStream(string pathToRandom = defaultPathToRandom, 71 string pathToStrongRandom = defaultPathToStrongRandom) {...} 72 ----------------------- 73 +/ 74 struct SystemEntropyStream(string pathToRandom = defaultPathToRandom, 75 string pathToStrongRandom = defaultPathToStrongRandom) 76 { 77 enum isUniformRandomStream = true; /// Mark this as a Rng Stream 78 79 version(Windows) 80 { 81 import std.c.windows.windows; 82 import core.runtime; 83 84 static assert(pathToRandom is null, "On Windows, SystemEntropyStream's pathToRandom must be null"); 85 static assert(pathToStrongRandom is null, "On Windows, SystemEntropyStream's pathToStrongRandom must be null"); 86 87 private static HMODULE _advapi32; 88 private static extern(Windows) BOOL function(void*, uint) _RtlGenRandom; 89 } 90 else version(Posix) 91 { 92 import std.stdio : File, _IONBF; 93 94 static assert(pathToRandom !is null, "On Posix, SystemEntropyStream's pathToRandom must NOT be null"); 95 96 private static File devRandom; 97 private static File devStrongRandom; 98 } 99 else 100 static assert(0); 101 102 /++ 103 Fills the buffer with entropy from the system-specific entropy generator. 104 Automatically opens SystemEntropyStream if it's closed. 105 106 If predictionResistance is Yes.PredictionResistance, then this will read 107 from a secondary source (if available), such as /dev/random instead of 108 /dev/urandom, which may block for a noticable amount of time to ensure 109 a minimum amount of estimated entropy is available. If no secondary 110 source is available, then predictionResistance is ignored. 111 112 Optional_Params: 113 predictionResistance - Default value is 'No.PredictionResistance' 114 +/ 115 static void read(ubyte[] buf, Flag!"PredictionResistance" predictionResistance = No.PredictionResistance) 116 { 117 open(); 118 119 version(Windows) 120 { 121 enforce(buf.length < uint.max, "Cannot read more than uint.max bytes from RtlGenRandom"); 122 _RtlGenRandom(buf.ptr, cast(uint)buf.length); 123 } 124 else version(Posix) 125 { 126 if(predictionResistance == Yes.PredictionResistance && pathToStrongRandom) 127 devStrongRandom.rawRead(buf); 128 else 129 devRandom.rawRead(buf); 130 } 131 else 132 static assert(0); 133 } 134 135 /++ 136 Establishes a handle/connection to the system-specific entropy generator. 137 Does nothing if already open. 138 +/ 139 static void open() 140 { 141 if(isOpen) 142 return; 143 144 version(Windows) 145 { 146 // Reference: http://blogs.msdn.com/b/michael_howard/archive/2005/01/14/353379.aspx 147 _advapi32 = Runtime.loadLibrary("ADVAPI32.DLL"); 148 _RtlGenRandom = cast(typeof(_RtlGenRandom))_advapi32.GetProcAddress("SystemFunction036"); 149 enforce(_RtlGenRandom); 150 } 151 else version(Posix) 152 { 153 static void openFile(ref File f, string path) 154 { 155 if(!f.isOpen) 156 { 157 f = File(path); 158 159 // Disable buffering for security, and to avoid consuming 160 // more system entropy than necessary. 161 f.setvbuf(null, _IONBF); 162 } 163 } 164 165 openFile(devRandom, pathToRandom); 166 if(pathToStrongRandom) 167 openFile(devStrongRandom, pathToStrongRandom); 168 } 169 else 170 static assert(0); 171 } 172 173 174 /// Manually release the handle/connection to the system-specific entropy generator. 175 static void close() 176 { 177 version(Windows) 178 { 179 if(_advapi32) 180 { 181 Runtime.unloadLibrary(_advapi32); 182 _advapi32 = null; 183 _RtlGenRandom = null; 184 } 185 } 186 else version(Posix) 187 { 188 if(devRandom.isOpen) 189 devRandom.close(); 190 if(devStrongRandom.isOpen) 191 devStrongRandom.close(); 192 } 193 else 194 static assert(0); 195 } 196 197 /++ 198 Check whether SystemEntropyStream is currently connected to with the 199 system-specific entropy generator. 200 +/ 201 static @property bool isOpen() 202 { 203 version(Windows) 204 return _advapi32 && _RtlGenRandom; 205 else version(Posix) 206 return devRandom.isOpen && (!pathToStrongRandom || devStrongRandom.isOpen); 207 else 208 static assert(0); 209 } 210 211 /// Automatically close upon module destruction. 212 static ~this() 213 { 214 close(); 215 } 216 } 217 218 /++ 219 Reads random entropy from a system-specific cryptographic random number 220 generator. On Windows, this loads ADVAPI32.DLL and uses RtlGenRandom. 221 On Posix, this reads from a file (by default, "/dev/urandom" normally and 222 "/dev/random" when Yes.PredictionResistance is requested). The speed 223 and cryptographic security of this is dependent on your operating system. 224 225 In most cases, this should not be used directly. It quickly consumes 226 available system entropy, which can decrease the cryptographic RNG 227 effectiveness across the whole computer and, on Linux, can cause reads from 228 "/dev/random" to stall for noticably long periods of time. Instead, 229 this is best used for seeding cryptographic psuedo-random number generators, 230 such as HashDRBG. 231 232 Optionally, you can use open() and close() to control the lifetime of 233 SystemEntropyStream's system handles (ie, loading/uloading ADVAPI32.DLL and 234 opening/closing pathToRandom). But this is not normally necessary since 235 SystemEntropyStream automatically opens them upon reading and closes upon 236 module destruction. 237 238 On Windows, pathToRandom and pathToStrongRandom must be null because Windows 239 uses a system call, not a file path, to retreive system entropy. 240 241 On Posix, pathToRandom must NOT be null. If pathToStrongRandom is null, 242 then pathToStrongRandom is assumed to be pathToRandom. 243 244 This is a convenience alias for WrappedStreamRNG!(SystemEntropyStream, Elem). 245 246 Note that to conform to the expected InputRange interface, this must keep a 247 copy of the last generated value in memory. For security purposes, it may 248 occasionally be appropriate to make an extra popFront() call before and/or 249 after retreiving entropy values. This may decrease the chance of using 250 a compromized entropy value in the event of a memory-sniffing attacker. 251 252 Optional_Params: 253 pathToRandom - Default value is 'defaultPathToRandom' 254 255 pathToStrongRandom - Default value is 'defaultPathToStrongRandom' 256 +/ 257 alias SystemEntropy(Elem, string pathToRandom = defaultPathToRandom, 258 string pathToStrongRandom = defaultPathToStrongRandom) = 259 WrappedStreamRNG!(SystemEntropyStream!(pathToRandom, pathToStrongRandom), Elem); 260 261 version (StdDdoc) 262 { 263 /++ 264 The path to the default OS-provided cryptographic entropy generator. 265 This should not be a blocking generator. 266 267 On Posix, this is "/dev/urandom". On Windows is empty string, because 268 Windows uses a system call, not a file path, to retreive system entropy. 269 +/ 270 enum string defaultPathToRandom = null; 271 272 /++ 273 The path to an OS-provided cryptographic entropy generator to be used 274 when Yes.PredictionResistance is requested. This should be at least as 275 strong as defaultPathToRandom. But unlike defaultPathToRandom, this may 276 be a generator that blocks when system entropy is low. 277 278 On Posix, this is "/dev/random". On Windows is empty string, because 279 Windows uses a system call, not a file path, to retreive system entropy. 280 +/ 281 enum string defaultPathToStrongRandom = null; 282 } 283 else version (Windows) 284 { 285 enum string defaultPathToRandom = null; 286 enum string defaultPathToStrongRandom = null; 287 } 288 else version (Posix) 289 { 290 enum defaultPathToRandom = "/dev/urandom"; 291 enum defaultPathToStrongRandom = "/dev/random"; 292 } 293 else 294 static assert(0); 295 296 static assert(isUniformRNG!(SystemEntropy!(ubyte[1]), ubyte[1])); 297 static assert(isUniformRNG!(SystemEntropy!(ubyte[5]), ubyte[5])); 298 static assert(isUniformRNG!(SystemEntropy!ubyte, ubyte )); 299 static assert(isUniformRNG!(SystemEntropy!ushort, ushort )); 300 static assert(isUniformRNG!(SystemEntropy!uint, uint )); 301 static assert(isUniformRNG!(SystemEntropy!ulong, ulong )); 302 303 /++ 304 The underlying stream-like interface for SystemEntropy. 305 306 TSHA: Any SHA-1 or SHA-2 digest type. Default is SHA512. 307 308 custom: The Hash_DRBG algorithm's personalization string. You 309 can optionally set this to any specific value of your own choosing for 310 improved security. 311 312 EntropyStream: The source of entropy from which to draw. 313 The default is SystemEntropyStream!(), but can be overridden. If you provide 314 your own, then it's your responsibility to ensure your entropy source is 315 non-deterministic. 316 317 Because std.stream is pending a full replacement, be aware that 318 stream-like random number generators currently use a temporary 319 design that may change once a new std.stream is available. 320 321 Declaration: 322 ----------------------- 323 struct HashDRBGStream(TSHA = SHA512, string custom = "D Crypto RNG", EntropyStream = SystemEntropyStream!()) 324 if(isInstanceOf!(SHA, TSHA)) 325 {...} 326 ----------------------- 327 +/ 328 struct HashDRBGStream(TSHA = SHA512, string custom = "D Crypto RNG", EntropyStream = SystemEntropyStream!()) 329 if(isInstanceOf!(SHA, TSHA)) 330 { 331 enum isUniformRandomStream = true; /// Mark this as a Rng Stream 332 333 // In bits. This is the same as the SHA's digestSize 334 private enum outputSizeBits = TemplateArgsOf!(TSHA)[1]; 335 336 static if (outputSizeBits < 384) 337 private enum seedSizeBytes = 440/8; // In bytes 338 else 339 private enum seedSizeBytes = 888/8; // In bytes 340 341 // Securty strength 256 bits. Less could provide insufficitent security, 342 // but more would consume more of the system's entropy for no benefit. 343 // See NIST's [SP800-90A] and [SP800-57] for details on this value. 344 private enum entropySizeBytes = 256/8; 345 346 // This must be at least entropySizeBytes/2 347 private enum nonceSizeBytes = entropySizeBytes/2; 348 349 // value[1..$] is Hash_DRBG's secret working state value V 350 // value[0] is a scratchpad to avoid unnecessary copying/concating of V 351 private static ubyte[seedSizeBytes+1] value; 352 353 private static ubyte[seedSizeBytes] constant; // Hash_DRBG's secret working state value C 354 private static uint numGenerated; // Number of values generated with the current seed 355 356 // Maximum number of values generated before automatically reseeding with fresh entropy. 357 // The algorithm's spec permits this to be anything less than or equal to 2^48, 358 // but we should take care not to overflow our actual countner. 359 private enum int maxGenerated = 0x0FFF_FFFF; 360 361 /++ 362 If your security needs are high enough that you'd rather risk blocking 363 for an arbitrarily-long period of time while sufficient system entropy 364 builds, than risk generating values from potentially insufficient entropy 365 (ex, if you'd rather reseed from Linux's /dev/random than /dev/urandom), 366 then set this to Yes.PredictionResistance. The next time a value is 367 generated, the internal state will first be replenished with additional 368 entropy, potentially from a blocking source. 369 370 After the next value is generated, this will automatically reset back 371 to No.PredictionResistance to avoid needlessly consuming the system's 372 available entropy. Note that forcefully setting this to Yes.PredictionResistance 373 before each and every value generated is NOT cryptographically necessary, 374 can quickly starve the system of entropy, and should not be done. 375 376 Default is No.PredictionResistance. 377 378 This setting is for changing read()'s default bahavior. Individual calls 379 to read() can manually override this per call. 380 +/ 381 Flag!"PredictionResistance" predictionResistance = No.PredictionResistance; 382 383 /++ 384 Further improve security by setting Hash_DRBG's optional "additional input" 385 for each call to read(). This can be set to a new value before each read() 386 call for maximum effect. 387 388 This setting is for changing read()'s default bahavior. Individual calls 389 to read() can manually override this per call. 390 +/ 391 ubyte[] extraInput = null; 392 393 private static bool inited = false; 394 private void init() 395 { 396 if(inited) 397 return; 398 399 // seedMaterial = entropy ~ nonce ~ custom; 400 ubyte[entropySizeBytes + nonceSizeBytes + custom.length] seedMaterial = void; 401 EntropyStream.read( seedMaterial[0 .. $-custom.length], predictionResistance ); 402 seedMaterial[$-custom.length .. $] = cast(ubyte[])custom; 403 404 // Generate seed for V 405 hashDerivation(seedMaterial, null, value[1..$]); 406 407 // Generate constant 408 value[0] = 0x00; 409 hashDerivation(value, null, constant); 410 411 numGenerated = 0; 412 inited = true; 413 } 414 415 private void reseed(ubyte[] extraInput=null) 416 { 417 // seedMaterial = 0x01 ~ V ~ entropy; 418 ubyte[value.sizeof + entropySizeBytes] seedMaterial = void; 419 seedMaterial[0] = 0x01; 420 seedMaterial[1 .. $-entropySizeBytes] = value[1..$]; 421 EntropyStream.read( seedMaterial[$-entropySizeBytes .. $], predictionResistance ); 422 423 // Generate seed for V 424 hashDerivation(seedMaterial, extraInput, value[1..$]); 425 426 // Generate constant 427 value[0] = 0x00; 428 hashDerivation(value, null, constant); 429 430 numGenerated = 0; 431 } 432 433 /++ 434 Fills the buffer with random values using the Hash_DRBG algorithm. 435 436 overridePredictionResistance: 437 Override this.predictionResistance setting for this call only. 438 439 overrideExtraInput: 440 Override this.extraInput setting for this call only. 441 +/ 442 void read(ubyte[] buf) 443 { 444 read(buf, predictionResistance, extraInput); 445 } 446 447 ///ditto 448 void read(ubyte[] buf, ubyte[] overrideExtraInput) 449 { 450 read(buf, predictionResistance, overrideExtraInput); 451 } 452 453 ///ditto 454 void read(ubyte[] buf, Flag!"PredictionResistance" overridePredictionResistance) 455 { 456 read(buf, overridePredictionResistance, extraInput); 457 } 458 459 ///ditto 460 void read(ubyte[] buf, 461 Flag!"PredictionResistance" overridePredictionResistance, 462 ubyte[] overrideExtraInput) 463 { 464 init(); 465 466 if(numGenerated >= maxGenerated || overridePredictionResistance == Yes.PredictionResistance) 467 reseed(overrideExtraInput); 468 469 predictionResistance = No.PredictionResistance; 470 471 if(overrideExtraInput) 472 { 473 value[0] = 0x02; 474 475 TSHA sha; 476 sha.put(value); 477 sha.put(overrideExtraInput); 478 ubyte[seedSizeBytes] tempHash; 479 tempHash[0..outputSizeBits/8] = sha.finish(); 480 addHash!seedSizeBytes(value[1..$], tempHash, value[1..$]); 481 } 482 483 ubyte[seedSizeBytes] workingData = value[1..$]; 484 if(buf.length > 0) 485 while(true) 486 { 487 // Fill the front of buf with up to seedSizeBytes of random data 488 ubyte[outputSizeBits/8] currHash = digest!TSHA(workingData); 489 auto length = buf.length < currHash.length? buf.length : currHash.length; 490 buf[0..length] = currHash[0..length]; 491 buf = buf[length..$]; 492 493 // Buffer filled? 494 if(buf.length == 0) 495 break; 496 497 incrementHash(workingData); 498 } 499 500 // Update V 501 value[0] = 0x03; 502 ubyte[seedSizeBytes] hashSum = void; 503 hashSum[0 .. outputSizeBits/8] = digest!TSHA(value); 504 hashSum[outputSizeBits/8 .. $] = 0; 505 addHash!seedSizeBytes(hashSum, value[1..$], hashSum); 506 addHash!seedSizeBytes(hashSum, constant, hashSum); 507 addHash!seedSizeBytes(hashSum, numGenerated+1, value[1..$]); 508 509 numGenerated++; 510 } 511 512 private static void hashDerivation(ubyte[] input, ubyte[] extraInput, ubyte[] buf) 513 { 514 ubyte counter = 1; 515 ulong originalBufLength = buf.length; 516 while(buf.length) 517 { 518 // Generate hashed data 519 TSHA sha; 520 sha.put(counter); 521 sha.put(*(cast(ubyte[8]*) &originalBufLength)); 522 sha.put(input); 523 if(extraInput) 524 sha.put(extraInput); 525 auto currHash = sha.finish(); 526 527 // Fill the front of buf with the hashed data 528 auto length = buf.length < currHash.length? buf.length : currHash.length; 529 buf[0..length] = currHash[0..length]; 530 buf = buf[length..$]; 531 532 counter++; 533 } 534 } 535 536 private static void incrementHash(int numBytes)(ref ubyte[numBytes] arr) 537 { 538 // Endianness (small, big or even weird mixes) doesn't matter since hashes 539 // don't have a particularly meaningful least/most significant bit. As 540 // long as we're consistent across the RNG instance's lifetime, we're good. 541 542 foreach(ref b; arr) 543 { 544 b++; 545 if(b != 0) 546 break; 547 } 548 } 549 550 private static void addHash(int numBytes)(ubyte[numBytes] arr1, 551 ubyte[numBytes] arr2, ubyte[] result) 552 { 553 // As with incrementHash, endianness doesn't matter here. 554 555 enforce(arr1.length == arr2.length); 556 enforce(arr1.length == result.length); 557 uint carry = 0; 558 foreach(i; 0..arr1.length) 559 { 560 auto sum = arr1[i] + arr2[i] + carry; 561 result[i] = sum & 0xFF; 562 carry = sum >> 8; 563 } 564 } 565 566 private static void addHash(int numBytes)(ubyte[numBytes] arr, uint value, 567 ubyte[] result) 568 { 569 // As with incrementHash, endianness doesn't matter here. 570 571 enforce(arr.length == result.length); 572 uint carry = value; 573 foreach(i; 0..arr.length) 574 { 575 uint sum = arr[i] + carry; 576 result[i] = sum & 0xFF; 577 carry = sum >> 8; 578 } 579 } 580 } 581 582 /++ 583 Cryptographic random number generator Hash_DRBG, as defined in 584 NIST's $(LINK2 http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf, SP800-90A). 585 586 The Hash_DRBG algorithm ("Hash - Deterministic Random Bit Generator") uses 587 SHA-2 (or optionally SHA-1) to stretch the useful life of a non-deterministic 588 $(LINK2 https://en.wikipedia.org/wiki/Entropy_%28information_theory%29, entropy) 589 source for security and cryptographic purposes, such as 590 $(LINK2 http://en.wikipedia.org/wiki/One-time_password, single-use tokens), 591 $(LINK2 http://en.wikipedia.org/wiki/Cryptographic_nonce, nonces) 592 or $(LINK2 http://en.wikipedia.org/wiki/Salt_%28cryptography%29, password salts). 593 594 While technically deterministic, Hash_DRBG is not intended for deterministic, 595 repeatable uses of psuedo-random number generation (such as generating randomized 596 interactive worlds with minimal-storage requirements - for which something like 597 Mt19937 would be better suited). For the sake of security, the algorithm is 598 intentionally defined to not support direct seeding and to automatically 599 accumulate (but never discard) entropy from not only a pre-determined source 600 of unpredictable entropy, but also from actual usage patterns. In that 601 spirit, this implementation is non-seedable, non-ForwardRange (only InputRange), 602 and all instances share a static state (albeit per thread, per EntropyStream type). 603 604 Mainly through the underlying HashDRBGStream (accessed via the $(D stream) member), 605 this supports the optional features of the Hash_DRBG algorithm. Specifically, 606 prediction resistance via forced reseeding, providing additional input for 607 each value generated, and custom personalization strings. 608 609 TSHA: Any SHA-1 or SHA-2 digest type. Default is SHA512. 610 611 custom: The Hash_DRBG algorithm's personalization string. You 612 can optionally set this to any specific value of your own choosing for 613 extra security. 614 615 EntropyStream: The source of entropy from which to draw. 616 The default is SystemEntropyStream!(), but can be overridden. If you provide 617 your own, then it's your responsibility to ensure your entropy source is 618 non-deterministic. 619 620 This is a convenience alias for WrappedStreamRNG!(HashDRBGStream, Elem). 621 622 Note that to conform to the expected InputRange interface, this must keep a 623 copy of the last generated value in memory. For security purposes, it may 624 occasionally be appropriate to make an extra popFront() call before and/or 625 after retrieving entropy values. This may decrease the chance of using 626 a compromised entropy value in the event of a memory-sniffing attacker. 627 628 Declaration: 629 ----------------------- 630 struct HashDRBGStream(TSHA = SHA512, string custom = "D Crypto RNG", EntropyStream = SystemEntropyStream!()) 631 if(isInstanceOf!(SHA, TSHA)) 632 {...} 633 ----------------------- 634 +/ 635 template HashDRBG(Elem, TSHA = SHA512, string custom = "D Crypto RNG", EntropyStream = SystemEntropyStream!()) 636 if(isInstanceOf!(SHA, TSHA)) 637 { 638 alias HashDRBG = WrappedStreamRNG!(HashDRBGStream!(TSHA, custom, EntropyStream), Elem); 639 } 640 641 static assert(isUniformRNG!(HashDRBG!(ubyte[1]), ubyte[1])); 642 static assert(isUniformRNG!(HashDRBG!(ubyte[5]), ubyte[5])); 643 static assert(isUniformRNG!(HashDRBG!ubyte, ubyte )); 644 static assert(isUniformRNG!(HashDRBG!ushort, ushort )); 645 static assert(isUniformRNG!(HashDRBG!uint, uint )); 646 static assert(isUniformRNG!(HashDRBG!ulong, ulong )); 647 static assert(isUniformRNG!(HashDRBG!(uint), uint)); 648 static assert(isUniformRNG!(HashDRBG!(uint, SHA256), uint)); 649 static assert(isUniformRNG!(HashDRBG!(uint, SHA256, "custom"), uint)); 650 static assert(isUniformRNG!(HashDRBG!(uint, SHA256, "custom", SystemEntropyStream!()), uint)); 651 652 version(DAuth_Unittest) 653 unittest 654 { 655 unitlog("Testing HashDRBGStream.incrementHash"); 656 657 HashDRBGStream!SHA1 rand; 658 ubyte[5] val = [0xFF, 0xFF, 0b0000_1011, 0x00, 0x00]; 659 ubyte[5] expected = [0x00, 0x00, 0b0000_1100, 0x00, 0x00]; 660 661 assert(val != expected); 662 rand.incrementHash(val); 663 assert(val == expected); 664 } 665 666 version(DAuth_Unittest) 667 unittest 668 { 669 unitlog("Testing HashDRBGStream.addHash(arr,arr,arr)"); 670 671 HashDRBGStream!SHA1 rand; 672 ubyte[5] val1 = [0xCC, 0x05, 0xFE, 0x01, 0x00]; 673 ubyte[5] val2 = [0x33, 0x02, 0x9E, 0x00, 0x00]; 674 ubyte[5] expected = [0xFF, 0x07, 0x9C, 0x02, 0x00]; 675 ubyte[5] result; 676 677 assert(result != expected); 678 rand.addHash(val1, val2, result); 679 assert(result == expected); 680 } 681 682 version(DAuth_Unittest) 683 unittest 684 { 685 unitlog("Testing HashDRBGStream.addHash(arr,int,arr)"); 686 687 HashDRBGStream!SHA1 rand; 688 ubyte[5] val1 = [0xCC, 0x05, 0xFE, 0x01, 0x00]; 689 uint val2 = 0x009E_0233; 690 ubyte[5] expected = [0xFF, 0x07, 0x9C, 0x02, 0x00]; 691 ubyte[5] result; 692 693 assert(result != expected); 694 rand.addHash(val1, val2, result); 695 assert(result == expected); 696 } 697 698 /++ 699 Takes a RandomStream (ex: SystemEntropyStream or HashDRBGStream) and 700 wraps it into a UniformRNG InputRange. 701 702 Note that to conform to the expected InputRange interface, this must keep a 703 copy of the last generated value in memory. If using this for security-related 704 purposes, it may occasionally be appropriate to make an extra popFront() 705 call before and/or after retreiving entropy values. This may decrease the 706 chance of using a compromized entropy value in the event of a 707 memory-sniffing attacker. 708 709 Declaration: 710 ----------------------- 711 struct WrappedStreamRNG(RandomStream, StaticUByteArr) 712 if(isRandomStream!RandomStream && isStaticArray!StaticUByteArr && is(ElementType!StaticUByteArr==ubyte)) 713 {...} 714 ----------------------- 715 +/ 716 struct WrappedStreamRNG(RandomStream, StaticUByteArr) 717 if(isRandomStream!RandomStream && isStaticArray!StaticUByteArr && is(ElementType!StaticUByteArr==ubyte)) 718 { 719 enum isUniformRandom = true; /// Mark this as a Rng 720 721 private StaticUByteArr _front; 722 private bool inited = false; 723 724 /// Access to underlying RandomStream so RNG-specific functionality can be accessed. 725 RandomStream stream; 726 727 /// Implements an InputRange 728 @property StaticUByteArr front() 729 { 730 if(!inited) 731 { 732 popFront(); 733 inited = true; 734 } 735 736 return _front; 737 } 738 739 ///ditto 740 void popFront() 741 { 742 stream.read(_front); 743 } 744 745 /// Infinite range. Never empty. 746 enum empty = false; 747 748 /// Smallest generated value. 749 enum min = StaticUByteArr.init; 750 751 /// Largest generated value. 752 static @property StaticUByteArr max() 753 { 754 StaticUByteArr val = void; 755 val[] = 0xFF; 756 return val; 757 } 758 } 759 760 ///ditto 761 struct WrappedStreamRNG(RandomStream, UIntType) 762 if(isRandomStream!RandomStream && isUnsigned!UIntType) 763 { 764 private WrappedStreamRNG!(RandomStream, ubyte[UIntType.sizeof]) bytesImpl; 765 766 enum isUniformRandom = true; /// Mark this as a Rng 767 768 private UIntType _front; 769 private bool inited = false; 770 771 /// Implements an InputRange 772 @property UIntType front() 773 { 774 auto val = bytesImpl.front; 775 return *(cast(UIntType*) &val); 776 } 777 778 ///ditto 779 void popFront() 780 { 781 bytesImpl.popFront(); 782 } 783 784 enum empty = false; /// Infinite range. Never empty. 785 enum min = UIntType.min; /// Smallest generated value. 786 enum max = UIntType.max; /// Largest generated value. 787 } 788 789 version(DAuth_Unittest) 790 unittest 791 { 792 alias RandStreamTypes = TypeTuple!( 793 SystemEntropyStream!(), 794 HashDRBGStream!SHA1, 795 HashDRBGStream!SHA224, 796 HashDRBGStream!SHA256, 797 HashDRBGStream!SHA384, 798 HashDRBGStream!SHA512, 799 HashDRBGStream!SHA512_224, 800 HashDRBGStream!SHA512_256, 801 HashDRBGStream!(SHA512, "other custom str"), 802 ); 803 804 unitlog("Testing SystemEntropyStream/HashDRBGStream"); 805 foreach(RandStream; RandStreamTypes) 806 { 807 //unitlog("Testing RandStream: "~RandStream.stringof); 808 809 RandStream rand; 810 ubyte[] values1; 811 ubyte[] values2; 812 values1.length = 10; 813 values2.length = 10; 814 815 rand.read(values1); 816 assert(values1 != typeof(values1).init); 817 assert(values1[0..4] != values1[4..8]); 818 rand.read(values2); 819 assert(values1 != values2); 820 821 auto randCopy = rand; 822 rand.read(values1); 823 randCopy.read(values2); 824 assert(values1 != values2); 825 826 static if(!is(RandStream == SystemEntropyStream!())) 827 { 828 values2[] = ubyte.init; 829 830 values1[] = ubyte.init; 831 rand.read(values1, Yes.PredictionResistance); 832 assert(values1 != values2); 833 834 values1[] = ubyte.init; 835 rand.read(values1, cast(ubyte[])"additional input"); 836 assert(values1 != values2); 837 838 values1[] = ubyte.init; 839 rand.read(values1, Yes.PredictionResistance, cast(ubyte[])"additional input"); 840 assert(values1 != values2); 841 } 842 } 843 } 844 845 version(DAuth_Unittest) 846 unittest 847 { 848 foreach(Rand; TypeTuple!(SystemEntropy, HashDRBG)) 849 { 850 unitlog("Testing Rand's min/max: "~Rand.stringof); 851 852 assert(Rand!(ubyte[1]).min == [0x00]); 853 assert(Rand!(ubyte[1]).max == [0xFF]); 854 assert(Rand!(ubyte[5]).min == [0x00,0x00,0x00,0x00,0x00]); 855 assert(Rand!(ubyte[5]).max == [0xFF,0xFF,0xFF,0xFF,0xFF]); 856 assert(Rand!(ubyte ).min == ubyte .min); 857 assert(Rand!(ubyte ).max == ubyte .max); 858 assert(Rand!(ushort ).min == ushort.min); 859 assert(Rand!(ushort ).max == ushort.max); 860 assert(Rand!(uint ).min == uint .min); 861 assert(Rand!(uint ).max == uint .max); 862 assert(Rand!(ulong ).min == ulong .min); 863 assert(Rand!(ulong ).max == ulong .max); 864 } 865 } 866 867 version(DAuth_Unittest) 868 unittest 869 { 870 // Don't test ubyte or ushort versions here because legitimate repeated 871 // values are too likely and would trigger a failure and unfounded worry. 872 873 alias RandTypes = TypeTuple!( 874 SystemEntropy!ulong, 875 SystemEntropy!uint, 876 SystemEntropy!(ubyte[5]), 877 SystemEntropy!(ubyte[1024]), 878 HashDRBG!(ulong, SHA1), 879 HashDRBG!(ulong, SHA224), 880 HashDRBG!(ulong, SHA256), 881 HashDRBG!(ulong, SHA384), 882 HashDRBG!(ulong, SHA512), 883 HashDRBG!(ulong, SHA512_224), 884 HashDRBG!(ulong, SHA512_256), 885 HashDRBG!(ulong, SHA512, "other custom str"), 886 HashDRBG!(uint, SHA512), 887 HashDRBG!(ubyte[5], SHA512), 888 HashDRBG!(ubyte[1024], SHA512), 889 ); 890 891 unitlog("Testing SystemEntropy/HashDRBG"); 892 foreach(Rand; RandTypes) 893 { 894 //unitlog("Testing Rand: "~Rand.stringof); 895 896 Rand rand; 897 assert(!rand.empty); 898 899 assert(rand.front == rand.front); 900 auto val = rand.front; 901 assert(val != ElementType!(Rand).init); 902 903 rand.popFront(); 904 assert(val != rand.front); 905 906 auto randCopy = rand; 907 assert(rand.front == randCopy.front); 908 rand.popFront(); 909 randCopy.popFront(); 910 assert(rand.front != randCopy.front); 911 } 912 }