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