1 module dcrypt.crypto.random.fortuna.generator;
2 
3 import std.range: chunks;
4 
5 import dcrypt.crypto.random.prng;
6 import dcrypt.crypto.blockcipher;
7 import dcrypt.crypto.digest;
8 import dcrypt.crypto.params.keyparameter;
9 
10 ///	generate a deterministic PRNG sequence
11 @safe unittest {
12 	import dcrypt.crypto.engines.aes;
13 	import dcrypt.crypto.digests.sha2;
14 
15 	FortunaGenerator!(AES, SHA256) prng;
16 	prng.addSeed([0]);
17 
18 	ubyte[71] random;
19 	prng.nextBytes(random);
20 
21 	assert(random == x"2fd720d5d7f93dc8371586ae8c09547095613e2cf8967206f8d16d5717cf15a53beae29b2cf9fc0443ae6c37fd1f11aefb13061415c4d5f27d876cb67a63ba592af029f0447815",
22 		"Unexpected output of deterministic PRNG.");
23 
24 	ubyte[random.length] random2;
25 
26 	prng.nextBytes(random2);
27 
28 	assert(random != random2, "PRNG produced twice the same data!");
29 }
30 
31 // Test if FortunaGenerator fullfills requirements to be a PRNG.
32 static {
33 	import dcrypt.crypto.engines.aes;
34 	import dcrypt.crypto.digests.sha2;
35 	static assert(isRNG!(FortunaGenerator!(AES, SHA256)), "FortunaGenerator violates requirements of isPRNG!");
36 }
37 
38 
39 /// This PRNG forms a base component of the Fortuna PRNG as proposed by Bruce Schneier & Niels Ferguson.
40 /// The Generator can be used stand alone as deterministic PRNG. It won't gather entropy on its own and
41 /// without calling `addSeed()` it will always generate the same sequence of bytes for the same underlying 
42 /// block cipher and hash algorithm.
43 /// 
44 /// Params:
45 /// Cipher = defines the underlying block cipher algorithm.
46 /// Digest = Underlying hash algorithm. Hash length has to be 256 bits (corresponds to used key size).
47 @safe
48 public struct FortunaGenerator(Cipher, Digest) if(isBlockCipher!Cipher && isDigest!Digest && Digest.digestLength == 32)
49 {
50 	// PRNG interface implementation
51 	public nothrow {
52 
53 		this(ubyte[] seed...) @nogc {
54 			addSeed(seed);
55 		}
56 
57 		enum isDeterministic = true;
58 		enum name = "FortunaGenerator/"~Cipher.name~"-"~Digest.name; /// Name of the PRNG algorithm.
59 
60 		/// Fill an arbitrary-size buffer with random data.
61 		void nextBytes(ubyte[] buf) @nogc {
62 			// pseudoRandomData won't generate more data than reseedLimit at once, so call it multiple times if necessary.
63 			foreach(chunk; chunks(buf, reseedLimit)) {
64 				pseudoRandomData(chunk);
65 			}
66 		}
67 		
68 		/// add entropy to the generator
69 		void addSeed(in ubyte[] seed...) @nogc {
70 			reseed(seed);
71 		}
72 
73 	}
74 
75 	private {
76 		enum reseedLimit = 1<<20;
77 		enum blockSize = Cipher.blockSize;
78 		
79 		ubyte[blockSize] counter;
80 		ubyte[blockSize] internalBuffer;
81 		ubyte[32] key;
82 		
83 		Cipher cipher;
84 		Digest digest;
85 
86 		bool initialized = false;
87 	}
88 
89 	private nothrow {
90 
91 		/// compute a new key: newKey = Hash(oldKey | seed)
92 		void reseed(in ubyte[] seed...) @nogc {
93 			digest.put(key);
94 			digest.put(seed);
95 			digest.doFinal(key);
96 
97 			updateKey();
98 
99 			incrementCounter();
100 
101 			initialized = true;
102 		}
103 
104 		/// inits cipher with the current key
105 		void updateKey () @nogc {
106 			cipher.start(true, key);
107 		}
108 
109 		/// increment the counter by 1
110 		void incrementCounter() @nogc {
111 			for (uint i = 0; i < counter.length; i++) {
112 				counter[i]++;
113 				if (counter[i] != 0) {
114 					break;
115 				}
116 			}
117 		}
118 
119 		/**
120 		 * Fill buffer with pseudo random blocks.
121 		 * 
122 		 * Params:
123 		 * buffer =	Fill this buffer with pseudo random blocks. Length must be multiple of blockSize (probably 16 or 32).
124 		 */
125 		void generateBlocks(ubyte[] buffer) @nogc
126 		in {
127 			assert(buffer.length % blockSize == 0, 
128 				"invalid input buffer size, multiple of blockSize required");
129 			assert(initialized, "PRNG not yet initalized. Call `addSeed()` first.");
130 		}
131 		body {
132 			foreach(chunk; chunks(buffer, blockSize)) {
133 				cipher.processBlock(counter, chunk);
134 				incrementCounter();
135 			}
136 		}
137 
138 		/**
139 		 * Fill the buffer with pseudo random data. Buffer size is limitet to 2^20 bytes.
140 		 * 
141 		 * Params:
142 		 * buffer = buffer for PRNG data
143 		 */
144 		void pseudoRandomData(ubyte[] buffer) @nogc
145 		in {
146 			assert(buffer.length <= reseedLimit, "won't generate more than reseedLimit bytes in one request");
147 		}
148 		body {
149 			scope(exit) {	// ensure that the key is changed after each request
150 				generateBlocks(key);
151 				updateKey();
152 			}
153 
154 			immutable size_t remaining = buffer.length % blockSize;
155 			generateBlocks(buffer[0..$-remaining]);
156 
157 			if(remaining) {
158 				generateBlocks(internalBuffer);
159 				scope(exit) {
160 					internalBuffer[] = 0; // wipe the buffer on exit
161 				}
162 
163 				buffer[$-remaining..$] = internalBuffer[0..remaining];
164 			}
165 		}
166 	}
167 }