1 /** 2 Implements cryptographically secure random number generators. 3 4 Copyright: © 2013 RejectedSoftware e.K. 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 41 Throws: 42 CryptoException on error. 43 */ 44 override size_t read(scope ubyte[] dst, IOMode mode) @safe; 45 46 alias read = InputStream.read; 47 } 48 49 50 /** 51 Operating system specific cryptography secure random number generator. 52 53 It uses the "CryptGenRandom" function for Windows and "/dev/urandom" for Posix. 54 It's recommended to combine the output use additional processing generated random numbers 55 via provided functions for systems where security matters. 56 57 Remarks: 58 Windows "CryptGenRandom" RNG has known security vulnerabilities on 59 Windows 2000 and Windows XP (assuming the attacker has control of the 60 machine). Fixed for Windows XP Service Pack 3 and Windows Vista. 61 62 See_Also: $(LINK http://en.wikipedia.org/wiki/CryptGenRandom) 63 */ 64 final class SystemRNG : RandomNumberStream { 65 @safe: 66 import std.exception; 67 68 version(Windows) 69 { 70 //cryptographic service provider 71 private HCRYPTPROV hCryptProv; 72 } 73 else version(Posix) 74 { 75 import core.stdc.errno : errno; 76 import core.stdc.stdio : FILE, _IONBF, fopen, fclose, fread, setvbuf; 77 78 //cryptographic file stream 79 private FILE* m_file; 80 } 81 else 82 { 83 static assert(0, "OS is not supported"); 84 } 85 86 /** 87 Creates new system random generator 88 */ 89 this() 90 @trusted { 91 version(Windows) 92 { 93 //init cryptographic service provider 94 enforce!CryptoException(CryptAcquireContext(&this.hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) != 0, 95 text("Cannot init SystemRNG: Error id is ", GetLastError())); 96 } 97 else version(Posix) 98 { 99 //open file 100 m_file = fopen("/dev/urandom", "rb"); 101 enforce!CryptoException(m_file !is null, "Failed to open /dev/urandom"); 102 scope (failure) fclose(m_file); 103 //do not use buffering stream to avoid possible attacks 104 enforce!CryptoException(setvbuf(m_file, null, 0, _IONBF) == 0, 105 "Failed to disable buffering for random number file handle"); 106 } 107 } 108 109 ~this() 110 @trusted { 111 version(Windows) 112 { 113 CryptReleaseContext(this.hCryptProv, 0); 114 } 115 else version (Posix) 116 { 117 fclose(m_file); 118 } 119 } 120 121 @property bool empty() { return false; } 122 @property ulong leastSize() { return ulong.max; } 123 @property bool dataAvailableForRead() { return true; } 124 const(ubyte)[] peek() { return null; } 125 126 size_t read(scope ubyte[] buffer, IOMode mode) @trusted 127 in 128 { 129 assert(buffer.length, "buffer length must be larger than 0"); 130 assert(buffer.length <= uint.max, "buffer length must be smaller or equal uint.max"); 131 } 132 body 133 { 134 version (Windows) 135 { 136 if(0 == CryptGenRandom(this.hCryptProv, cast(DWORD)buffer.length, buffer.ptr)) 137 { 138 throw new CryptoException(text("Cannot get next random number: Error id is ", GetLastError())); 139 } 140 } 141 else version (Posix) 142 { 143 enforce!CryptoException(fread(buffer.ptr, buffer.length, 1, m_file) == 1, 144 text("Failed to read next random number: ", errno)); 145 } 146 return buffer.length; 147 } 148 149 alias read = RandomNumberStream.read; 150 } 151 152 //test heap-based arrays 153 unittest 154 { 155 import std.algorithm; 156 import std.range; 157 158 //number random bytes in the buffer 159 enum uint bufferSize = 20; 160 161 //number of iteration counts 162 enum iterationCount = 10; 163 164 auto rng = new SystemRNG(); 165 166 //holds the random number 167 ubyte[] rand = new ubyte[bufferSize]; 168 169 //holds the previous random number after the creation of the next one 170 ubyte[] prevRadn = new ubyte[bufferSize]; 171 172 //create the next random number 173 rng.read(prevRadn); 174 175 assert(!equal(prevRadn, take(repeat(0), bufferSize)), "it's almost unbelievable - all random bytes is zero"); 176 177 //take "iterationCount" arrays with random bytes 178 foreach(i; 0..iterationCount) 179 { 180 //create the next random number 181 rng.read(rand); 182 183 assert(!equal(rand, take(repeat(0), bufferSize)), "it's almost unbelievable - all random bytes is zero"); 184 185 assert(!equal(rand, prevRadn), "it's almost unbelievable - current and previous random bytes are equal"); 186 187 //copy current random bytes for next iteration 188 prevRadn[] = rand[]; 189 } 190 } 191 192 //test stack-based arrays 193 unittest 194 { 195 import std.algorithm; 196 import std.range; 197 import std.array; 198 199 //number random bytes in the buffer 200 enum uint bufferSize = 20; 201 202 //number of iteration counts 203 enum iterationCount = 10; 204 205 //array that contains only zeros 206 ubyte[bufferSize] zeroArray; 207 zeroArray[] = take(repeat(cast(ubyte)0), bufferSize).array()[]; 208 209 auto rng = new SystemRNG(); 210 211 //holds the random number 212 ubyte[bufferSize] rand; 213 214 //holds the previous random number after the creation of the next one 215 ubyte[bufferSize] prevRadn; 216 217 //create the next random number 218 rng.read(prevRadn); 219 220 assert(prevRadn != zeroArray, "it's almost unbelievable - all random bytes is zero"); 221 222 //take "iterationCount" arrays with random bytes 223 foreach(i; 0..iterationCount) 224 { 225 //create the next random number 226 rng.read(rand); 227 228 assert(prevRadn != zeroArray, "it's almost unbelievable - all random bytes is zero"); 229 230 assert(rand != prevRadn, "it's almost unbelievable - current and previous random bytes are equal"); 231 232 //copy current random bytes for next iteration 233 prevRadn[] = rand[]; 234 } 235 } 236 237 238 /** 239 Hash-based cryptographically secure random number mixer. 240 241 This RNG uses a hash function to mix a specific amount of random bytes from the input RNG. 242 Use only cryptographically secure hash functions like SHA-512, Whirlpool or SHA-256, but not MD5. 243 244 Params: 245 Hash: The hash function used, for example SHA1 246 factor: Determines how many times the hash digest length of input data 247 is used as input to the hash function. Increase factor value if you 248 need more security because it increases entropy level or decrease 249 the factor value if you need more speed. 250 251 */ 252 final class HashMixerRNG(Hash, uint factor) : RandomNumberStream 253 if(isDigest!Hash) 254 { 255 static assert(factor, "factor must be larger than 0"); 256 257 //random number generator 258 SystemRNG rng; 259 260 /** 261 Creates new hash-based mixer random generator. 262 */ 263 this() 264 { 265 //create random number generator 266 this.rng = new SystemRNG(); 267 } 268 269 @property bool empty() { return false; } 270 @property ulong leastSize() { return ulong.max; } 271 @property bool dataAvailableForRead() { return true; } 272 const(ubyte)[] peek() { return null; } 273 274 size_t read(scope ubyte[] buffer, IOMode mode) 275 in 276 { 277 assert(buffer.length, "buffer length must be larger than 0"); 278 assert(buffer.length <= uint.max, "buffer length must be smaller or equal uint.max"); 279 } 280 body 281 { 282 auto len = buffer.length; 283 284 //use stack to allocate internal buffer 285 ubyte[factor * digestLength!Hash] internalBuffer = void; 286 287 //init internal buffer 288 this.rng.read(internalBuffer); 289 290 //create new random number on stack 291 ubyte[digestLength!Hash] randomNumber = digest!Hash(internalBuffer); 292 293 //allows to fill buffers longer than hash digest length 294 while(buffer.length > digestLength!Hash) 295 { 296 //fill the buffer's beginning 297 buffer[0..digestLength!Hash] = randomNumber[0..$]; 298 299 //receive the buffer's end 300 buffer = buffer[digestLength!Hash..$]; 301 302 //re-init internal buffer 303 this.rng.read(internalBuffer); 304 305 //create next random number 306 randomNumber = digest!Hash(internalBuffer); 307 } 308 309 //fill the buffer's end 310 buffer[0..$] = randomNumber[0..buffer.length]; 311 312 return len; 313 } 314 315 alias read = RandomNumberStream.read; 316 } 317 318 /// A SHA-1 based mixing RNG. Alias for HashMixerRNG!(SHA1, 5). 319 alias SHA1HashMixerRNG = HashMixerRNG!(SHA1, 5); 320 321 //test heap-based arrays 322 unittest 323 { 324 import std.algorithm; 325 import std.range; 326 import std.typetuple; 327 import std.digest.md; 328 329 //number of iteration counts 330 enum iterationCount = 10; 331 332 enum uint factor = 5; 333 334 //tested hash functions 335 foreach(Hash; TypeTuple!(SHA1, MD5)) 336 { 337 //test for different number random bytes in the buffer from 10 to 80 inclusive 338 foreach(bufferSize; iota(10, 81)) 339 { 340 auto rng = new HashMixerRNG!(Hash, factor)(); 341 342 //holds the random number 343 ubyte[] rand = new ubyte[bufferSize]; 344 345 //holds the previous random number after the creation of the next one 346 ubyte[] prevRadn = new ubyte[bufferSize]; 347 348 //create the next random number 349 rng.read(prevRadn); 350 351 assert(!equal(prevRadn, take(repeat(0), bufferSize)), "it's almost unbelievable - all random bytes is zero"); 352 353 //take "iterationCount" arrays with random bytes 354 foreach(i; 0..iterationCount) 355 { 356 //create the next random number 357 rng.read(rand); 358 359 assert(!equal(rand, take(repeat(0), bufferSize)), "it's almost unbelievable - all random bytes is zero"); 360 361 assert(!equal(rand, prevRadn), "it's almost unbelievable - current and previous random bytes are equal"); 362 363 //make sure that we have different random bytes in different hash digests 364 if(bufferSize > digestLength!Hash) 365 { 366 //begin and end of random number array 367 ubyte[] begin = rand[0..digestLength!Hash]; 368 ubyte[] end = rand[digestLength!Hash..$]; 369 370 //compare all nearby hash digests 371 while(end.length >= digestLength!Hash) 372 { 373 assert(!equal(begin, end[0..digestLength!Hash]), "it's almost unbelievable - random bytes in different hash digests are equal"); 374 375 //go to the next hash digests 376 begin = end[0..digestLength!Hash]; 377 end = end[digestLength!Hash..$]; 378 } 379 } 380 381 //copy current random bytes for next iteration 382 prevRadn[] = rand[]; 383 } 384 } 385 } 386 } 387 388 //test stack-based arrays 389 unittest 390 { 391 import std.algorithm; 392 import std.range; 393 import std.array; 394 import std.typetuple; 395 import std.digest.md; 396 397 //number of iteration counts 398 enum iterationCount = 10; 399 400 enum uint factor = 5; 401 402 //tested hash functions 403 foreach(Hash; TypeTuple!(SHA1, MD5)) 404 { 405 //test for different number random bytes in the buffer 406 foreach(bufferSize; TypeTuple!(10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80)) 407 { 408 //array that contains only zeros 409 ubyte[bufferSize] zeroArray; 410 zeroArray[] = take(repeat(cast(ubyte)0), bufferSize).array()[]; 411 412 auto rng = new HashMixerRNG!(Hash, factor)(); 413 414 //holds the random number 415 ubyte[bufferSize] rand; 416 417 //holds the previous random number after the creation of the next one 418 ubyte[bufferSize] prevRadn; 419 420 //create the next random number 421 rng.read(prevRadn); 422 423 assert(prevRadn != zeroArray, "it's almost unbelievable - all random bytes is zero"); 424 425 //take "iterationCount" arrays with random bytes 426 foreach(i; 0..iterationCount) 427 { 428 //create the next random number 429 rng.read(rand); 430 431 assert(prevRadn != zeroArray, "it's almost unbelievable - all random bytes is zero"); 432 433 assert(rand != prevRadn, "it's almost unbelievable - current and previous random bytes are equal"); 434 435 //make sure that we have different random bytes in different hash digests 436 if(bufferSize > digestLength!Hash) 437 { 438 //begin and end of random number array 439 ubyte[] begin = rand[0..digestLength!Hash]; 440 ubyte[] end = rand[digestLength!Hash..$]; 441 442 //compare all nearby hash digests 443 while(end.length >= digestLength!Hash) 444 { 445 assert(!equal(begin, end[0..digestLength!Hash]), "it's almost unbelievable - random bytes in different hash digests are equal"); 446 447 //go to the next hash digests 448 begin = end[0..digestLength!Hash]; 449 end = end[digestLength!Hash..$]; 450 } 451 } 452 453 //copy current random bytes for next iteration 454 prevRadn[] = rand[]; 455 } 456 } 457 } 458 } 459 460 461 /** 462 Thrown when an error occurs during random number generation. 463 */ 464 class CryptoException : Exception 465 { 466 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) @safe pure nothrow 467 { 468 super(msg, file, line, next); 469 } 470 } 471 472 473 version(Windows) 474 { 475 import core.sys.windows.windows; 476 477 private extern(Windows) nothrow 478 { 479 alias HCRYPTPROV = size_t; 480 481 enum LPCTSTR NULL = cast(LPCTSTR)0; 482 enum DWORD PROV_RSA_FULL = 1; 483 enum DWORD CRYPT_VERIFYCONTEXT = 0xF0000000; 484 485 BOOL CryptAcquireContextA(HCRYPTPROV *phProv, LPCTSTR pszContainer, LPCTSTR pszProvider, DWORD dwProvType, DWORD dwFlags); 486 alias CryptAcquireContext = CryptAcquireContextA; 487 488 BOOL CryptReleaseContext(HCRYPTPROV hProv, DWORD dwFlags); 489 490 BOOL CryptGenRandom(HCRYPTPROV hProv, DWORD dwLen, BYTE *pbBuffer); 491 } 492 } 493