1 module dcrypt.random.fortuna.generator;
2 
3 import std.range: chunks;
4 
5 import dcrypt.random.drng;
6 import dcrypt.blockcipher.blockcipher;
7 import dcrypt.digest;
8 
9 ///	generate a deterministic PRNG sequence
10 @safe unittest {
11 	import dcrypt.blockcipher.aes;
12 	import dcrypt.digests.sha2;
13 
14 	FortunaGenerator!(AES, SHA256) prng;
15 	prng.addSeed([0]);
16 
17 	ubyte[71] random;
18 	prng.nextBytes(random);
19 
20 	assert(random == x"2fd720d5d7f93dc8371586ae8c09547095613e2cf8967206f8d16d5717cf15a53beae29b2cf9fc0443ae6c37fd1f11aefb13061415c4d5f27d876cb67a63ba592af029f0447815",
21 		"Unexpected output of deterministic PRNG.");
22 
23 	ubyte[random.length] random2;
24 
25 	prng.nextBytes(random2);
26 
27 	assert(random != random2, "PRNG produced twice the same data!");
28 }
29 
30 // Test if FortunaGenerator fullfills requirements to be a PRNG.
31 import dcrypt.blockcipher.aes;
32 import dcrypt.digests.sha2;
33 static assert(isDRNGWithInput!(FortunaGenerator!(AES, SHA256)), "FortunaGenerator violates requirements of isDRNGWithInput!");
34 
35 
36 
37 /// This PRNG forms a base component of the Fortuna PRNG as proposed by Bruce Schneier & Niels Ferguson (PRNG with input).
38 /// The Generator can be used stand alone as deterministic PRNG (DRNG). It won't gather entropy on its own and
39 /// provided with the same seed it will always generate the same sequence of bytes for the same underlying 
40 /// block cipher and hash algorithm.
41 /// 
42 /// Note: Generator MUST be seeded before generating pseudo random data either with `addSeed()` or by passing the seed to the constructor.
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 @nogc {
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 		void setSeed(in ubyte[] seed...) @nogc {
74 			counter[] = 0;
75 			internalBuffer[] = 0;
76 			key[] = 0;
77 
78 			reseed(seed);
79 		}
80 
81 	}
82 
83 	private {
84 		enum reseedLimit = 1<<20;			/// Force a reseed after generating this amount of bytes.
85 		enum blockSize = Cipher.blockSize;
86 		
87 		ubyte[blockSize] counter;			/// Counter for CTR mode.
88 		ubyte[blockSize] internalBuffer;	
89 		ubyte[32] key;						/// Secret encryption key.
90 		
91 		Cipher cipher;
92 
93 		bool initialized = false;
94 	}
95 
96 	private nothrow {
97 
98 		/// compute a new key: newKey = Hash(oldKey | seed)
99 		void reseed(in ubyte[] seed...) @nogc {
100 			Digest digest;
101 			digest.put(key);
102 			digest.put(seed);
103 			key = digest.finish();
104 
105 			updateKey();
106 
107 			incrementCounter();
108 
109 			initialized = true;
110 		}
111 
112 		/// inits cipher with the current key
113 		void updateKey () @nogc {
114 			cipher.start(true, key);
115 		}
116 
117 		/// increment the counter by 1
118 		void incrementCounter() @nogc {
119 			for (uint i = 0; i < counter.length; i++) {
120 				counter[i]++;
121 				if (counter[i] != 0) {
122 					break;
123 				}
124 			}
125 		}
126 
127 		/**
128 		 * Fill buffer with pseudo random blocks.
129 		 * 
130 		 * Params:
131 		 * buffer =	Fill this buffer with pseudo random blocks. Length must be multiple of blockSize (probably 16 or 32).
132 		 */
133 		void generateBlocks(ubyte[] buffer) @nogc
134 		in {
135 			assert(buffer.length % blockSize == 0, 
136 				"invalid input buffer size, multiple of blockSize required");
137 			assert(initialized, "PRNG not yet initalized. Call `addSeed()` first.");
138 		}
139 		body {
140 			foreach(chunk; chunks(buffer, blockSize)) {
141 				cipher.processBlock(counter, chunk);
142 				incrementCounter();
143 			}
144 		}
145 
146 		/**
147 		 * Fill the buffer with pseudo random data. Buffer size is limitet to 2^20 bytes.
148 		 * 
149 		 * Params:
150 		 * buffer = buffer for PRNG data
151 		 */
152 		void pseudoRandomData(ubyte[] buffer) @nogc
153 		in {
154 			assert(buffer.length <= reseedLimit, "won't generate more than reseedLimit bytes in one request");
155 		}
156 		body {
157 			scope(exit) {	// ensure that the key is changed after each request
158 				generateBlocks(key);
159 				updateKey();
160 			}
161 
162 			immutable size_t remaining = buffer.length % blockSize;
163 			generateBlocks(buffer[0..$-remaining]);
164 
165 			if(remaining) {
166 				generateBlocks(internalBuffer);
167 				scope(exit) {
168 					internalBuffer[] = 0; // wipe the buffer on exit
169 				}
170 
171 				buffer[$-remaining..$] = internalBuffer[0..remaining];
172 			}
173 		}
174 	}
175 
176 	~this() {
177 		import dcrypt.util: wipe;
178 
179 		wipe(key);
180 		wipe(counter);
181 		wipe(internalBuffer);
182 	}
183 }