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