示例#1
0
        private static SecureRandom CreateSecureRandom()
        {
            /*
             * We use our threaded seed generator to generate a good random seed. If the user
             * has a better random seed, he should use the constructor with a SecureRandom.
             *
             * Hopefully, 20 bytes in fast mode are good enough.
             */
            byte[] seed = new ThreadedSeedGenerator().GenerateSeed(20, true);

            return(new SecureRandom(seed));
        }
示例#2
0
        /*
         * Both streams can be the same object
         */
        public TlsProtocolHandler(
            Stream inStr,
            Stream outStr)
        {
            /*
             * We use a threaded seed generator to generate a good random
             * seed. If the user has a better random seed, he should use
             * the constructor with a SecureRandom.
             *
             * Hopefully, 20 bytes in fast mode are good enough.
             */
            byte[] seed = new ThreadedSeedGenerator().GenerateSeed(20, true);

            this.random = new SecureRandom(seed);
            this.rs     = new RecordStream(this, inStr, outStr);
        }
示例#3
0
        public static void Main(string[] args)
        {
            /*
             * It's always good to call StartEarly.StartFillingEntropyPools() as early as possible when the application is launched.
             * But during benchmarking performance below, then it's not really fair to let them start early.
             */
            Benchmark("SystemRng", bytesBuffer =>
            {
                var mySystemRngCryptoServiceProvider = new SystemRng();
                mySystemRngCryptoServiceProvider.GetBytes(bytesBuffer);
                return(bytesBuffer);
            });
            Benchmark("RNGCryptoServiceProvider", bytesBuffer =>
            {
                using (var rng = RandomNumberGenerator.Create())
                {
                    rng.GetBytes(bytesBuffer);
                }

                return(bytesBuffer);
            });

            /*
             * Test each of the ThreadedSeedGeneratorRng and ThreadSchedulerRng prior to doing SafeRandom
             * or FastRandom, because otherwise, SafeRandom will create static instances of them, which race, etc
             * thus throwing off my results.
             */
            Benchmark("ThreadedSeedGeneratorRng", bytesBuffer =>
            {
                var myThreadedSeedGeneratorRng = new ThreadedSeedGeneratorRng();
                myThreadedSeedGeneratorRng.GetBytes(bytesBuffer);
                return(bytesBuffer);
            });
            SleepForPools(3000);
            Benchmark("ThreadedSeedGenerator(fast)", bytesBuffer =>
            {
                var myThreadedSeedGenerator = new ThreadedSeedGenerator();
                var seed = myThreadedSeedGenerator.GenerateSeed(RandBytesLength, true);
                return(seed);
            });
            Benchmark("ThreadedSeedGenerator(slow)", bytesBuffer =>
            {
                var myThreadedSeedGenerator = new ThreadedSeedGenerator();
                var seed = myThreadedSeedGenerator.GenerateSeed(RandBytesLength, false);
                return(seed);
            });
            Benchmark("ThreadSchedulerRNG(slow)", bytesBuffer =>
            {
                var threadSchedulerRng = new ThreadSchedulerRng();
                threadSchedulerRng.GetBytes(bytesBuffer);
                return(bytesBuffer);
            });

            SleepForPools(15000);

            Benchmark(
                "SafeRandom",
                bytesBuffer => SafeRandom.StaticInstance.GetBytes(bytesBuffer.Length));

            SleepForPools(15000);

            Benchmark(
                "FastRandom",
                bytesBuffer => FastRandom.StaticInstance.GetBytes(bytesBuffer.Length));

            SleepForPools(15000);

            Console.WriteLine("");

            PresentResults();

            Console.WriteLine("");
            Console.Error.WriteLine("Finished");
            Console.Out.Flush();
            Console.ReadKey();
        }
        /*
         * Creates and seeds a new SecureRandom to be used for keypair creation
         */
        private static SecureRandom CreateNewSecureRandom(ApplicationContext context, Node args)
        {
            // First we retrieve the seed provided by caller through the [seed] argument, defaulting to "foobar" if no user seed is provided
            string userSeed = args.GetExChildValue <string> ("seed", context, "foobar");

            // Then we change the given seed by hashing it, such that each pass through this method creates a different user provided seed
            args.FindOrInsert("seed").Value = context.Raise("p5.crypto.hash.create-sha512", new Node("", userSeed)).Get <string> (context);

            // Then we retrieve a cryptographically secure random number of 128 bytes
            var rndBytes = context.Raise(
                "p5.crypto.create-random",
                new Node("", null, new Node[] {
                new Node("resolution", 128),
                new Node("raw", true)
            })).Get <byte[]> (context);

            // Then retrieving "seed generator" from BouncyCastle
            var bcSeed = new ThreadedSeedGenerator().GenerateSeed(128, false);

            // Then we retrieve the server password salt
            string serverPasswordSalt = context.Raise(".p5.auth.get-server-salt").Get <string> (context);

            // Then we retrieve the ticks of server
            string serverSeed = DateTime.Now.Ticks.ToString();

            // Then we append the Hyperlambda for the entire code tree
            // Notice, this will even include the GnuPG password in our seed, in sha2 hashed form!
            // In addition, every time the Hyperlambda active Event calling this method changes, the seed will change
            var code = Utilities.Convert <string> (context, args.Root);

            serverSeed += context.Raise("p5.crypto.hash.create-sha256", new Node("", code)).Get <string> (context);;

            // Then adding current thread ID
            serverSeed += System.Threading.Thread.CurrentThread.ManagedThreadId.ToString();

            // Then appending a randomly created Guid
            serverSeed += Guid.NewGuid().ToString();

            // Then we hash the user seed, multiple times, depending upon the length of the supplied user seed
            // This is done this way, to avoid reducing the resolution of the user-provided seed, such that the longer seed the user
            // provides, the better the strength of the key becomes, and the more difficult a brute force of a user seed guess becomes
            // Basically, we create a new hash, for each 100 characters in user provided seed. This makes a brute force significantly more
            // difficult, since the resolution of the user-provided seed is kept, while also making a brute force more expensive, due
            // to multiple hashes having to be done
            List <byte> userSeedByteList = new List <byte>();

            for (int idx = 0; idx < userSeed.Length; idx += 100)
            {
                var    subStr = userSeed.Substring(idx, Math.Min(100, userSeed.Length - idx));
                byte[] buffer = context.Raise("p5.crypto.hash.create-sha512", new Node("", subStr, new Node[] { new Node("raw", true) })).Get <byte[]> (context);
                userSeedByteList.AddRange(buffer);
            }
            byte[] userSeedBytes = userSeedByteList.ToArray();
            args ["seed"].Value = userSeedBytes;

            // Then we hash the server seed and the user seed with sha512, to create maximum size, and spread bytes evenly around [0-255] value range
            byte[] serverSeedBytes         = context.Raise("p5.crypto.hash.create-sha512", new Node("", serverSeed, new Node[] { new Node("raw", true) })).Get <byte[]> (context);
            byte[] serverPasswordSaltBytes = context.Raise("p5.crypto.hash.create-sha512", new Node("", serverPasswordSalt, new Node[] { new Node("raw", true) })).Get <byte[]> (context);

            // Then we "braid" all the different parts together, to make sure no single parts of our seed becomes predictable due to weaknesses in one or more of
            // our seed generators. Meaning, if at least ONE of our "seed generators" are well functioning, then the entire result will be difficult to predict
            List <byte> seedBytesList = new List <byte>();

            for (int idx = 0; idx < Math.Max(serverSeedBytes.Length, Math.Max(userSeedBytes.Length, Math.Max(rndBytes.Length, Math.Max(bcSeed.Length, serverPasswordSalt.Length)))); idx++)
            {
                seedBytesList.Add(userSeedBytes [idx % userSeedBytes.Length]);
                seedBytesList.Add(serverSeedBytes [idx % serverSeedBytes.Length]);
                seedBytesList.Add(rndBytes [idx % rndBytes.Length]);
                seedBytesList.Add(bcSeed [idx % bcSeed.Length]);
                seedBytesList.Add(serverPasswordSaltBytes [idx % serverPasswordSaltBytes.Length]);
            }

            // At this point, we are fairly certain that we have a pretty random and cryptographically secure seed
            // Provided that SecureRandom from BouncyCastle is implemented correctly, we should now have a VERY, VERY, VERY unique,
            // and cryptographically secure Random Number seed!!
            // And since the seed is not "setting the seed", but rather "stirring up with additional entropy", this logic should
            // with extremely high certainty make sure we now have a very, very, very random seed for our Random number generator!
            // In addition, there are multiple hash invocations running, either directly or indirectly, meaning it becomes very
            // expensive to do a brute force attack on random number generator, leaving us with something that is "close to guaranteed"
            // being a good random number generator, assuming SecureRandom does its job!
            // In addition, our seed is at this point 640 bytes long, which translates into 5120 bits, meaning in no ways we have unintentionally
            // reduced the resolution of SecureRandom by applying "low resolution seeds" ...
            // In addition, our seed should be evenly distributed in the [0,255] range, and also no single parts of our seed should be predictable,
            // unless every single method above fails, due to seed being "braided together".
            SecureRandom retVal = new SecureRandom();

            retVal.SetSeed(seedBytesList.ToArray());

            return(retVal);
        }
        /// <summary>
        /// Safely get Crypto Random byte array at the size you desire.
        /// </summary>
        /// <param name="size">Size of the crypto random byte array to build</param>
        /// <param name="seedStretchingIterations">Optional parameter to specify how many SHA512 passes occur over our seed before we use it. Higher value is greater security but uses more computational power. If random byte generation is taking too long try specifying values lower than the default of 5000. You can set 0 to turn off stretching</param>
        /// <returns>A byte array of completely random bytes</returns>
        public static byte[] GetRandomBytes(int size, int seedStretchingIterations = 5000)
        {
            //varies from system to system, a tiny amount of entropy, tiny
            int processorCount = System.Environment.ProcessorCount;

            //another tiny amount of entropy due to the varying nature of thread id
            int currentThreadId = System.Environment.CurrentManagedThreadId;

            //a GUID is considered unique so also provides some entropy
            byte[] guidBytes = Guid.NewGuid().ToByteArray();

            //this combined with DateTime.Now is the default seed in BouncyCastles SecureRandom
            byte[] threadedSeedBytes = new ThreadedSeedGenerator().GenerateSeed(24, true);

            byte[] output = new byte[size];

            //if for whatever reason it says 0 or less processors just make it 16
            if (processorCount <= 0)
            {
                processorCount = 16;
            }

            //if some fool trys to set stretching to < 0 we protect them from themselves
            if (seedStretchingIterations < 0)
            {
                seedStretchingIterations = 0;
            }

            //we create a SecureRandom based off SHA256 just to get a random int which will be used to determine what bytes to "take" from our built seed hash and then rehash those taken seed bytes using a KDF (key stretching) such that it would slow down anyone trying to rebuild private keys from common seeds.
            SecureRandom seedByteTakeDetermine = SecureRandom.GetInstance("SHA256PRNG");

            guidBytes = HmacSha512Digest(guidBytes, 0, guidBytes.Length, MergeByteArrays(threadedSeedBytes, UTF8Encoding.UTF8.GetBytes(Convert.ToString(System.Environment.TickCount))));

            try
            {
                seedByteTakeDetermine.SetSeed(((DateTime.Now.Ticks - System.Environment.TickCount) * processorCount) + currentThreadId);
                seedByteTakeDetermine.SetSeed(guidBytes);
                seedByteTakeDetermine.SetSeed(seedByteTakeDetermine.GenerateSeed(1 + currentThreadId));
                seedByteTakeDetermine.SetSeed(threadedSeedBytes);
            }
            catch
            {
                try
                {
                    //if the number is too big or causes an error or whatever we will failover to this, as it's not our main source of random bytes and not used in the KDF stretching it's ok.
                    seedByteTakeDetermine.SetSeed((DateTime.Now.Ticks - System.Environment.TickCount) + currentThreadId);
                    seedByteTakeDetermine.SetSeed(guidBytes);
                    seedByteTakeDetermine.SetSeed(seedByteTakeDetermine.GenerateSeed(1 + currentThreadId));
                    seedByteTakeDetermine.SetSeed(threadedSeedBytes);
                }
                catch
                {
                    //if again the number is too big or causes an error or whatever we will failover to this, as it's not our main source of random bytes and not used in the KDF stretching it's ok.
                    seedByteTakeDetermine.SetSeed(DateTime.Now.Ticks - System.Environment.TickCount);
                    seedByteTakeDetermine.SetSeed(guidBytes);
                    seedByteTakeDetermine.SetSeed(seedByteTakeDetermine.GenerateSeed(1 + currentThreadId));
                    seedByteTakeDetermine.SetSeed(threadedSeedBytes);
                }
            }

            //hardened seed
            byte[] toHashForSeed;

            try
            {
                toHashForSeed = BitConverter.GetBytes(((processorCount - seedByteTakeDetermine.Next(0, processorCount)) * System.Environment.TickCount) * currentThreadId);
            }
            catch
            {
                try
                {
                    //if the number was too large or something we failover to this
                    toHashForSeed = BitConverter.GetBytes(((processorCount - seedByteTakeDetermine.Next(0, processorCount)) + System.Environment.TickCount) * currentThreadId);
                }
                catch
                {
                    //if the number was again too large or something we failover to this
                    toHashForSeed = BitConverter.GetBytes(((processorCount - seedByteTakeDetermine.Next(0, processorCount)) + System.Environment.TickCount) + currentThreadId);
                }
            }

            toHashForSeed = Sha512Digest(toHashForSeed, 0, toHashForSeed.Length);
            toHashForSeed = MergeByteArrays(toHashForSeed, guidBytes);
            toHashForSeed = MergeByteArrays(toHashForSeed, BitConverter.GetBytes(currentThreadId));
            toHashForSeed = MergeByteArrays(toHashForSeed, BitConverter.GetBytes(DateTime.UtcNow.Ticks));
            toHashForSeed = MergeByteArrays(toHashForSeed, BitConverter.GetBytes(DateTime.Now.Ticks));
            toHashForSeed = MergeByteArrays(toHashForSeed, BitConverter.GetBytes(System.Environment.TickCount));
            toHashForSeed = MergeByteArrays(toHashForSeed, BitConverter.GetBytes(processorCount));
            toHashForSeed = MergeByteArrays(toHashForSeed, threadedSeedBytes);
            toHashForSeed = Sha512Digest(toHashForSeed, 0, toHashForSeed.Length);

            //we grab a random amount of bytes between 24 and 64 to rehash  make a new set of 64 bytes, using guidBytes as hmackey
            toHashForSeed = Sha512Digest(HmacSha512Digest(toHashForSeed, 0, seedByteTakeDetermine.Next(24, 64), guidBytes), 0, 64);

            seedByteTakeDetermine.SetSeed(currentThreadId + (DateTime.Now.Ticks - System.Environment.TickCount));

            //by making the iterations also random we are again making it hard to determin our seed by brute force
            int iterations = seedStretchingIterations - (seedByteTakeDetermine.Next(0, (seedStretchingIterations / seedByteTakeDetermine.Next(9, 100))));

            //here we use key stretching techniques to make it harder to replay the random seed values by forcing computational time up
            byte[] seedMaterial = Rfc2898_pbkdf2_hmacsha512.PBKDF2(toHashForSeed, seedByteTakeDetermine.GenerateSeed(64), iterations);

            //build a SecureRandom object that uses Sha512 to provide randomness and we will give it our created above hardened seed
            SecureRandom secRand = new SecureRandom(new Org.BouncyCastle.Crypto.Prng.DigestRandomGenerator(new Sha512Digest()));

            //set the seed that we created just above
            secRand.SetSeed(seedMaterial);

            //generate more seed materisal
            secRand.SetSeed(currentThreadId);
            secRand.SetSeed(MergeByteArrays(guidBytes, threadedSeedBytes));
            secRand.SetSeed(secRand.GenerateSeed(1 + secRand.Next(64)));

            //add our prefab seed again onto the previous material just to be sure the above statements are adding and not clobbering seed material
            secRand.SetSeed(seedMaterial);

            //here we derive our random bytes
            secRand.NextBytes(output, 0, size);

            return(output);
        }
