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