1 module dcrypt.crypto.random.fortuna.fortuna; 2 3 public import dcrypt.crypto.random.drng; 4 public import dcrypt.crypto.blockcipher; 5 public import dcrypt.crypto.digest; 6 7 import dcrypt.crypto.random.fortuna.generator; 8 import dcrypt.crypto.random.fortuna.accumulator; 9 10 import dcrypt.crypto.engines.aes; 11 import dcrypt.crypto.digests.sha2; 12 13 import std.datetime; 14 15 16 /// OOP wrapper 17 public alias WrapperPRNG!Fortuna FortunaRNG; 18 public alias FortunaCore!(FortunaGenerator!(AES, SHA256)) Fortuna; 19 20 /// Get some random bytes from Fortuna. 21 unittest { 22 FortunaCore!(FortunaGenerator!(AES, SHA256)) fortuna; 23 24 ubyte[61] buf1; 25 ubyte[buf1.length] buf2; 26 fortuna.addSeed([0,1,2,3]); 27 foreach(i;0..10) { 28 buf2 = buf1; 29 fortuna.nextBytes(buf1); 30 31 if(i > 0) { 32 assert(buf2 != buf1, "data is not random"); 33 } 34 } 35 } 36 37 38 /// Add real entropy to the global accumulator. 39 /// 40 /// Params: 41 /// sourceID = The ID of the entropy source. Can actually be any number. 42 /// pool = The ID of the pool to add the entropy. 43 /// seed = Random data. 44 @safe 45 public void addEntropy(in ubyte sourceID, in size_t pool, in ubyte[] seed...) nothrow @nogc 46 in { 47 assert(pool < Accumulator.pools, "Pool ID out of range."); 48 } 49 body { 50 assert(globalAcc !is null, "Accumulator not initialized!"); 51 globalAcc.addEntropy(sourceID, pool, seed); 52 53 } 54 55 /// Get 32 bytes of unpredictable* seed from the global accumulator. 56 /// 57 /// Note: 58 /// * The seed can only be unpredictable if the accumulator gets enough entropy from entropy sources. 59 /// 60 /// Params: 61 /// buf = A buffer for exactly 32 bytes. 62 /// 63 /// Throws: 64 /// Error = if buffer has wrong size. 65 @safe 66 private void getSeed(ubyte[] buf) nothrow @nogc 67 in { 68 assert(buf.length == 32, "buf must be 32 bytes long."); 69 } 70 body { 71 assert(globalAcc !is null, "Accumulator not initialized!"); 72 globalAcc.extractEntropy(buf); 73 } 74 75 private shared Accumulator globalAcc; /// The entropy accumulator is used globally. 76 77 /// initialize and seed the global accumulator 78 private shared static this() { 79 globalAcc = new shared Accumulator; 80 81 // seed the accumulator 82 ubyte[32] buf; 83 foreach(i;0..4096/buf.length) { 84 import dcrypt.crypto.random.fortuna.sources.systemtick; 85 86 getTimingEntropy(buf); 87 addEntropy(0, i%Accumulator.pools, buf); 88 89 } 90 } 91 92 93 static assert(isRNGWithInput!(FortunaCore!(FortunaGenerator!(AES, SHA256))), "Fortuna does not meet requirements for PRNGs."); 94 95 /// FortunaCore is meant to be the mothership of the PRNGs. It should run as a singleton - 96 /// one instance per application that handles the accumulator and entropy sources. 97 /// 98 /// Params: 99 /// Cipher = A block cipher. 100 /// Digest = A hash algorithm. 101 @safe 102 private struct FortunaCore(RNGWithInput) if(isRNGWithInput!RNGWithInput) { 103 nothrow: 104 105 public { 106 107 enum name = "FortunaCore"; 108 enum isDeterministic = true; 109 110 /// Add entropy to generators state but not to the accumulator. 111 @safe 112 void addSeed(in ubyte[] seed...) nothrow @nogc { 113 // pass this call directly to the generator 114 prng.addSeed(seed); 115 } 116 117 /// Fill the buffer with random bytes. 118 void nextBytes(ubyte[] buffer) nothrow @nogc { 119 randomData(buffer); 120 } 121 } 122 123 124 private { 125 enum minReseedInterval = 100; /// minimal time in ms between reseeds 126 127 RNGWithInput prng; 128 129 size_t reseedCount = 0; /// increment each time reseed() is called 130 ulong lastReseed = 0; /// time of the last reseed in ms 131 132 @safe 133 void randomData(ubyte[] buffer) nothrow @nogc 134 { 135 136 if( 137 //a.getLength() >= MINPOOLSIZE && 138 TickDuration.currSystemTick.msecs - lastReseed > minReseedInterval) { 139 140 reseed(); 141 142 } 143 144 if(lastReseed == 0 && reseedCount == 0) { 145 assert(false, "PRNG not seeded yet"); 146 } 147 148 // ready to generate the random data 149 prng.nextBytes(buffer); 150 } 151 152 /// get entropy from accumulator 153 @trusted 154 private void reseed() nothrow @nogc { 155 ubyte[32] buf; 156 157 getSeed(buf); 158 159 assert(std.algorithm.any!"a != 0"(buf[]), "Got only zeros from accumulator instead of noise!"); 160 161 prng.addSeed(buf); 162 163 lastReseed = TickDuration.currSystemTick.msecs; 164 ++reseedCount; 165 166 } 167 168 } 169 }