C#中创建加密随机数的最快、线程安全的方法是什么?

4

注意,在多线程并行生成随机数时,加密随机数生成器不是线程安全的。使用的生成器是 RNGCryptoServiceProvider,看起来会重复长时间的随机位(128位)。以下代码可用于重现此问题。

除了使用锁来保护对 RNGCryptoServiceProvider 实例的访问(这会破坏整个速度点),有没有更快的方法来生成加密随机数?

using System;
using System.Runtime.Caching;
using System.Security.Cryptography;
using System.Threading.Tasks;

namespace ParallelRandomness
{
    class Program
    {
        static void Main(string[] args)
        {
            var test = new Test();
            Console.Write("Serialized verion running ...  ");
            test.Run(false);
            Console.WriteLine();
            Console.Write("Parallelized verion running ...  ");
            test.Run(true);
            Console.WriteLine(Environment.NewLine + "Done.");
            Console.ReadLine();
        }
    }

    class Test
    {
        private readonly RNGCryptoServiceProvider _rng = new RNGCryptoServiceProvider();
        private readonly byte[] _randomBytes = new byte[128 / 8];
        private int collisionCount = 0;
        private readonly object collisionCountLock  = new object();

        public void Run(bool parallel)
        {
            const int numOfRuns = 100000;
            const int startInclusive = 1;
            const int endExclusive = numOfRuns + startInclusive;
            if (parallel)
                Parallel.For(startInclusive, endExclusive, x => GenRandomByteArrays(x));
            else
            {
                for (var i = startInclusive; i < endExclusive; i++)
                    GenRandomByteArrays(i);
            }
        }

        private void GenRandomByteArrays(long instance)
        {
            _rng.GetBytes(_randomBytes);
            var randomString = Convert.ToBase64String(_randomBytes);
            var cache = MemoryCache.Default;
            if (cache.Contains(randomString))
            {
                // uh-oh!
                lock (collisionCountLock)
                {
                    Console.WriteLine(Environment.NewLine + "Instance {0}: Collision count={1}. key={2} already in cache. ", instance, ++collisionCount, randomString);
                }
            }
            else
            {
                MemoryCache.Default.Add(randomString, true, DateTimeOffset.UtcNow.AddMinutes(5));
                Console.Write(instance % 2 == 0 ? "\b-" : "\b|"); // poor man's activity indicator
            }
        }
    }
}

可能是与是否C#随机数生成器线程安全?重复的内容。 - Oblivious Sage
那个问题的一些答案提供了快速且线程安全的生成随机数的方法。 - Oblivious Sage
RNGCryptoServiceProvider 的文档明确说明:“此类型是线程安全的。” - CodesInChaos
@ObliviousSage:实际上,所讨论的那个类(Random)甚至不是加密随机数生成器,因此它的线程安全性和性能都是无关紧要的。 - DeepSpace101
1
你难道不是在多个线程中使用同一个数组吗?即使 PRNG 本身是安全的,这显然是不安全的。 - CodesInChaos
@CodesInChaos:哦!是的,那就是问题所在!你想把它作为答案,这样我就可以给你信用分了吗? - DeepSpace101
1个回答

10

RNGCryptoServiceProvider文档说明:

此类型是线程安全的。

您的代码并未证明RNGCryptoServiceProvider不是线程安全的,因为您在多个线程中使用了相同的数组。 即使RNGCryptoServiceProvider是线程安全的,该数组重用也是不安全的。

关于性能,我想指出创建RNGCryptoServiceProvider的新实例非常便宜。 昂贵的部分是GetBytes的每次调用开销。

因此,如果您遇到性能问题,第一件事就是尝试一次请求更多数据并自行拆分数据。 如果仍然不够,请使用由系统CSPRNG提供种子的流密码。


1
无论如何,使用流密码器来扩展初始熵,但是您必须使用加密CSRNG而不是系统PRNG进行种子生成,因为后者不安全。定期从CSPRNG重新生成种子,以便您不会从流密码器中获得过长的运行时间。加密强度取决于其最薄弱的环节,非加密PRNG是脆弱的。 - rossum
1
@rossum 通过系统PRNG,我指的是/dev/urandom或者CryptGenRandom,而不是随附.NET的那个糟糕的System.Random - CodesInChaos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接