1 /++ 2 DAuth - Authentication Utility for D 3 Hash_DRBG Cryptographic Random Number Generator 4 5 Main module: $(LINK2 index.html,dauth)$(BR) 6 +/ 7 8 module dauth.hashdrbg; 9 10 import std.conv; 11 import std.exception; 12 import std.random; 13 import std.range; 14 import std.stdio; 15 import std.traits; 16 import std.typecons; 17 import std.typetuple; 18 19 import dauth.core; 20 import dauth.sha; 21 22 version(Windows) 23 { 24 import std.c.windows.windows; 25 import core.runtime; 26 } 27 28 /// 29 enum isRandomStream(T) = 30 is(typeof({ 31 static assert(T.isUniformRandomStream); 32 T t; 33 ubyte[] buf; 34 t.read(buf); 35 })); 36 37 /// Check whether T is either isUniformRNG or isRandomStream. 38 enum isSomeStream(T) = isUniformRNG!T || isRandomStream!T; 39 40 static assert(isRandomStream!(SystemEntropyStream)); 41 static assert(isRandomStream!(HashDRBGStream!())); 42 static assert(!isRandomStream!(SystemEntropy!uint)); 43 static assert(!isRandomStream!(HashDRBG!uint)); 44 static assert(!isRandomStream!uint); 45 static assert(!isRandomStream!File); 46 47 /++ 48 Reads any desired amount of random entropy from a system-specific cryptographic 49 random number generator. On Windows, this loads ADVAPI32.DLL and uses 50 RtlGenRandom. On Posix, this uses '/dev/random'. 51 52 Optionally, you can use open() and close() to control the lifetime of 53 SystemEntropyStream's system handles (ie, loading/uloading ADVAPI32.DLL and 54 opening/closing '/dev/random'). But this is not normally necessary since 55 SystemEntropyStream automatically opens them upon reading and closes upon 56 module destruction. 57 58 The speed and cryptographic security of this is dependent on your operating 59 system. This may be perfectly suitable for many cryptographic-grade random 60 number generation needs, but it's primary inteded for seeding/reseeding 61 cryptographic psuedo-random number generators, such as Hash_DRBG or HMAC_DRBG, 62 which are likely to be faster and no less secure than using an entropy source 63 directly. 64 +/ 65 struct SystemEntropyStream 66 { 67 enum isUniformRandomStream = true; /// Mark this as a Rng Stream 68 69 version(Windows) 70 { 71 private static HMODULE _advapi32; 72 private static extern(Windows) BOOL function(void*, uint) _RtlGenRandom; 73 } 74 else version(Posix) 75 private static File devRandom; 76 else 77 static assert(false); 78 79 /// Fills the buffer with entropy from the system-specific entropy generator. 80 /// Automatically opens SystemEntropyStream if it's closed. 81 static void read(ubyte[] buf) 82 { 83 open(); 84 85 version(Windows) 86 { 87 enforce(buf.length < uint.max, "Cannot read more than uint.max bytes from RtlGenRandom"); 88 _RtlGenRandom(buf.ptr, cast(uint)buf.length); 89 } 90 else version(Posix) 91 devRandom.rawRead(buf); 92 else 93 static assert(false); 94 } 95 96 /// Establishes a handle/connection to the system-specific entropy generator. 97 /// Does nothing if already open. 98 static void open() 99 { 100 if(isOpen) 101 return; 102 103 version(Windows) 104 { 105 // Reference: http://blogs.msdn.com/b/michael_howard/archive/2005/01/14/353379.aspx 106 _advapi32 = Runtime.loadLibrary("ADVAPI32.DLL"); 107 _RtlGenRandom = cast(typeof(_RtlGenRandom))_advapi32.GetProcAddress("SystemFunction036"); 108 enforce(_RtlGenRandom); 109 } 110 else version(Posix) 111 { 112 devRandom = File("/dev/random"); 113 devRandom.setvbuf(null, _IONBF); // Disable buffering for security 114 } 115 else 116 static assert(false); 117 } 118 119 /// Manually release the handle/connection to the system-specific entropy generator. 120 static void close() 121 { 122 version(Windows) 123 { 124 if(_advapi32) 125 { 126 Runtime.unloadLibrary(_advapi32); 127 _advapi32 = null; 128 _RtlGenRandom = null; 129 } 130 } 131 else version(Posix) 132 { 133 if(devRandom.isOpen) 134 devRandom.close(); 135 } 136 else 137 static assert(false); 138 } 139 140 /// Check whether SystemEntropyStream is currently connected to with the 141 /// system-specific entropy generator. 142 static @property bool isOpen() 143 { 144 version(Windows) 145 return _advapi32 && _RtlGenRandom; 146 else version(Posix) 147 return devRandom.isOpen; 148 else 149 static assert(false); 150 } 151 152 /// Automatically close upon module destruction. 153 static ~this() 154 { 155 close(); 156 } 157 } 158 159 /// A convenience alias to create a UniformRNG from SystemEntropyStream. 160 /// See the WrappedStreamRNG documentation for important information. 161 alias SystemEntropy(Elem) = WrappedStreamRNG!(SystemEntropyStream, Elem); 162 163 static assert(isUniformRNG!(SystemEntropy!(ubyte[1]), ubyte[1])); 164 static assert(isUniformRNG!(SystemEntropy!(ubyte[5]), ubyte[5])); 165 static assert(isUniformRNG!(SystemEntropy!ubyte, ubyte )); 166 static assert(isUniformRNG!(SystemEntropy!ushort, ushort )); 167 static assert(isUniformRNG!(SystemEntropy!uint, uint )); 168 static assert(isUniformRNG!(SystemEntropy!ulong, ulong )); 169 170 /++ 171 Cryptographic random number generator Hash_DRBG, as defined in 172 NIST's $(LINK2 http://csrc.nist.gov/publications/nistpubs/800-90A/SP800-90A.pdf, SP800-90A). 173 174 TSHA: Any SHA-1 or SHA-2 digest type. Default is SHA512. 175 176 custom: Hash_DRBG's personalization string. You can optionally set this to any 177 specific value of your own choosing for improved security. 178 +/ 179 struct HashDRBGStream(TSHA = SHA512, string custom = "D Crypto RNG", EntropyStream = SystemEntropyStream) 180 if(isInstanceOf!(SHA, TSHA)) 181 { 182 enum isUniformRandomStream = true; /// Mark this as a Rng Stream 183 184 // In bits. This is the same as the SHA's digestSize 185 private enum outputSizeBits = TemplateArgsOf!(TSHA)[1]; 186 187 static if(outputSizeBits < 384) 188 private enum seedSizeBytes = 440/8; // In bytes 189 else 190 private enum seedSizeBytes = 888/8; // In bytes 191 192 // This can be just about any arbitrary size, although there is a 193 // minimum. 1024 bits is above the minimum for SHA-1 and all SHA-2. 194 // See NIST's [SP800-90A] and [SP800-57] for details. 195 private enum entropySizeBytes = 1024/8; 196 197 // This must be at least entropySizeBytes/2 198 private enum nonceSizeBytes = entropySizeBytes/2; 199 200 // value[1..$] is Hash_DRBG's secret working state value V 201 // value[0] is a scratchpad to avoid unnecessary copying/concating of V 202 private static ubyte[seedSizeBytes+1] value; 203 204 private static ubyte[seedSizeBytes] constant; // Hash_DRBG's secret working state value C 205 private static uint numGenerated; // Number of values generated with the current seed 206 207 // Maximum number of values generated before automatically reseeding with fresh entropy. 208 // The algorithm's spec permits this to be anything less than or equal to 2^48, 209 // but we should take care not to overflow our actual countner. 210 private enum int maxGenerated = 0x0FFF_FFFF; 211 212 /++ 213 Set to Yes.PredictionResistance for additional protection against 214 prediction attacks by forcing a reseed with fresh entropy for each call 215 to read(). Reset back to No.PredictionResistance afterwords for faster, 216 but still cryptographically-secure, operation when you're done with 217 extra-elevated security needs. 218 219 Default is No.PredictionResistance. 220 221 This setting is for changing read()'s default bahavior. Individual calls to 222 read() can manually override this per call. 223 +/ 224 Flag!"PredictionResistance" predictionResistance = No.PredictionResistance; 225 226 /++ 227 Further improve security by setting Hash_DRBG's optional "additional input" 228 for each call to read(). This can be set to a new value before each read() 229 call for maximum effect. 230 231 This setting is for changing read()'s default bahavior. Individual calls to 232 read() can manually override this per call. 233 +/ 234 ubyte[] extraInput = null; 235 236 private static bool inited = false; 237 private void init() 238 { 239 if(inited) 240 return; 241 242 // seedMaterial = entropy ~ nonce ~ custom; 243 ubyte[entropySizeBytes + nonceSizeBytes + custom.length] seedMaterial = void; 244 EntropyStream.read( seedMaterial[0 .. $-custom.length] ); 245 seedMaterial[$-custom.length .. $] = cast(ubyte[])custom; 246 247 // Generate seed for V 248 hashDerivation(seedMaterial, null, value[1..$]); 249 250 // Generate constant 251 value[0] = 0x00; 252 hashDerivation(value, null, constant); 253 254 numGenerated = 0; 255 inited = true; 256 } 257 258 private void reseed(ubyte[] extraInput=null) 259 { 260 // seedMaterial = 0x01 ~ V ~ entropy; 261 ubyte[value.sizeof + entropySizeBytes] seedMaterial = void; 262 seedMaterial[0] = 0x01; 263 seedMaterial[1 .. $-entropySizeBytes] = value[1..$]; 264 EntropyStream.read( seedMaterial[$-entropySizeBytes .. $] ); 265 266 // Generate seed for V 267 hashDerivation(seedMaterial, extraInput, value[1..$]); 268 269 // Generate constant 270 value[0] = 0x00; 271 hashDerivation(value, null, constant); 272 273 numGenerated = 0; 274 } 275 276 /++ 277 Fills the buffer with random values using the Hash_DRBG algorithm. 278 279 overridePredictionResistance: 280 Override this.predictionResistance setting for this call only. 281 282 overrideExtraInput: 283 Override this.extraInput setting for this call only. 284 +/ 285 void read(ubyte[] buf) 286 { 287 read(buf, predictionResistance, extraInput); 288 } 289 290 ///ditto 291 void read(ubyte[] buf, ubyte[] overrideExtraInput) 292 { 293 read(buf, predictionResistance, overrideExtraInput); 294 } 295 296 ///ditto 297 void read(ubyte[] buf, Flag!"PredictionResistance" overridePredictionResistance) 298 { 299 read(buf, overridePredictionResistance, extraInput); 300 } 301 302 ///ditto 303 void read(ubyte[] buf, 304 Flag!"PredictionResistance" overridePredictionResistance, 305 ubyte[] overrideExtraInput) 306 { 307 if(numGenerated >= maxGenerated || overridePredictionResistance == Yes.PredictionResistance) 308 reseed(overrideExtraInput); 309 310 if(overrideExtraInput) 311 { 312 value[0] = 0x02; 313 314 TSHA sha; 315 sha.put(value); 316 sha.put(overrideExtraInput); 317 ubyte[seedSizeBytes] tempHash; 318 tempHash[0..outputSizeBits/8] = sha.finish(); 319 addHash!seedSizeBytes(value[1..$], tempHash, value[1..$]); 320 } 321 322 ubyte[seedSizeBytes] workingData = value[1..$]; 323 if(buf.length > 0) 324 while(true) 325 { 326 // Fill the front of buf with up to seedSizeBytes of random data 327 ubyte[outputSizeBits/8] currHash = digest!TSHA(workingData); 328 auto length = buf.length < currHash.length? buf.length : currHash.length; 329 buf[0..length] = currHash[0..length]; 330 buf = buf[length..$]; 331 332 // Buffer filled? 333 if(buf.length == 0) 334 break; 335 336 incrementHash(workingData); 337 } 338 339 // Update V 340 value[0] = 0x03; 341 ubyte[seedSizeBytes] hashSum = void; 342 hashSum[0 .. outputSizeBits/8] = digest!TSHA(value); 343 hashSum[outputSizeBits/8 .. $] = 0; 344 addHash!seedSizeBytes(hashSum, value[1..$], hashSum); 345 addHash!seedSizeBytes(hashSum, constant, hashSum); 346 addHash!seedSizeBytes(hashSum, numGenerated+1, value[1..$]); 347 348 numGenerated++; 349 } 350 351 private static void hashDerivation(ubyte[] input, ubyte[] extraInput, ubyte[] buf) 352 { 353 ubyte counter = 1; 354 ulong originalBufLength = buf.length; 355 while(buf.length) 356 { 357 // Generate hashed data 358 TSHA sha; 359 sha.put(counter); 360 sha.put(*(cast(ubyte[8]*) &originalBufLength)); 361 sha.put(input); 362 if(extraInput) 363 sha.put(extraInput); 364 auto currHash = sha.finish(); 365 366 // Fill the front of buf with the hashed data 367 auto length = buf.length < currHash.length? buf.length : currHash.length; 368 buf[0..length] = currHash[0..length]; 369 buf = buf[length..$]; 370 371 counter++; 372 } 373 } 374 375 private static void incrementHash(int numBytes)(ref ubyte[numBytes] arr) 376 { 377 // Endianness (small, big or even weird mixes) doesn't matter since hashes 378 // don't have a particularly meaningful least/most significant bit. As 379 // long as we're consistent across the RNG instance's lifetime, we're good. 380 381 foreach(ref b; arr) 382 { 383 b++; 384 if(b != 0) 385 break; 386 } 387 } 388 389 private static void addHash(int numBytes)(ubyte[numBytes] arr1, 390 ubyte[numBytes] arr2, ubyte[] result) 391 { 392 // As with incrementHash, endianness doesn't matter here. 393 394 enforce(arr1.length == arr2.length); 395 enforce(arr1.length == result.length); 396 uint carry = 0; 397 foreach(i; 0..arr1.length) 398 { 399 auto sum = arr1[i] + arr2[i] + carry; 400 result[i] = sum & 0xFF; 401 carry = sum >> 8; 402 } 403 } 404 405 private static void addHash(int numBytes)(ubyte[numBytes] arr, uint value, 406 ubyte[] result) 407 { 408 // As with incrementHash, endianness doesn't matter here. 409 410 enforce(arr.length == result.length); 411 uint carry = value; 412 foreach(i; 0..arr.length) 413 { 414 uint sum = arr[i] + carry; 415 result[i] = sum & 0xFF; 416 carry = sum >> 8; 417 } 418 } 419 } 420 421 /// A convenience template to create a UniformRNG from HashDRBGStream. 422 /// See the WrappedStreamRNG documentation for important information. 423 template HashDRBG(Elem, TSHA = SHA512, string custom = "D Crypto RNG", EntropyStream = SystemEntropyStream) 424 if(isInstanceOf!(SHA, TSHA)) 425 { 426 alias HashDRBG = WrappedStreamRNG!(HashDRBGStream!(TSHA, custom, EntropyStream), Elem); 427 } 428 429 static assert(isUniformRNG!(HashDRBG!(ubyte[1]), ubyte[1])); 430 static assert(isUniformRNG!(HashDRBG!(ubyte[5]), ubyte[5])); 431 static assert(isUniformRNG!(HashDRBG!ubyte, ubyte )); 432 static assert(isUniformRNG!(HashDRBG!ushort, ushort )); 433 static assert(isUniformRNG!(HashDRBG!uint, uint )); 434 static assert(isUniformRNG!(HashDRBG!ulong, ulong )); 435 static assert(isUniformRNG!(HashDRBG!(uint), uint)); 436 static assert(isUniformRNG!(HashDRBG!(uint, SHA256), uint)); 437 static assert(isUniformRNG!(HashDRBG!(uint, SHA256, "custom"), uint)); 438 static assert(isUniformRNG!(HashDRBG!(uint, SHA256, "custom", SystemEntropyStream), uint)); 439 440 version(DAuth_Unittest) 441 unittest 442 { 443 unitlog("Testing HashDRBGStream.incrementHash"); 444 445 HashDRBGStream!SHA1 rand; 446 ubyte[5] val = [0xFF, 0xFF, 0b0000_1011, 0x00, 0x00]; 447 ubyte[5] expected = [0x00, 0x00, 0b0000_1100, 0x00, 0x00]; 448 449 assert(val != expected); 450 rand.incrementHash(val); 451 assert(val == expected); 452 } 453 454 version(DAuth_Unittest) 455 unittest 456 { 457 unitlog("Testing HashDRBGStream.addHash(arr,arr,arr)"); 458 459 HashDRBGStream!SHA1 rand; 460 ubyte[5] val1 = [0xCC, 0x05, 0xFE, 0x01, 0x00]; 461 ubyte[5] val2 = [0x33, 0x02, 0x9E, 0x00, 0x00]; 462 ubyte[5] expected = [0xFF, 0x07, 0x9C, 0x02, 0x00]; 463 ubyte[5] result; 464 465 assert(result != expected); 466 rand.addHash(val1, val2, result); 467 assert(result == expected); 468 } 469 470 version(DAuth_Unittest) 471 unittest 472 { 473 unitlog("Testing HashDRBGStream.addHash(arr,int,arr)"); 474 475 HashDRBGStream!SHA1 rand; 476 ubyte[5] val1 = [0xCC, 0x05, 0xFE, 0x01, 0x00]; 477 uint val2 = 0x009E_0233; 478 ubyte[5] expected = [0xFF, 0x07, 0x9C, 0x02, 0x00]; 479 ubyte[5] result; 480 481 assert(result != expected); 482 rand.addHash(val1, val2, result); 483 assert(result == expected); 484 } 485 486 /++ 487 Takes a RandomStream (ex: SystemEntropyStream or HashDRBGStream) and 488 wraps it into a UniformRNG InputRange. 489 490 Note that, to conform to the expected InputRange interface, this must keep a 491 copy of the last generated value in memory. For security purposes, it may 492 occasionally be appropriate to make an extra popFront() call before and/or 493 after retreiving entropy values. This may decrease the chance of using 494 a compromized entropy value in the event of a memory-sniffing attacker. 495 +/ 496 struct WrappedStreamRNG(RandomStream, StaticUByteArr) 497 if(isRandomStream!RandomStream && isStaticArray!StaticUByteArr && is(ElementType!StaticUByteArr==ubyte)) 498 { 499 enum isUniformRandom = true; /// Mark this as a Rng 500 501 private StaticUByteArr _front; 502 private bool inited = false; 503 504 /// Access to underlying RandomStream so RNG-specific functionality can be accessed. 505 RandomStream stream; 506 507 /// Implements an InputRange 508 @property StaticUByteArr front() 509 { 510 if(!inited) 511 { 512 popFront(); 513 inited = true; 514 } 515 516 return _front; 517 } 518 519 ///ditto 520 void popFront() 521 { 522 stream.read(_front); 523 } 524 525 /// Infinite range. Never empty. 526 enum empty = false; 527 528 /// Smallest generated value. 529 enum min = StaticUByteArr.init; 530 531 /// Largest generated value. 532 static @property StaticUByteArr max() 533 { 534 StaticUByteArr val = void; 535 val[] = 0xFF; 536 return val; 537 } 538 } 539 540 ///ditto 541 struct WrappedStreamRNG(RandomStream, UIntType) 542 if(isRandomStream!RandomStream && isUnsigned!UIntType) 543 { 544 private WrappedStreamRNG!(RandomStream, ubyte[UIntType.sizeof]) bytesImpl; 545 546 enum isUniformRandom = true; /// Mark this as a Rng 547 548 private UIntType _front; 549 private bool inited = false; 550 551 /// Implements an InputRange 552 @property UIntType front() 553 { 554 auto val = bytesImpl.front; 555 return *(cast(UIntType*) &val); 556 } 557 558 ///ditto 559 void popFront() 560 { 561 bytesImpl.popFront(); 562 } 563 564 enum empty = false; /// Infinite range. Never empty. 565 enum min = UIntType.min; /// Smallest generated value. 566 enum max = UIntType.max; /// Largest generated value. 567 } 568 569 version(DAuth_Unittest) 570 unittest 571 { 572 alias RandStreamTypes = TypeTuple!( 573 SystemEntropyStream, 574 HashDRBGStream!SHA1, 575 HashDRBGStream!SHA224, 576 HashDRBGStream!SHA256, 577 HashDRBGStream!SHA384, 578 HashDRBGStream!SHA512, 579 HashDRBGStream!SHA512_224, 580 HashDRBGStream!SHA512_256, 581 HashDRBGStream!(SHA512, "other custom str"), 582 ); 583 584 unitlog("Testing SystemEntropyStream/HashDRBGStream"); 585 foreach(RandStream; RandStreamTypes) 586 { 587 //unitlog("Testing RandStream: "~RandStream.stringof); 588 589 RandStream rand; 590 ubyte[] values1; 591 ubyte[] values2; 592 values1.length = 10; 593 values2.length = 10; 594 595 rand.read(values1); 596 assert(values1 != typeof(values1).init); 597 assert(values1[0..4] != values1[4..8]); 598 rand.read(values2); 599 assert(values1 != values2); 600 601 auto randCopy = rand; 602 rand.read(values1); 603 randCopy.read(values2); 604 assert(values1 != values2); 605 606 static if(!is(RandStream == SystemEntropyStream)) 607 { 608 values2[] = ubyte.init; 609 610 values1[] = ubyte.init; 611 rand.read(values1, Yes.PredictionResistance); 612 assert(values1 != values2); 613 614 values1[] = ubyte.init; 615 rand.read(values1, cast(ubyte[])"additional input"); 616 assert(values1 != values2); 617 618 values1[] = ubyte.init; 619 rand.read(values1, Yes.PredictionResistance, cast(ubyte[])"additional input"); 620 assert(values1 != values2); 621 } 622 } 623 } 624 625 version(DAuth_Unittest) 626 unittest 627 { 628 foreach(Rand; TypeTuple!(SystemEntropy, HashDRBG)) 629 { 630 unitlog("Testing Rand's min/max: "~Rand.stringof); 631 632 assert(Rand!(ubyte[1]).min == [0x00]); 633 assert(Rand!(ubyte[1]).max == [0xFF]); 634 assert(Rand!(ubyte[5]).min == [0x00,0x00,0x00,0x00,0x00]); 635 assert(Rand!(ubyte[5]).max == [0xFF,0xFF,0xFF,0xFF,0xFF]); 636 assert(Rand!(ubyte ).min == ubyte .min); 637 assert(Rand!(ubyte ).max == ubyte .max); 638 assert(Rand!(ushort ).min == ushort.min); 639 assert(Rand!(ushort ).max == ushort.max); 640 assert(Rand!(uint ).min == uint .min); 641 assert(Rand!(uint ).max == uint .max); 642 assert(Rand!(ulong ).min == ulong .min); 643 assert(Rand!(ulong ).max == ulong .max); 644 } 645 } 646 647 version(DAuth_Unittest) 648 unittest 649 { 650 // Don't test ubyte or ushort versions here because legitimate repeated 651 // values are too likely and would trigger a failure and unfounded worry. 652 653 alias RandTypes = TypeTuple!( 654 SystemEntropy!ulong, 655 SystemEntropy!uint, 656 SystemEntropy!(ubyte[5]), 657 SystemEntropy!(ubyte[1024]), 658 HashDRBG!(ulong, SHA1), 659 HashDRBG!(ulong, SHA224), 660 HashDRBG!(ulong, SHA256), 661 HashDRBG!(ulong, SHA384), 662 HashDRBG!(ulong, SHA512), 663 HashDRBG!(ulong, SHA512_224), 664 HashDRBG!(ulong, SHA512_256), 665 HashDRBG!(ulong, SHA512, "other custom str"), 666 HashDRBG!(uint, SHA512), 667 HashDRBG!(ubyte[5], SHA512), 668 HashDRBG!(ubyte[1024], SHA512), 669 ); 670 671 unitlog("Testing SystemEntropy/HashDRBG"); 672 foreach(Rand; RandTypes) 673 { 674 //unitlog("Testing Rand: "~Rand.stringof); 675 676 Rand rand; 677 assert(!rand.empty); 678 679 assert(rand.front == rand.front); 680 auto val = rand.front; 681 assert(val != ElementType!(Rand).init); 682 683 rand.popFront(); 684 assert(val != rand.front); 685 686 auto randCopy = rand; 687 assert(rand.front == randCopy.front); 688 rand.popFront(); 689 randCopy.popFront(); 690 assert(rand.front != randCopy.front); 691 } 692 }