加密随机唯一字符串

6
这个答案中,下面的代码是用于创建唯一的随机字母数字字符串的。请问有人能够澄清一下这些字符串在这段代码中如何确保唯一性以及它们的唯一性程度?如果我在不同的场合重新运行此方法,我是否仍然会得到唯一的字符串?
或者我是否误解了回答,这些只是随机生成的字符串,并没有生成唯一的键?
我已经在评论中提出了这个问题,但用户似乎处于不活跃状态。
    public static string GetUniqueKey()
    {
        int maxSize = 8;
        char[] chars = new char[62];
        string a;
        a = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
        chars = a.ToCharArray();
        int size = maxSize;
        byte[] data = new byte[1];
        RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider();
        crypto.GetNonZeroBytes(data);
        size = maxSize;
        data = new byte[size];
        crypto.GetNonZeroBytes(data);
        StringBuilder result = new StringBuilder(size);
        foreach (byte b in data)
        { result.Append(chars[b % (chars.Length - 1)]); }
        return result.ToString();
    }   

它不是一个真正独特的字符串,只是随机的。话虽如此,它是一个非常强大的字母数字随机字符串,在少量出现的情况下不太可能重复自己,但可能性确实存在。无法确定字符串何时会重复,但如果您生成了十亿个字符串,您很可能会得到一个重复项。 - Howard Renollet
1
在加密中,我们通常假设足够长的随机值是唯一的。由于这只有47位,我不会假设它是唯一的。我只会信任至少120位的值。 - CodesInChaos
1
这段代码也相当糟糕。它是有偏差的(字符数不能被255整除),并且由于一个偏移错误,它永远不会输出0。生成随机字符串的更好的函数是我在如何在C#中生成随机字母数字字符串?中的答案,但显然仍需要足够长的输出才能实现唯一性。 - CodesInChaos
4个回答

9
代码中没有保证结果唯一的内容。若要获取唯一值,您需要保留所有先前的值以便检查重复项,或者使用更长的代码使重复项几乎不可能出现(例如GUID)。该代码包含的信息少于48位,远少于GUID的128位。
字符串只是随机生成的,尽管使用了加密强度随机生成器,但从随机数据生成代码的方式破坏了它的强度。代码中存在一些问题:
- 创建一个字符数组,然后将其丢弃并替换为另一个字符数组。 - 创建一个没有任何明显原因的随机数据字节数组,因为它根本没有用到。 - 使用GetNonZeroBytes方法而不是GetBytes方法,这会导致字符分布的偏差,因为代码没有处理零值的缺失。 - 使用模运算符(%)将随机数减小到使用的字符数,但无法将随机数均匀地分成每个字符数,这也会增加字符分布的偏差。 - 在减少数字时使用chars.Length - 1而不是chars.Length,这意味着预定义的62个字符中只有61个可以出现在字符串中。
虽然这些问题很小,但在处理加密强度的随机性时非常重要。
以下是一种版本的代码,它可以生成没有这些问题的字符串,并提供足够信息的代码,以被认为是几乎唯一的:
public static string GetUniqueKey() {
  int size = 16;
  byte[] data = new byte[size];
  RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider();
  crypto.GetBytes(data);
  return BitConverter.ToString(data).Replace("-", String.Empty);
}

使用十六进制的缺点是它只使用了16个字符而非62个字符,这会使得一个128位编码值的大小从22个字符增加到32个字符。 - CodesInChaos
@CodesInChaos:是的,但好处在于可以很容易地将随机数据转换为文本,而不会添加任何偏差。 - Guffa
你所说的“几乎唯一”,是指它只有非常低的碰撞概率吗?这个语句中的16而不是8的键大小是否有关? - JuhaKangas
@JuhaKangas:是的,使用如此大的数字,您随机得到重复的概率与内存芯片中的位自发变化的概率相同。使用16字节而不是8字节是其中的一部分,使用每个随机字节中的所有8位而不是将其分成少于6位是另一部分。 - Guffa
@JuhaKangas:请参见https://en.wikipedia.org/wiki/Birthday_attack#Mathematics,其中包含确切的碰撞概率表。由于该代码使用16字节随机数,因此128位的行适用于此情况。 - Perseids
非常棒。简单明了,完整详实地解释了。 - pim

4
唯一性和随机性是互相排斥的概念。如果一个随机数生成器是真正的随机,那么它可以返回相同的值。如果值是真正唯一的,尽管它们可能不是确定性的,但它们肯定不是真正的随机,因为每个生成的值都会从允许值的池中删除一个值。这意味着每次运行都会影响后续运行的结果,并且在某个时候池就会被消耗完(当然除非有一个无限大的允许值池的可能性,但是避免碰撞的唯一方法是使用确定的方法选择值)。
您展示的代码生成的值非常随机,但并不能保证100%唯一。经过足够多的运行,必然会发生冲突。

