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

Revision 2572, 13.2 KB checked in by lowjoel, 2 years ago (diff)

Added a priority list for the primary and secondary hash algorithms. Favour the Crypto Next Gen APIs first, since they have higher speed on Vista+, fallback to the CryptoAPI implementations if they can't be created, and finally use the Managed implementations if the above two fail.

Should close #406.

  • 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;
34using Eraser.Util;
35
36namespace Eraser.Manager
37{
38    /// <summary>
39    /// A class which uses EntropyPoll class to fetch system data as a source of
40    /// randomness at "regular" but "random" intervals
41    /// </summary>
42    public class EntropyPoller
43    {
44        /// <summary>
45        /// Constructor.
46        /// </summary>
47        public EntropyPoller()
48        {
49            //Create the pool and its complement.
50            Pool = new byte[sizeof(uint) << 7];
51            PoolInvert = new byte[Pool.Length];
52            for (uint i = 0, j = (uint)PoolInvert.Length; i < j; ++i)
53                PoolInvert[i] = byte.MaxValue;
54
55            //Handle the Entropy Source Registered event.
56            Host.Instance.EntropySources.Registered += OnEntropySourceRegistered;
57
58            //Meanwhile, add all entropy sources already registered.
59            foreach (IEntropySource source in Host.Instance.EntropySources)
60                AddEntropySource(source);
61
62            //Then start the thread which maintains the pool.
63            Thread = new Thread(Main);
64            Thread.Start();
65        }
66
67        /// <summary>
68        /// The PRNG entropy thread. This thread will run in the background, getting
69        /// random data to be used for entropy. This will maintain the integrity
70        /// of generated data from the PRNGs.
71        /// </summary>
72        private void Main()
73        {
74            //Maintain the time we last provided entropy to the PRNGs. We will only
75            //provide entropy every 10 minutes.
76            DateTime lastAddedEntropy = DateTime.Now;
77            TimeSpan managerEntropySpan = new TimeSpan(0, 10, 0);
78
79            while (Thread.ThreadState != System.Threading.ThreadState.AbortRequested)
80            {
81                lock (EntropySources)
82                    foreach (IEntropySource src in EntropySources)
83                    {
84                        byte[] entropy = src.GetEntropy();
85                        AddEntropy(entropy);
86                    }
87
88                //Sleep for a "random" period between roughly [2, 5) seconds from now
89                Thread.Sleep(2000 + (int)(DateTime.Now.Ticks % 2999));
90
91                //Send entropy to the PRNGs for new seeds.
92                DateTime now = DateTime.Now;
93                if (now - lastAddedEntropy > managerEntropySpan)
94                {
95                    Host.Instance.Prngs.AddEntropy(GetPool());
96                    lastAddedEntropy = now;
97                }
98            }
99        }
100
101        /// <summary>
102        /// Stops the execution of the thread.
103        /// </summary>
104        public void Abort()
105        {
106            Thread.Abort();
107        }
108
109        /// <summary>
110        /// Handles the OnEntropySourceRegistered event so we can register them with
111        /// ourselves.
112        /// </summary>
113        /// <param name="sender">The object which was registered.</param>
114        /// <param name="e">Event argument.</param>
115        private void OnEntropySourceRegistered(object sender, EventArgs e)
116        {
117            AddEntropySource((IEntropySource)sender);
118        }
119
120        /// <summary>
121        /// Adds a new Entropy Source to the Poller.
122        /// </summary>
123        /// <param name="source">The EntropySource object to add.</param>
124        public void AddEntropySource(IEntropySource source)
125        {
126            lock (EntropySources)
127                EntropySources.Add(source);
128
129            AddEntropy(source.GetPrimer());
130            MixPool();
131
132            //Apply "whitening" effect. Try to mix the pool using RIPEMD-160 to strengthen
133            //the cryptographic strength of the pool.
134            //There is a need to catch the InvalidOperationException because if Eraser is
135            //running under an OS with FIPS-compliance mode the RIPEMD-160 algorithm cannot
136            //be used.
137            HashAlgorithm secondaryHash = GetSecondaryHash();
138            if (secondaryHash != null)
139                MixPool(secondaryHash);
140        }
141
142        /// <summary>
143        /// Retrieves the current contents of the entropy pool.
144        /// </summary>
145        /// <returns>A byte array containing all the randomness currently found.</returns>
146        public byte[] GetPool()
147        {
148            //Mix and invert the pool
149            MixPool();
150            InvertPool();
151
152            //Return a safe copy
153            lock (PoolLock)
154            {
155                byte[] result = new byte[Pool.Length];
156                Pool.CopyTo(result, 0);
157
158                return result;
159            }
160        }
161
162        /// <summary>
163        /// Inverts the contents of the pool.
164        /// </summary>
165        private void InvertPool()
166        {
167            lock (PoolLock)
168            {
169                MemoryXor(PoolInvert, 0, Pool, 0, Pool.Length);
170            }
171        }
172
173        /// <summary>
174        /// Mixes the contents of the pool.
175        /// </summary>
176        private void MixPool(HashAlgorithm hash)
177        {
178            lock (PoolLock)
179            {
180                //Mix the last 128 bytes first.
181                const int mixBlockSize = 128;
182                int hashSize = hash.HashSize / 8;
183                hash.ComputeHash(Pool, Pool.Length - mixBlockSize, mixBlockSize).CopyTo(Pool, 0);
184
185                //Then mix the following bytes until wraparound is required
186                int i = 0;
187                for (; i < Pool.Length - hashSize; i += hashSize)
188                    Buffer.BlockCopy(hash.ComputeHash(Pool, i,
189                        Math.Min(mixBlockSize, Pool.Length - i)), 0, Pool, i,
190                        Math.Min(hashSize, Pool.Length - i));
191
192                //Mix the remaining blocks which require copying from the front
193                byte[] combinedBuffer = new byte[mixBlockSize];
194                for (; i < Pool.Length; i += hashSize)
195                {
196                    int remainder = Pool.Length - i;
197                    Buffer.BlockCopy(Pool, i, combinedBuffer, 0, remainder);
198                    Buffer.BlockCopy(Pool, 0, combinedBuffer, remainder,
199                        mixBlockSize - remainder);
200
201                    Buffer.BlockCopy(hash.ComputeHash(combinedBuffer, 0, mixBlockSize), 0,
202                        Pool, i, Math.Min(hashSize, remainder));
203                }
204            }
205        }
206
207        /// <summary>
208        /// Mixes the contents of the entropy pool using the currently specified default
209        /// algorithm.
210        /// </summary>
211        private void MixPool()
212        {
213            MixPool(GetPrimaryHash());
214        }
215
216        /// <summary>
217        /// Adds data which is random to the pool
218        /// </summary>
219        /// <param name="entropy">An array of data which will be XORed with pool
220        /// contents.</param>
221        public void AddEntropy(byte[] entropy)
222        {
223            lock (PoolLock)
224            {
225                for (int i = entropy.Length, j = 0; i > 0; )
226                {
227                    //Bring the pool position back to the front if we are at our end
228                    if (PoolPosition >= Pool.Length)
229                    {
230                        PoolPosition = 0;
231                        MixPool();
232                    }
233
234                    int amountToMix = Math.Min(i, Pool.Length - PoolPosition);
235                    MemoryXor(entropy, j, Pool, PoolPosition, amountToMix);
236                    i -= amountToMix;
237                    j += amountToMix;
238                    PoolPosition += amountToMix;
239                }
240            }
241        }
242
243        /// <summary>
244        /// Copies a specified number of bytes from a source array starting at a particular
245        /// offset to a destination array starting at a particular offset.
246        /// </summary>
247        /// <param name="src">The source buffer.</param>
248        /// <param name="srcOffset">The zero-based byte offset into src.</param>
249        /// <param name="dst">The destination buffer.</param>
250        /// <param name="dstOffset">The zero-based byte offset into dst.</param>
251        /// <param name="count">The number of bytes to copy.</param>
252        ///
253        /// <exception cref="System.ArgumentNullException"><paramref name="src"/> or
254        /// <paramref name="dst"/> is null.</exception>
255        /// <exception cref="System.ArgumentException"><paramref name="src"/> or
256        /// <paramref name="dst"/> is not an array of primitives or the length of
257        /// <paramref name="src"/> is less than <paramref name="srcOffset"/> +
258        /// <paramref name="count"/> or the length of <paramref name="dst"/>
259        /// is less than <paramref name="dstOffset"/> + <paramref name="count"/>.</exception>
260        /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="srcOffset"/>,
261        /// <paramref name="dstOffset"/>, or <paramref name="count"/> is less than 0.</exception>
262        private static void MemoryXor(byte[] src, int srcOffset, byte[] dst, int dstOffset, int count)
263        {
264            if (src == null || dst == null)
265                throw new ArgumentNullException();
266            if (src.Length < srcOffset + count ||
267                dst.Length < dstOffset + count)
268                throw new ArgumentException();
269            if (srcOffset < 0 || dstOffset < 0 || count < 0)
270                throw new ArgumentOutOfRangeException();
271           
272            unsafe
273            {
274                fixed (byte* pSrc = src)
275                fixed (byte* pDst = dst)
276                    MemoryXor64(pDst + dstOffset, pSrc + srcOffset, (uint)count);
277            }
278        }
279
280        /// <summary>
281
282        /// XOR's <paramref name="source"/> onto <paramref name="destination"/>, at the
283        /// natural word alignment of the current processor.
284        /// </summary>
285        /// <typeparam name="T">An integral type indicating the natural word of the
286        /// processor.</typeparam>
287        /// <param name="destination">The destination buffer to XOR to.</param>
288        /// <param name="source">The source buffer to XOR with.</param>
289        /// <param name="length">The amount of data, in bytes, to XOR.</param>
290        private static unsafe void MemoryXor64(byte* destination, byte* source, uint length)
291        {
292            //XOR the buffers using a processor word
293            {
294                ulong* wDestination = (ulong*)destination;
295                ulong* wSource = (ulong*)source;
296                for (uint i = 0, j = (uint)(length / sizeof(ulong)); i < j; ++i)
297                    *wDestination++ ^= *wSource++;
298            }
299
300            //XOR the remaining bytes
301            {
302                uint i = length - (length % sizeof(ulong));
303                destination += i;
304                source += i;
305                for (; i < length; ++i)
306                    *destination++ ^= *source++;
307            }
308        }
309
310        /// <summary>
311        /// Gets the primary hash algorithm used for pool mixing.
312        /// </summary>
313        /// <returns>A hash algorithm suitable for the current platform serving as the
314        /// primary hash algorithm for pool mixing.</returns>
315        /// <remarks>The instance returned need not be freed as it is cached.</remarks>
316        private static HashAlgorithm GetPrimaryHash()
317        {
318            if (PrimaryHashAlgorithmCache != null)
319                return PrimaryHashAlgorithmCache;
320
321            HashFactoryDelegate[] priorityList = new HashFactoryDelegate[] {
322                delegate() { return new SHA512Cng(); },
323                delegate() { return new SHA512CryptoServiceProvider(); },
324                delegate() { return new SHA512Managed(); },
325                delegate() { return new SHA256Cng(); },
326                delegate() { return new SHA256CryptoServiceProvider(); },
327                delegate() { return new SHA256Managed(); },
328                delegate() { return new SHA1Cng(); },
329                delegate() { return new SHA1CryptoServiceProvider(); },
330                delegate() { return new SHA1Managed(); }
331            };
332
333            foreach (HashFactoryDelegate func in priorityList)
334            {
335                try
336                {
337                    return PrimaryHashAlgorithmCache = func();
338                }
339                catch (InvalidOperationException)
340                {
341                }
342            }
343
344            throw new InvalidOperationException(S._("No suitable hash algorithms were found " +
345                "on this computer."));
346        }
347
348        /// <summary>
349        /// Gets the secondary hash algorithm used for pool mixing, serving roughly analogous
350        /// to key whitening.
351        /// </summary>
352        /// <returns>A hash algorithm suitable for the current platform serving as the
353        /// secondary hash algorithm for pool mixing, or null if no secondary hash
354        /// algorithm can be used (e.g. due to FIPS algorithm restrictions)</returns>
355        /// <remarks>The instance returned need not be freed as it is cached.</remarks>
356        private static HashAlgorithm GetSecondaryHash()
357        {
358            if (HasSecondaryHashAlgorithm)
359                return SecondaryHashAlgorithmCache;
360
361            HashFactoryDelegate[] priorityList = new HashFactoryDelegate[] {
362                delegate() { return new RIPEMD160Managed(); }
363            };
364
365            foreach (HashFactoryDelegate func in priorityList)
366            {
367                try
368                {
369                    SecondaryHashAlgorithmCache = func();
370                    HasSecondaryHashAlgorithm = true;
371                    return SecondaryHashAlgorithmCache;
372                }
373                catch (InvalidOperationException)
374                {
375                }
376            }
377
378            return null;
379        }
380
381        /// <summary>
382        /// The function prototype for factory delegates in the Primary and Secondary hash
383        /// priority lists.
384        /// </summary>
385        /// <returns></returns>
386        private delegate HashAlgorithm HashFactoryDelegate();
387
388        /// <summary>
389        /// The pool of data which we currently maintain.
390        /// </summary>
391        private byte[] Pool;
392
393        /// <summary>
394        /// A pool, the same size as <see cref="Pool"/>, but containing all bitwise 1's
395        /// for XOR for pool inversion
396        /// </summary>
397        private byte[] PoolInvert;
398
399        /// <summary>
400        /// The next position where entropy will be added to the pool.
401        /// </summary>
402        private int PoolPosition;
403
404        /// <summary>
405        /// The lock guarding the pool array and the current entropy addition index.
406        /// </summary>
407        private object PoolLock = new object();
408
409        /// <summary>
410        /// The thread object.
411        /// </summary>
412        private Thread Thread;
413
414        /// <summary>
415        /// The list of entropy sources registered with the Poller.
416        /// </summary>
417        private List<IEntropySource> EntropySources = new List<IEntropySource>();
418
419        /// <summary>
420        /// Cache object for <see cref="GetPrimaryHash"/>
421        /// </summary>
422        private static HashAlgorithm PrimaryHashAlgorithmCache;
423
424        /// <summary>
425        /// Cache object for <see cref="GetSecondaryHash"/>
426        /// </summary>
427        private static HashAlgorithm SecondaryHashAlgorithmCache;
428
429        /// <summary>
430        /// Cache for whether construction for a <see cref="SecondaryHashAlgorithmCache"/>
431        /// has been attempted.
432        /// </summary>
433        private static bool HasSecondaryHashAlgorithm;
434    }
435}
Note: See TracBrowser for help on using the repository browser.