示例#6
0
        public static void Main(string[] args)
        {
            // It's always good to StartFillingEntropyPools as early as possible when the application is launched.
            // But if I'm benchmarking performance below, then it's not really fair to let them start early.
            // StartEarly.StartFillingEntropyPools();

            DateTime     before;
            DateTime     after;
            RandomResult result;

            const int randBytesLength = 8 * 1024;

            var results = new List <RandomResult>();

            //AllZeros
            result = new RandomResult {
                AlgorithmName = "AllZeros"
            };
            before = DateTime.Now;
            var randBytes = new byte[randBytesLength];

            after                   = DateTime.Now;
            result.TimeSpan         = after - before;
            result.CompressionRatio = CompressionUtility.GetCompressionRatio(randBytes).Result;
            results.Add(result);

            //SystemRng
            result = new RandomResult {
                AlgorithmName = "SystemRng"
            };
            Console.Write(result.AlgorithmName + " ");
            before = DateTime.Now;
            var mySystemRngCryptoServiceProvider = new SystemRng();

            mySystemRngCryptoServiceProvider.GetBytes(randBytes);
            after                   = DateTime.Now;
            result.TimeSpan         = after - before;
            result.CompressionRatio = CompressionUtility.GetCompressionRatio(randBytes).Result;
            results.Add(result);
            Console.WriteLine((after - before).ToString());

            //RNGCryptoServiceProvider
            result = new RandomResult();
            result.AlgorithmName = "RNGCryptoServiceProvider";
            Console.Write(result.AlgorithmName + " ");
            before = DateTime.Now;
            using (var rng = RandomNumberGenerator.Create())
            {
                rng.GetBytes(randBytes);
            }
            after                   = DateTime.Now;
            result.TimeSpan         = after - before;
            result.CompressionRatio = CompressionUtility.GetCompressionRatio(randBytes).Result;
            results.Add(result);
            Console.WriteLine((after - before).ToString());

            //ThreadedSeedGeneratorRng
            result = new RandomResult();
            result.AlgorithmName = "ThreadedSeedGeneratorRng";
            Console.Write(result.AlgorithmName + " ");
            before = DateTime.Now;
            var myThreadedSeedGeneratorRng = new ThreadedSeedGeneratorRng();

            myThreadedSeedGeneratorRng.GetBytes(randBytes);
            after                   = DateTime.Now;
            result.TimeSpan         = after - before;
            result.CompressionRatio = CompressionUtility.GetCompressionRatio(randBytes).Result;
            results.Add(result);
            Console.WriteLine((after - before).ToString());
            Console.Write("Sleeping to allow pool to fill...");
            Thread.Sleep(3000); // Should be enough time for its pool to fill up, so it won't slow down next:
            Console.WriteLine("  Done.");

            //ThreadedSeedGenerator(fast)
            result = new RandomResult();
            result.AlgorithmName = "ThreadedSeedGenerator(fast)";
            Console.Write(result.AlgorithmName + " ");
            var myThreadedSeedGenerator = new ThreadedSeedGenerator();

            Array.Clear(randBytes, 0, randBytesLength);
            before                  = DateTime.Now;
            randBytes               = myThreadedSeedGenerator.GenerateSeed(randBytesLength, true);
            after                   = DateTime.Now;
            result.TimeSpan         = after - before;
            result.CompressionRatio = CompressionUtility.GetCompressionRatio(randBytes).Result;
            results.Add(result);
            Console.WriteLine((after - before).ToString());

            //ThreadedSeedGenerator(slow)
            result = new RandomResult();
            result.AlgorithmName = "ThreadedSeedGenerator(slow)";
            Console.Write(result.AlgorithmName + " ");
            Array.Clear(randBytes, 0, randBytesLength);
            before                  = DateTime.Now;
            randBytes               = myThreadedSeedGenerator.GenerateSeed(randBytesLength, false);
            after                   = DateTime.Now;
            result.TimeSpan         = after - before;
            result.CompressionRatio = CompressionUtility.GetCompressionRatio(randBytes).Result;
            results.Add(result);
            Console.WriteLine((after - before).ToString());

            //ThreadSchedulerRNG(slow)
            result = new RandomResult();
            result.AlgorithmName = "ThreadSchedulerRng";
            Console.Write(result.AlgorithmName + " ");
            before = DateTime.Now;
            var myThreadSchedulerRNG = new ThreadSchedulerRng();

            myThreadSchedulerRNG.GetBytes(randBytes);
            after                   = DateTime.Now;
            result.TimeSpan         = after - before;
            result.CompressionRatio = CompressionUtility.GetCompressionRatio(randBytes).Result;
            results.Add(result);
            Console.WriteLine((after - before).ToString());

            const int numResults = 14;

            Console.Write("ticks bit positions ");
            before = DateTime.Now;
            var ticksResults      = new RandomResult[numResults];
            var ticksResultsBytes = new byte[numResults][];

            for (var i = 0; i < numResults; i++)
            {
                ticksResults[i] = new RandomResult
                {
                    AlgorithmName = "ticks bit #" + i.ToString().PadLeft(2)
                };
                ticksResultsBytes[i] = new byte[randBytesLength];
            }
            for (var i = 0; i < randBytesLength; i++)
            {
                for (var j = 0; j < 8; j++)
                {
                    var ticks = DateTime.Now.Ticks;
                    for (var bitPos = 0; bitPos < numResults; bitPos++)
                    {
                        ticksResultsBytes[bitPos][i] <<= 1;
                        ticksResultsBytes[bitPos][i]  += (byte)(ticks % 2);
                        ticks >>= 1;
                    }
                    Thread.Sleep(1);
                }
            }
            after = DateTime.Now;
            for (var i = 0; i < numResults; i++)
            {
                ticksResults[i].TimeSpan         = after - before;
                ticksResults[i].CompressionRatio = CompressionUtility.GetCompressionRatio(ticksResultsBytes[i]).Result;
                results.Add(ticksResults[i]);
            }
            Console.WriteLine((after - before).ToString());
            Console.Write("Sleeping to allow pool to fill...");
            Thread.Sleep(15000); // Should be enough time for its pool to fill up, so it won't slow down next:
            Console.WriteLine("  Done.");

            // I want to test each of the ThreadedSeedGeneratorRng and ThreadSchedulerRng prior to doing TinHatRandom
            // or TinHatURandom, because otherwise, TinHatRandom will create static instances of them, which race, etc.
            // thus throwing off my benchmark results.

            //SafeRandom
            result = new RandomResult {
                AlgorithmName = "SafeRandom"
            };
            Console.Write(result.AlgorithmName + " ");
            before                  = DateTime.Now;
            randBytes               = SafeRandom.StaticInstance.GetBytes(randBytes.Length);
            after                   = DateTime.Now;
            result.TimeSpan         = after - before;
            result.CompressionRatio = CompressionUtility.GetCompressionRatio(randBytes).Result;
            results.Add(result);
            Console.WriteLine((after - before).ToString());
            Console.Write("Sleeping to allow pool to fill...");
            Thread.Sleep(15000); // Should be enough time for its pool to fill up, so it won't slow down next:
            Console.WriteLine("  Done.");

            result = new RandomResult {
                AlgorithmName = "FastRandom"
            };
            Console.Write(result.AlgorithmName + " ");
            before                  = DateTime.Now;
            randBytes               = FastRandom.StaticInstance.GetBytes(randBytes.Length);
            after                   = DateTime.Now;
            result.TimeSpan         = after - before;
            result.CompressionRatio = CompressionUtility.GetCompressionRatio(randBytes).Result;
            results.Add(result);
            Console.WriteLine((after - before).ToString());
            Console.Write("Sleeping to allow pool to fill...");
            Thread.Sleep(15000); // Should be enough time for its pool to fill up, so it won't slow down next:
            Console.WriteLine("  Done.");

            Console.WriteLine("");

            var maxCompressionRatio = double.MinValue;
            var minCompressionRatio = double.MaxValue;
            var longestName         = 0;

            foreach (var theResult in results)
            {
                if (theResult.AlgorithmName.Length > longestName)
                {
                    longestName = theResult.AlgorithmName.Length;
                }
                if (theResult.CompressionRatio < minCompressionRatio)
                {
                    minCompressionRatio = theResult.CompressionRatio;
                }
                if (theResult.CompressionRatio > maxCompressionRatio)
                {
                    maxCompressionRatio = theResult.CompressionRatio;
                }
            }
            Console.WriteLine("AlgorithmName".PadLeft(longestName) + " : bits per bit : elapsed sec : effective rate");
            foreach (var theResult in results)
            {
                var bitsPerBit = (theResult.CompressionRatio - minCompressionRatio) /
                                 (maxCompressionRatio - minCompressionRatio);
                double byteRate;
                string byteRateString;
                if (theResult.TimeSpan.TotalSeconds == 0)
                {
                    if (theResult.CompressionRatio == minCompressionRatio)
                    {
                        byteRateString = "0";
                    }
                    else
                    {
                        byteRateString = "infinity";
                    }
                }
                else
                {
                    byteRate = bitsPerBit * randBytesLength / theResult.TimeSpan.TotalSeconds;
                    if (byteRate > 1000000)
                    {
                        byteRateString = (byteRate / 1000000).ToString("F2") + " MiB/sec";
                    }
                    else if (byteRate > 1000)
                    {
                        byteRateString = (byteRate / 1000).ToString("F2") + " KiB/sec";
                    }
                    else
                    {
                        byteRateString = byteRate.ToString("F2") + " B/sec";
                    }
                }
                Console.WriteLine(theResult.AlgorithmName.PadLeft(longestName) + " : " +
                                  bitsPerBit.ToString("0.000").PadLeft(12) + " : " +
                                  theResult.TimeSpan.TotalSeconds.ToString("0.000").PadLeft(11) + " : " +
                                  byteRateString.PadLeft(14));
            }

            Console.WriteLine("");
            Console.Error.WriteLine("Finished");
            Console.Out.Flush();
            Console.ReadKey();
        }