1
在这种情况下,一旦您生成约1000万个字符串,碰撞就变得很可能。 - CodesInChaos
@CodesInChaos 谢谢你做了这个数学,我太懒了。 - Nathan

1
我需要生成一个由字母数字组成的7位字符串。经过一番搜索,我写出了以下代码。性能结果已上传。
我使用哈希表类来保证唯一性,并使用RNGCryptoServiceProvider类获取高质量的随机字符。 生成100,000 - 1,000,000 - 10,000,000个样本的结果
生成唯一字符串; 感谢nipul parikh
    public static Tuple<List<string>, List<string>> GenerateUniqueList(int count)
        {
            uniqueHashTable = new Hashtable();
            nonUniqueList = new List<string>();
            uniqueList = new List<string>();

            for (int i = 0; i < count; i++)
            {
                isUniqueGenerated = false;

                while (!isUniqueGenerated)
                {
                    uniqueStr = GetUniqueKey();
                    try
                    {
                        uniqueHashTable.Add(uniqueStr, "");
                        isUniqueGenerated = true;
                    }
                    catch (Exception ex)
                    {
                        nonUniqueList.Add(uniqueStr);
                        // Non-unique generated
                    }
                }
            }

            uniqueList = uniqueHashTable.Keys.Cast<string>().ToList();

            return new Tuple<List<string>, List<string>>(uniqueList, nonUniqueList);
        }

        public static string GetUniqueKey()
        {
            int size = 7;
            char[] chars = new char[62];
            string a = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
            chars = a.ToCharArray();

            RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider();

            byte[] data = new byte[size];
            crypto.GetNonZeroBytes(data);

            StringBuilder result = new StringBuilder(size);

            foreach (byte b in data)
                result.Append(chars[b % (chars.Length - 1)]);

            return Convert.ToString(result);
        }

整个控制台应用程序的代码如下:

    class Program
    {
        static string uniqueStr;
        static Stopwatch stopwatch;
        static bool isUniqueGenerated;
        static Hashtable uniqueHashTable;
        static List<string> uniqueList;
        static List<string> nonUniqueList;
        static Tuple<List<string>, List<string>> generatedTuple;

        static void Main(string[] args)
        {
            int i = 0, y = 0, count = 100000;

            while (i < 10 && y < 4)
            {
                stopwatch = new Stopwatch();

                stopwatch.Start();

                generatedTuple = GenerateUniqueList(count);

                stopwatch.Stop();

                Console.WriteLine("Time elapsed: {0} --- {1} Unique  --- {2} nonUnique",
                    stopwatch.Elapsed,
                    generatedTuple.Item1.Count().ToFormattedInt(),
                    generatedTuple.Item2.Count().ToFormattedInt());

                i++;
                if (i == 9)
                {
                    Console.WriteLine(string.Empty);
                    y++;
                    count *= 10;
                    i = 0;
                }
            }


            Console.ReadLine();
        }

        public static Tuple<List<string>, List<string>> GenerateUniqueList(int count)
        {
            uniqueHashTable = new Hashtable();
            nonUniqueList = new List<string>();
            uniqueList = new List<string>();

            for (int i = 0; i < count; i++)
            {
                isUniqueGenerated = false;

                while (!isUniqueGenerated)
                {
                    uniqueStr = GetUniqueKey();
                    try
                    {
                        uniqueHashTable.Add(uniqueStr, "");
                        isUniqueGenerated = true;
                    }
                    catch (Exception ex)
                    {
                        nonUniqueList.Add(uniqueStr);
                        // Non-unique generated
                    }
                }
            }

            uniqueList = uniqueHashTable.Keys.Cast<string>().ToList();

            return new Tuple<List<string>, List<string>>(uniqueList, nonUniqueList);
        }

        public static string GetUniqueKey()
        {
            int size = 7;
            char[] chars = new char[62];
            string a = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
            chars = a.ToCharArray();

            RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider();

            byte[] data = new byte[size];
            crypto.GetNonZeroBytes(data);

            StringBuilder result = new StringBuilder(size);

            foreach (byte b in data)
                result.Append(chars[b % (chars.Length - 1)]);

            return Convert.ToString(result);
        }
    }

    public static class IntExtensions
    {
        public static string ToFormattedInt(this int value)
        {
            return string.Format(CultureInfo.InvariantCulture, "{0:0,0}", value);
        }
    }

0

仅使用字母数字字符会限制您可以选择的字符池为62个。使用完整的可打印字符集(ASCII 32-126),将您的字符池扩大到94个,减少碰撞的可能性并消除了需要单独创建字符池的步骤。


1
挑剔一点:大于127的字符不属于ASCII。 - CodesInChaos

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