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

Revision 2571, 10.3 KB checked in by lowjoel, 14 months 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.