source: trunk/eraser/Eraser.Manager/EntropyPoller.cs @ 2571

Revision 2571, 10.3 KB checked in by lowjoel, 3 years ago (diff)

Use our MemoryXor? function to invert the pool, instead of writing another loop. Performance for this code is about 10% faster than the unsafe loop, on average, on x64.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Rev URL
Line 
1/*
2 * $Id$
3 * Copyright 2008-2012 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By: Kasra Nassiri <cjax@users.sourceforge.net>
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.Collections.Generic;
24using System.Text;
25
26using System.Threading;
27using System.Security.Cryptography;
28using System.Runtime.InteropServices;
29using System.Diagnostics;
30using System.Reflection;
31
32using Eraser.Plugins;
33using Eraser.Plugins.ExtensionPoints;
34
35namespace Eraser.Manager
36{
37    /// <summary>
38    /// A class which uses EntropyPoll class to fetch system data as a source of
39    /// randomness at "regular" but "random" intervals
40    /// </summary>
41    public class EntropyPoller
42    {
43        /// <summary>
44        /// Constructor.
45        /// </summary>
46        public EntropyPoller()
47        {
48            //Create the pool and its complement.
49            Pool = new byte[sizeof(uint) << 7];
50            PoolInvert = new byte[Pool.Length];
51            for (uint i = 0, j = (uint)PoolInvert.Length; i < j; ++i)
52                PoolInvert[i] = byte.MaxValue;
53
54            //Handle the Entropy Source Registered event.
55            Host.Instance.EntropySources.Registered += OnEntropySourceRegistered;
56
57            //Meanwhile, add all entropy sources already registered.
58            foreach (IEntropySource source in Host.Instance.EntropySources)
59                AddEntropySource(source);
60
61            //Then start the thread which maintains the pool.
62            Thread = new Thread(Main);
63            Thread.Start();
64        }
65
66        /// <summary>
67        /// The PRNG entropy thread. This thread will run in the background, getting
68        /// random data to be used for entropy. This will maintain the integrity
69        /// of generated data from the PRNGs.
70        /// </summary>
71        private void Main()
72        {
73            //Maintain the time we last provided entropy to the PRNGs. We will only
74            //provide entropy every 10 minutes.
75            DateTime lastAddedEntropy = DateTime.Now;
76            TimeSpan managerEntropySpan = new TimeSpan(0, 10, 0);
77
78            while (Thread.ThreadState != System.Threading.ThreadState.AbortRequested)
79            {
80                lock (EntropySources)
81                    foreach (IEntropySource src in EntropySources)
82                    {
83                        byte[] entropy = src.GetEntropy();
84                        AddEntropy(entropy);
85                    }
86
87                //Sleep for a "random" period between roughly [2, 5) seconds from now
88                Thread.Sleep(2000 + (int)(DateTime.Now.Ticks % 2999));
89
90                //Send entropy to the PRNGs for new seeds.
91                DateTime now = DateTime.Now;
92                if (now - lastAddedEntropy > managerEntropySpan)
93                {
94                    Host.Instance.Prngs.AddEntropy(GetPool());
95                    lastAddedEntropy = now;
96                }
97            }
98        }
99
100        /// <summary>
101        /// Stops the execution of the thread.
102        /// </summary>
103        public void Abort()
104        {
105            Thread.Abort();
106        }
107
108        /// <summary>
109        /// Handles the OnEntropySourceRegistered event so we can register them with
110        /// ourselves.
111        /// </summary>
112        /// <param name="sender">The object which was registered.</param>
113        /// <param name="e">Event argument.</param>
114        private void OnEntropySourceRegistered(object sender, EventArgs e)
115        {
116            AddEntropySource((IEntropySource)sender);
117        }
118
119        /// <summary>
120        /// Adds a new Entropy Source to the Poller.
121        /// </summary>
122        /// <param name="source">The EntropySource object to add.</param>
123        public void AddEntropySource(IEntropySource source)
124        {
125            lock (EntropySources)
126                EntropySources.Add(source);
127
128            AddEntropy(source.GetPrimer());
129            MixPool();
130
131            //Apply "whitening" effect. Try to mix the pool using RIPEMD-160 to strengthen
132            //the cryptographic strength of the pool.
133            //There is a need to catch the InvalidOperationException because if Eraser is
134            //running under an OS with FIPS-compliance mode the RIPEMD-160 algorithm cannot
135            //be used.
136            try
137            {
138                using (HashAlgorithm hash = new RIPEMD160Managed())
139                    MixPool(hash);
140            }
141            catch (InvalidOperationException)
142            {
143            }
144        }
145
146        /// <summary>
147        /// Retrieves the current contents of the entropy pool.
148        /// </summary>
149        /// <returns>A byte array containing all the randomness currently found.</returns>
150        public byte[] GetPool()
151        {
152            //Mix and invert the pool
153            MixPool();
154            InvertPool();
155
156            //Return a safe copy
157            lock (PoolLock)
158            {
159                byte[] result = new byte[Pool.Length];
160                Pool.CopyTo(result, 0);
161
162                return result;
163            }
164        }
165
166        /// <summary>
167        /// Inverts the contents of the pool.
168        /// </summary>
169        private void InvertPool()
170        {
171            lock (PoolLock)
172            {
173                MemoryXor(PoolInvert, 0, Pool, 0, Pool.Length);
174            }
175        }
176
177        /// <summary>
178        /// Mixes the contents of the pool.
179        /// </summary>
180        private void MixPool(HashAlgorithm hash)
181        {
182            lock (PoolLock)
183            {
184                //Mix the last 128 bytes first.
185                const int mixBlockSize = 128;
186                int hashSize = hash.HashSize / 8;
187                hash.ComputeHash(Pool, Pool.Length - mixBlockSize, mixBlockSize).CopyTo(Pool, 0);
188
189                //Then mix the following bytes until wraparound is required
190                int i = 0;
191                for (; i < Pool.Length - hashSize; i += hashSize)
192                    Buffer.BlockCopy(hash.ComputeHash(Pool, i,
193                        Math.Min(mixBlockSize, Pool.Length - i)), 0, Pool, i,
194                        Math.Min(hashSize, Pool.Length - i));
195
196                //Mix the remaining blocks which require copying from the front
197                byte[] combinedBuffer = new byte[mixBlockSize];
198                for (; i < Pool.Length; i += hashSize)
199                {
200                    int remainder = Pool.Length - i;
201                    Buffer.BlockCopy(Pool, i, combinedBuffer, 0, remainder);
202                    Buffer.BlockCopy(Pool, 0, combinedBuffer, remainder,
203                        mixBlockSize - remainder);
204
205                    Buffer.BlockCopy(hash.ComputeHash(combinedBuffer, 0, mixBlockSize), 0,
206                        Pool, i, Math.Min(hashSize, remainder));
207                }
208            }
209        }
210
211        /// <summary>
212        /// Mixes the contents of the entropy pool using the currently specified default
213        /// algorithm.
214        /// </summary>
215        private void MixPool()
216        {
217            using (HashAlgorithm hash = new SHA1CryptoServiceProvider())
218                MixPool(hash);
219        }
220
221        /// <summary>
222        /// Adds data which is random to the pool
223        /// </summary>
224        /// <param name="entropy">An array of data which will be XORed with pool
225        /// contents.</param>
226        public void AddEntropy(byte[] entropy)
227        {
228            lock (PoolLock)
229            {
230                for (int i = entropy.Length, j = 0; i > 0; )
231                {
232                    //Bring the pool position back to the front if we are at our end
233                    if (PoolPosition >= Pool.Length)
234                    {
235                        PoolPosition = 0;
236                        MixPool();
237                    }
238
239                    int amountToMix = Math.Min(i, Pool.Length - PoolPosition);
240                    MemoryXor(entropy, j, Pool, PoolPosition, amountToMix);
241                    i -= amountToMix;
242                    j += amountToMix;
243                    PoolPosition += amountToMix;
244                }
245            }
246        }
247
248        /// <summary>
249        /// Copies a specified number of bytes from a source array starting at a particular
250        /// offset to a destination array starting at a particular offset.
251        /// </summary>
252        /// <param name="src">The source buffer.</param>
253        /// <param name="srcOffset">The zero-based byte offset into src.</param>
254        /// <param name="dst">The destination buffer.</param>
255        /// <param name="dstOffset">The zero-based byte offset into dst.</param>
256        /// <param name="count">The number of bytes to copy.</param>
257        ///
258        /// <exception cref="System.ArgumentNullException"><paramref name="src"/> or
259        /// <paramref name="dst"/> is null.</exception>
260        /// <exception cref="System.ArgumentException"><paramref name="src"/> or
261        /// <paramref name="dst"/> is not an array of primitives or the length of
262        /// <paramref name="src"/> is less than <paramref name="srcOffset"/> +
263        /// <paramref name="count"/> or the length of <paramref name="dst"/>
264        /// is less than <paramref name="dstOffset"/> + <paramref name="count"/>.</exception>
265        /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="srcOffset"/>,
266        /// <paramref name="dstOffset"/>, or <paramref name="count"/> is less than 0.</exception>
267        private static void MemoryXor(byte[] src, int srcOffset, byte[] dst, int dstOffset, int count)
268        {
269            if (src == null || dst == null)
270                throw new ArgumentNullException();
271            if (src.Length < srcOffset + count ||
272                dst.Length < dstOffset + count)
273                throw new ArgumentException();
274            if (srcOffset < 0 || dstOffset < 0 || count < 0)
275                throw new ArgumentOutOfRangeException();
276           
277            unsafe
278            {
279                fixed (byte* pSrc = src)
280                fixed (byte* pDst = dst)
281                    MemoryXor64(pDst + dstOffset, pSrc + srcOffset, (uint)count);
282            }
283        }
284
285        /// <summary>
286
287        /// XOR's <paramref name="source"/> onto <paramref name="destination"/>, at the
288        /// natural word alignment of the current processor.
289        /// </summary>
290        /// <typeparam name="T">An integral type indicating the natural word of the
291        /// processor.</typeparam>
292        /// <param name="destination">The destination buffer to XOR to.</param>
293        /// <param name="source">The source buffer to XOR with.</param>
294        /// <param name="length">The amount of data, in bytes, to XOR.</param>
295        private static unsafe void MemoryXor64(byte* destination, byte* source, uint length)
296        {
297            //XOR the buffers using a processor word
298            {
299                ulong* wDestination = (ulong*)destination;
300                ulong* wSource = (ulong*)source;
301                for (uint i = 0, j = (uint)(length / sizeof(ulong)); i < j; ++i)
302                    *wDestination++ ^= *wSource++;
303            }
304
305            //XOR the remaining bytes
306            {
307                uint i = length - (length % sizeof(ulong));
308                destination += i;
309                source += i;
310                for (; i < length; ++i)
311                    *destination++ ^= *source++;
312            }
313        }
314
315        /// <summary>
316        /// The pool of data which we currently maintain.
317        /// </summary>
318        private byte[] Pool;
319
320        /// <summary>
321        /// A pool, the same size as <see cref="Pool"/>, but containing all bitwise 1's
322        /// for XOR for pool inversion
323        /// </summary>
324        private byte[] PoolInvert;
325
326        /// <summary>
327        /// The next position where entropy will be added to the pool.
328        /// </summary>
329        private int PoolPosition;
330
331        /// <summary>
332        /// The lock guarding the pool array and the current entropy addition index.
333        /// </summary>
334        private object PoolLock = new object();
335
336        /// <summary>
337        /// The thread object.
338        /// </summary>
339        private Thread Thread;
340
341        /// <summary>
342        /// The list of entropy sources registered with the Poller.
343        /// </summary>
344        private List<IEntropySource> EntropySources = new List<IEntropySource>();
345    }
346}
Note: See TracBrowser for help on using the repository browser.