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 }