OLD | NEW |
(Empty) | |
| 1 # -*- coding: ascii -*- |
| 2 # |
| 3 # FortunaAccumulator.py : Fortuna's internal accumulator |
| 4 # |
| 5 # Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net> |
| 6 # |
| 7 # =================================================================== |
| 8 # The contents of this file are dedicated to the public domain. To |
| 9 # the extent that dedication to the public domain is not available, |
| 10 # everyone is granted a worldwide, perpetual, royalty-free, |
| 11 # non-exclusive license to exercise all rights associated with the |
| 12 # contents of this file for any purpose whatsoever. |
| 13 # No rights are reserved. |
| 14 # |
| 15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| 16 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| 17 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| 18 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
| 19 # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
| 20 # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| 21 # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 22 # SOFTWARE. |
| 23 # =================================================================== |
| 24 |
| 25 __revision__ = "$Id$" |
| 26 |
| 27 import sys |
| 28 if sys.version_info[0] == 2 and sys.version_info[1] == 1: |
| 29 from Crypto.Util.py21compat import * |
| 30 from Crypto.Util.py3compat import * |
| 31 |
| 32 from binascii import b2a_hex |
| 33 import time |
| 34 import warnings |
| 35 |
| 36 from Crypto.pct_warnings import ClockRewindWarning |
| 37 import SHAd256 |
| 38 |
| 39 import FortunaGenerator |
| 40 |
| 41 class FortunaPool(object): |
| 42 """Fortuna pool type |
| 43 |
| 44 This object acts like a hash object, with the following differences: |
| 45 |
| 46 - It keeps a count (the .length attribute) of the number of bytes that |
| 47 have been added to the pool |
| 48 - It supports a .reset() method for in-place reinitialization |
| 49 - The method to add bytes to the pool is .append(), not .update(). |
| 50 """ |
| 51 |
| 52 digest_size = SHAd256.digest_size |
| 53 |
| 54 def __init__(self): |
| 55 self.reset() |
| 56 |
| 57 def append(self, data): |
| 58 self._h.update(data) |
| 59 self.length += len(data) |
| 60 |
| 61 def digest(self): |
| 62 return self._h.digest() |
| 63 |
| 64 def hexdigest(self): |
| 65 if sys.version_info[0] == 2: |
| 66 return b2a_hex(self.digest()) |
| 67 else: |
| 68 return b2a_hex(self.digest()).decode() |
| 69 |
| 70 def reset(self): |
| 71 self._h = SHAd256.new() |
| 72 self.length = 0 |
| 73 |
| 74 def which_pools(r): |
| 75 """Return a list of pools indexes (in range(32)) that are to be included dur
ing reseed number r. |
| 76 |
| 77 According to _Practical Cryptography_, chapter 10.5.2 "Pools": |
| 78 |
| 79 "Pool P_i is included if 2**i is a divisor of r. Thus P_0 is used |
| 80 every reseed, P_1 every other reseed, P_2 every fourth reseed, etc." |
| 81 """ |
| 82 # This is a separate function so that it can be unit-tested. |
| 83 assert r >= 1 |
| 84 retval = [] |
| 85 mask = 0 |
| 86 for i in range(32): |
| 87 # "Pool P_i is included if 2**i is a divisor of [reseed_count]" |
| 88 if (r & mask) == 0: |
| 89 retval.append(i) |
| 90 else: |
| 91 break # optimization. once this fails, it always fails |
| 92 mask = (mask << 1) | 1L |
| 93 return retval |
| 94 |
| 95 class FortunaAccumulator(object): |
| 96 |
| 97 # An estimate of how many bytes we must append to pool 0 before it will |
| 98 # contain 128 bits of entropy (with respect to an attack). We reseed the |
| 99 # generator only after pool 0 contains `min_pool_size` bytes. Note that |
| 100 # unlike with some other PRNGs, Fortuna's security does not rely on the |
| 101 # accuracy of this estimate---we can accord to be optimistic here. |
| 102 min_pool_size = 64 # size in bytes |
| 103 |
| 104 # If an attacker can predict some (but not all) of our entropy sources, the |
| 105 # `min_pool_size` check may not be sufficient to prevent a successful state |
| 106 # compromise extension attack. To resist this attack, Fortuna spreads the |
| 107 # input across 32 pools, which are then consumed (to reseed the output |
| 108 # generator) with exponentially decreasing frequency. |
| 109 # |
| 110 # In order to prevent an attacker from gaining knowledge of all 32 pools |
| 111 # before we have a chance to fill them with enough information that the |
| 112 # attacker cannot predict, we impose a rate limit of 10 reseeds/second (one |
| 113 # per 100 ms). This ensures that a hypothetical 33rd pool would only be |
| 114 # needed after a minimum of 13 years of sustained attack. |
| 115 reseed_interval = 0.100 # time in seconds |
| 116 |
| 117 def __init__(self): |
| 118 self.reseed_count = 0 |
| 119 self.generator = FortunaGenerator.AESGenerator() |
| 120 self.last_reseed = None |
| 121 |
| 122 # Initialize 32 FortunaPool instances. |
| 123 # NB: This is _not_ equivalent to [FortunaPool()]*32, which would give |
| 124 # us 32 references to the _same_ FortunaPool instance (and cause the |
| 125 # assertion below to fail). |
| 126 self.pools = [FortunaPool() for i in range(32)] # 32 pools |
| 127 assert(self.pools[0] is not self.pools[1]) |
| 128 |
| 129 def _forget_last_reseed(self): |
| 130 # This is not part of the standard Fortuna definition, and using this |
| 131 # function frequently can weaken Fortuna's ability to resist a state |
| 132 # compromise extension attack, but we need this in order to properly |
| 133 # implement Crypto.Random.atfork(). Otherwise, forked child processes |
| 134 # might continue to use their parent's PRNG state for up to 100ms in |
| 135 # some cases. (e.g. CVE-2013-1445) |
| 136 self.last_reseed = None |
| 137 |
| 138 def random_data(self, bytes): |
| 139 current_time = time.time() |
| 140 if (self.last_reseed is not None and self.last_reseed > current_time): #
Avoid float comparison to None to make Py3k happy |
| 141 warnings.warn("Clock rewind detected. Resetting last_reseed.", Clock
RewindWarning) |
| 142 self.last_reseed = None |
| 143 if (self.pools[0].length >= self.min_pool_size and |
| 144 (self.last_reseed is None or |
| 145 current_time > self.last_reseed + self.reseed_interval)): |
| 146 self._reseed(current_time) |
| 147 # The following should fail if we haven't seeded the pool yet. |
| 148 return self.generator.pseudo_random_data(bytes) |
| 149 |
| 150 def _reseed(self, current_time=None): |
| 151 if current_time is None: |
| 152 current_time = time.time() |
| 153 seed = [] |
| 154 self.reseed_count += 1 |
| 155 self.last_reseed = current_time |
| 156 for i in which_pools(self.reseed_count): |
| 157 seed.append(self.pools[i].digest()) |
| 158 self.pools[i].reset() |
| 159 |
| 160 seed = b("").join(seed) |
| 161 self.generator.reseed(seed) |
| 162 |
| 163 def add_random_event(self, source_number, pool_number, data): |
| 164 assert 1 <= len(data) <= 32 |
| 165 assert 0 <= source_number <= 255 |
| 166 assert 0 <= pool_number <= 31 |
| 167 self.pools[pool_number].append(bchr(source_number)) |
| 168 self.pools[pool_number].append(bchr(len(data))) |
| 169 self.pools[pool_number].append(data) |
| 170 |
| 171 # vim:set ts=4 sw=4 sts=4 expandtab: |
OLD | NEW |