1 /** 2 Implements cryptographically secure random number generators. 3 4 Copyright: © 2013 Sönke Ludwig 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Ilya Shipunov 7 */ 8 module vibe.crypto.cryptorand; 9 10 import std.conv : text; 11 import std.digest.sha; 12 import vibe.core.stream; 13 14 15 /** Creates a cryptographically secure random number generator. 16 17 Note that the returned RNG will operate in a non-blocking mode, which means 18 that if no sufficient entropy has been generated, new random numbers will be 19 generated from previous state. 20 */ 21 RandomNumberStream secureRNG() 22 @safe { 23 static SystemRNG m_rng; 24 if (!m_rng) m_rng = new SystemRNG; 25 return m_rng; 26 } 27 28 29 /** 30 Base interface for all cryptographically secure RNGs. 31 */ 32 interface RandomNumberStream : InputStream { 33 /** 34 Fills the buffer new random numbers. 35 36 Params: 37 dst = The buffer that will be filled with random numbers. 38 It will contain buffer.length random ubytes. 39 Supportes both heap-based and stack-based arrays. 40 mode = The desired waiting mode for IO operations. 41 42 Throws: 43 CryptoException on error. 44 */ 45 override size_t read(scope ubyte[] dst, IOMode mode) @safe; 46 47 alias read = InputStream.read; 48 } 49 50 version(linux) 51 enum bool LinuxMaybeHasGetrandom = __traits(compiles, {import mir.linux._asm.unistd : NR_getrandom;}); 52 else 53 enum bool LinuxMaybeHasGetrandom = false; 54 55 static if (LinuxMaybeHasGetrandom) 56 { 57 // getrandom was introduced in Linux 3.17 58 private enum GET_RANDOM { 59 UNINITIALIZED, 60 NOT_AVAILABLE, 61 AVAILABLE, 62 } 63 private __gshared GET_RANDOM hasGetRandom = GET_RANDOM.UNINITIALIZED; 64 private import core.sys.posix.sys.utsname : utsname; 65 // druntime might not be properly annotated 66 private extern(C) int uname(scope utsname* __name) @nogc nothrow; 67 // checks whether the Linux kernel supports getRandom by looking at the 68 // reported version 69 private bool initHasGetRandom() @nogc @trusted nothrow 70 { 71 import core.stdc.string : strtok; 72 import core.stdc.stdlib : atoi; 73 74 utsname uts; 75 uname(&uts); 76 char* p = uts.release.ptr; 77 78 // poor man's version check 79 auto token = strtok(p, "."); 80 int major = atoi(token); 81 if (major > 3) return true; 82 83 if (major == 3) 84 { 85 token = strtok(p, "."); 86 if (atoi(token) >= 17) return true; 87 } 88 89 return false; 90 } 91 private extern(C) int syscall(size_t ident, size_t n, size_t arg1, size_t arg2) @nogc nothrow; 92 } 93 94 version (CRuntime_Bionic) 95 version = secure_arc4random;//ChaCha20 96 version (OSX) 97 version = secure_arc4random;//AES 98 version (OpenBSD) 99 version = secure_arc4random;//ChaCha20 100 version (NetBSD) 101 version = secure_arc4random;//ChaCha20 102 version (secure_arc4random) 103 extern(C) @nogc nothrow private @system 104 { 105 void arc4random_buf(scope void* buf, size_t nbytes); 106 } 107 108 /** 109 Operating system specific cryptography secure random number generator. 110 111 It uses the "CryptGenRandom" function for Windows; the "arc4random_buf" 112 function (not based on RC4 but on a modern and cryptographically secure 113 cipher) for macOS/OpenBSD/NetBSD; the "getrandom" syscall for Linux 3.17 114 and later; and "/dev/urandom" for other Posix platforms. 115 It's recommended to combine the output use additional processing generated random numbers 116 via provided functions for systems where security matters. 117 118 Remarks: 119 Windows "CryptGenRandom" RNG has known security vulnerabilities on 120 Windows 2000 and Windows XP (assuming the attacker has control of the 121 machine). Fixed for Windows XP Service Pack 3 and Windows Vista. 122 123 See_Also: $(LINK http://en.wikipedia.org/wiki/CryptGenRandom) 124 */ 125 final class SystemRNG : RandomNumberStream { 126 @safe: 127 import std.exception; 128 129 version(Windows) 130 { 131 //cryptographic service provider 132 private HCRYPTPROV hCryptProv; 133 } 134 else version(secure_arc4random) 135 { 136 //Using arc4random does not involve any extra fields. 137 } 138 else version(Posix) 139 { 140 import core.stdc.errno : errno, EINTR; 141 //cryptographic file descriptor 142 private int m_fd = -1; 143 } 144 else 145 { 146 static assert(0, "OS is not supported"); 147 } 148 149 /** 150 Creates new system random generator 151 */ 152 this() 153 @trusted { 154 version(Windows) 155 { 156 //init cryptographic service provider 157 enforce!CryptoException(CryptAcquireContext(&this.hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) != 0, 158 text("Cannot init SystemRNG: Error id is ", GetLastError())); 159 } 160 else version(secure_arc4random) 161 { 162 //arc4random requires no setup or cleanup. 163 } 164 else version(Posix) 165 { 166 import core.sys.posix.fcntl : open, O_RDONLY; 167 version (linux) static if (LinuxMaybeHasGetrandom) 168 { 169 import core.atomic : atomicLoad, atomicStore; 170 GET_RANDOM p = atomicLoad(*cast(const shared GET_RANDOM*) &hasGetRandom); 171 if (p == GET_RANDOM.UNINITIALIZED) 172 { 173 p = initHasGetRandom() ? GET_RANDOM.AVAILABLE 174 : GET_RANDOM.NOT_AVAILABLE; 175 // Benign race condition. 176 atomicStore(*cast(shared GET_RANDOM*) &hasGetRandom, p); 177 } 178 if (p == GET_RANDOM.AVAILABLE) 179 return; 180 } 181 //open file 182 m_fd = open("/dev/urandom", O_RDONLY); 183 enforce!CryptoException(m_fd != -1, "Failed to open /dev/urandom"); 184 } 185 } 186 187 ~this() 188 @trusted { 189 version(Windows) 190 { 191 CryptReleaseContext(this.hCryptProv, 0); 192 } 193 else version (secure_arc4random) 194 { 195 //arc4random requires no setup or cleanup. 196 } 197 else version (Posix) 198 { 199 import core.sys.posix.unistd : close; 200 version (linux) static if (LinuxMaybeHasGetrandom) 201 { 202 if (m_fd == -1) return; 203 } 204 close(m_fd); 205 } 206 } 207 208 @property bool empty() { return false; } 209 @property ulong leastSize() { return ulong.max; } 210 @property bool dataAvailableForRead() { return true; } 211 const(ubyte)[] peek() { return null; } 212 213 size_t read(scope ubyte[] buffer, IOMode mode) @trusted 214 in 215 { 216 assert(buffer.length, "buffer length must be larger than 0"); 217 assert(buffer.length <= uint.max, "buffer length must be smaller or equal uint.max"); 218 } 219 do 220 { 221 version (Windows) 222 { 223 if(0 == CryptGenRandom(this.hCryptProv, cast(DWORD)buffer.length, buffer.ptr)) 224 { 225 throw new CryptoException(text("Cannot get next random number: Error id is ", GetLastError())); 226 } 227 } 228 else version (secure_arc4random) 229 { 230 arc4random_buf(buffer.ptr, buffer.length);//Cannot fail. 231 } 232 else version (Posix) 233 { 234 version (linux) static if (LinuxMaybeHasGetrandom) 235 { 236 if (hasGetRandom == GET_RANDOM.AVAILABLE) 237 { 238 /* 239 http://man7.org/linux/man-pages/man2/getrandom.2.html 240 If the urandom source has been initialized, reads of up to 256 bytes 241 will always return as many bytes as requested and will not be 242 interrupted by signals. No such guarantees apply for larger buffer 243 sizes. 244 */ 245 import mir.linux._asm.unistd : NR_getrandom; 246 size_t len = buffer.length; 247 size_t ptr = cast(size_t) buffer.ptr; 248 while (len > 0) 249 { 250 auto res = syscall(NR_getrandom, ptr, len, 0); 251 if (res >= 0) 252 { 253 len -= res; 254 ptr += res; 255 } 256 else if (errno != EINTR) 257 { 258 throw new CryptoException( 259 text("Failed to read next random number: ", errno)); 260 } 261 } 262 return buffer.length; 263 } 264 } 265 import core.sys.posix.unistd : _read = read; 266 enforce!CryptoException(_read(m_fd, buffer.ptr, buffer.length) == buffer.length, 267 text("Failed to read next random number: ", errno)); 268 } 269 return buffer.length; 270 } 271 272 alias read = RandomNumberStream.read; 273 } 274 275 //test heap-based arrays 276 unittest 277 { 278 import std.algorithm; 279 import std.range; 280 281 //number random bytes in the buffer 282 enum uint bufferSize = 20; 283 284 //number of iteration counts 285 enum iterationCount = 10; 286 287 auto rng = new SystemRNG(); 288 289 //holds the random number 290 ubyte[] rand = new ubyte[bufferSize]; 291 292 //holds the previous random number after the creation of the next one 293 ubyte[] prevRadn = new ubyte[bufferSize]; 294 295 //create the next random number 296 rng.read(prevRadn); 297 298 assert(!equal(prevRadn, take(repeat(0), bufferSize)), "it's almost unbelievable - all random bytes is zero"); 299 300 //take "iterationCount" arrays with random bytes 301 foreach(i; 0..iterationCount) 302 { 303 //create the next random number 304 rng.read(rand); 305 306 assert(!equal(rand, take(repeat(0), bufferSize)), "it's almost unbelievable - all random bytes is zero"); 307 308 assert(!equal(rand, prevRadn), "it's almost unbelievable - current and previous random bytes are equal"); 309 310 //copy current random bytes for next iteration 311 prevRadn[] = rand[]; 312 } 313 } 314 315 //test stack-based arrays 316 unittest 317 { 318 import std.algorithm; 319 import std.range; 320 import std.array; 321 322 //number random bytes in the buffer 323 enum uint bufferSize = 20; 324 325 //number of iteration counts 326 enum iterationCount = 10; 327 328 //array that contains only zeros 329 ubyte[bufferSize] zeroArray; 330 zeroArray[] = take(repeat(cast(ubyte)0), bufferSize).array()[]; 331 332 auto rng = new SystemRNG(); 333 334 //holds the random number 335 ubyte[bufferSize] rand; 336 337 //holds the previous random number after the creation of the next one 338 ubyte[bufferSize] prevRadn; 339 340 //create the next random number 341 rng.read(prevRadn); 342 343 assert(prevRadn != zeroArray, "it's almost unbelievable - all random bytes is zero"); 344 345 //take "iterationCount" arrays with random bytes 346 foreach(i; 0..iterationCount) 347 { 348 //create the next random number 349 rng.read(rand); 350 351 assert(prevRadn != zeroArray, "it's almost unbelievable - all random bytes is zero"); 352 353 assert(rand != prevRadn, "it's almost unbelievable - current and previous random bytes are equal"); 354 355 //copy current random bytes for next iteration 356 prevRadn[] = rand[]; 357 } 358 } 359 360 361 /** 362 Hash-based cryptographically secure random number mixer. 363 364 This RNG uses a hash function to mix a specific amount of random bytes from the input RNG. 365 Use only cryptographically secure hash functions like SHA-512, Whirlpool or SHA-256, but not MD5. 366 367 Params: 368 Hash: The hash function used, for example SHA1 369 factor: Determines how many times the hash digest length of input data 370 is used as input to the hash function. Increase factor value if you 371 need more security because it increases entropy level or decrease 372 the factor value if you need more speed. 373 374 */ 375 final class HashMixerRNG(Hash, uint factor) : RandomNumberStream 376 if(isDigest!Hash) 377 { 378 static assert(factor, "factor must be larger than 0"); 379 380 //random number generator 381 SystemRNG rng; 382 383 /** 384 Creates new hash-based mixer random generator. 385 */ 386 this() 387 { 388 //create random number generator 389 this.rng = new SystemRNG(); 390 } 391 392 @property bool empty() { return false; } 393 @property ulong leastSize() { return ulong.max; } 394 @property bool dataAvailableForRead() { return true; } 395 const(ubyte)[] peek() { return null; } 396 397 size_t read(scope ubyte[] buffer, IOMode mode) 398 in 399 { 400 assert(buffer.length, "buffer length must be larger than 0"); 401 assert(buffer.length <= uint.max, "buffer length must be smaller or equal uint.max"); 402 } 403 do 404 { 405 auto len = buffer.length; 406 407 //use stack to allocate internal buffer 408 ubyte[factor * digestLength!Hash] internalBuffer = void; 409 410 //init internal buffer 411 this.rng.read(internalBuffer); 412 413 //create new random number on stack 414 ubyte[digestLength!Hash] randomNumber = digest!Hash(internalBuffer); 415 416 //allows to fill buffers longer than hash digest length 417 while(buffer.length > digestLength!Hash) 418 { 419 //fill the buffer's beginning 420 buffer[0..digestLength!Hash] = randomNumber[0..$]; 421 422 //receive the buffer's end 423 buffer = buffer[digestLength!Hash..$]; 424 425 //re-init internal buffer 426 this.rng.read(internalBuffer); 427 428 //create next random number 429 randomNumber = digest!Hash(internalBuffer); 430 } 431 432 //fill the buffer's end 433 buffer[0..$] = randomNumber[0..buffer.length]; 434 435 return len; 436 } 437 438 alias read = RandomNumberStream.read; 439 } 440 441 /// A SHA-1 based mixing RNG. Alias for HashMixerRNG!(SHA1, 5). 442 alias SHA1HashMixerRNG = HashMixerRNG!(SHA1, 5); 443 444 //test heap-based arrays 445 unittest 446 { 447 import std.algorithm; 448 import std.range; 449 import std.typetuple; 450 import std.digest.md; 451 452 //number of iteration counts 453 enum iterationCount = 10; 454 455 enum uint factor = 5; 456 457 //tested hash functions 458 foreach(Hash; TypeTuple!(SHA1, MD5)) 459 { 460 //test for different number random bytes in the buffer from 10 to 80 inclusive 461 foreach(bufferSize; iota(10, 81)) 462 { 463 auto rng = new HashMixerRNG!(Hash, factor)(); 464 465 //holds the random number 466 ubyte[] rand = new ubyte[bufferSize]; 467 468 //holds the previous random number after the creation of the next one 469 ubyte[] prevRadn = new ubyte[bufferSize]; 470 471 //create the next random number 472 rng.read(prevRadn); 473 474 assert(!equal(prevRadn, take(repeat(0), bufferSize)), "it's almost unbelievable - all random bytes is zero"); 475 476 //take "iterationCount" arrays with random bytes 477 foreach(i; 0..iterationCount) 478 { 479 //create the next random number 480 rng.read(rand); 481 482 assert(!equal(rand, take(repeat(0), bufferSize)), "it's almost unbelievable - all random bytes is zero"); 483 484 assert(!equal(rand, prevRadn), "it's almost unbelievable - current and previous random bytes are equal"); 485 486 //make sure that we have different random bytes in different hash digests 487 if(bufferSize > digestLength!Hash) 488 { 489 //begin and end of random number array 490 ubyte[] begin = rand[0..digestLength!Hash]; 491 ubyte[] end = rand[digestLength!Hash..$]; 492 493 //compare all nearby hash digests 494 while(end.length >= digestLength!Hash) 495 { 496 assert(!equal(begin, end[0..digestLength!Hash]), "it's almost unbelievable - random bytes in different hash digests are equal"); 497 498 //go to the next hash digests 499 begin = end[0..digestLength!Hash]; 500 end = end[digestLength!Hash..$]; 501 } 502 } 503 504 //copy current random bytes for next iteration 505 prevRadn[] = rand[]; 506 } 507 } 508 } 509 } 510 511 //test stack-based arrays 512 unittest 513 { 514 import std.algorithm; 515 import std.range; 516 import std.array; 517 import std.typetuple; 518 import std.digest.md; 519 520 //number of iteration counts 521 enum iterationCount = 10; 522 523 enum uint factor = 5; 524 525 //tested hash functions 526 foreach(Hash; TypeTuple!(SHA1, MD5)) 527 { 528 //test for different number random bytes in the buffer 529 foreach(bufferSize; TypeTuple!(10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80)) 530 { 531 //array that contains only zeros 532 ubyte[bufferSize] zeroArray; 533 zeroArray[] = take(repeat(cast(ubyte)0), bufferSize).array()[]; 534 535 auto rng = new HashMixerRNG!(Hash, factor)(); 536 537 //holds the random number 538 ubyte[bufferSize] rand; 539 540 //holds the previous random number after the creation of the next one 541 ubyte[bufferSize] prevRadn; 542 543 //create the next random number 544 rng.read(prevRadn); 545 546 assert(prevRadn != zeroArray, "it's almost unbelievable - all random bytes is zero"); 547 548 //take "iterationCount" arrays with random bytes 549 foreach(i; 0..iterationCount) 550 { 551 //create the next random number 552 rng.read(rand); 553 554 assert(prevRadn != zeroArray, "it's almost unbelievable - all random bytes is zero"); 555 556 assert(rand != prevRadn, "it's almost unbelievable - current and previous random bytes are equal"); 557 558 //make sure that we have different random bytes in different hash digests 559 if(bufferSize > digestLength!Hash) 560 { 561 //begin and end of random number array 562 ubyte[] begin = rand[0..digestLength!Hash]; 563 ubyte[] end = rand[digestLength!Hash..$]; 564 565 //compare all nearby hash digests 566 while(end.length >= digestLength!Hash) 567 { 568 assert(!equal(begin, end[0..digestLength!Hash]), "it's almost unbelievable - random bytes in different hash digests are equal"); 569 570 //go to the next hash digests 571 begin = end[0..digestLength!Hash]; 572 end = end[digestLength!Hash..$]; 573 } 574 } 575 576 //copy current random bytes for next iteration 577 prevRadn[] = rand[]; 578 } 579 } 580 } 581 } 582 583 584 /** 585 Thrown when an error occurs during random number generation. 586 */ 587 class CryptoException : Exception 588 { 589 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) @safe pure nothrow 590 { 591 super(msg, file, line, next); 592 } 593 } 594 595 596 version(Windows) 597 { 598 import core.sys.windows.windows; 599 600 private extern(Windows) nothrow 601 { 602 alias HCRYPTPROV = size_t; 603 604 enum LPCTSTR NULL = cast(LPCTSTR)0; 605 enum DWORD PROV_RSA_FULL = 1; 606 enum DWORD CRYPT_VERIFYCONTEXT = 0xF0000000; 607 608 BOOL CryptAcquireContextA(HCRYPTPROV *phProv, LPCTSTR pszContainer, LPCTSTR pszProvider, DWORD dwProvType, DWORD dwFlags); 609 alias CryptAcquireContext = CryptAcquireContextA; 610 611 BOOL CryptReleaseContext(HCRYPTPROV hProv, DWORD dwFlags); 612 613 BOOL CryptGenRandom(HCRYPTPROV hProv, DWORD dwLen, BYTE *pbBuffer); 614 } 615 }