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 }