【问题标题】:Cryptograhically random unique strings加密随机唯一字符串
【发布时间】:2014-06-03 13:40:50
【问题描述】:

在this 答案中,发布了以下代码,用于创建唯一的随机字母数字字符串。有人可以为我澄清一下如何确保它们在此代码中是唯一的,以及它们在多大程度上是唯一的?如果我在不同的场合重新运行这个方法,我仍然会得到唯一的字符串吗?

或者我只是误解了回复,这些根本没有生成唯一的密钥,只是随机的?

我已经在对该答案的评论中问过这个问题,但用户似乎不活跃。

    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();
    }   

【问题讨论】:

  • 它不是真正唯一的字符串,只是随机的。话虽如此,它是一个非常强大的字母数字随机字符串,不太可能在少量出现的情况下自我复制,但这种可能性确实存在。无法确定字符串是否或何时重复,但如果您生成了十亿个字符串,您很可能会得到一个重复。
  • 在加密中,我们通常假设足够长的随机值是唯一的。由于这只有 47 位,我不会假设唯一性。我只相信至少 120 位的值。
  • 这段代码也很糟糕。它是有偏差的(字符数不除以 255),并且由于非一错误,它永远不会输出 0。生成随机字符串的更好功能是我对How can I generate random alphanumeric strings in C#? 的回答,但显然它仍然需要足够长的输出才能实现唯一性。

标签: c# random cryptography unique


【解决方案1】:

代码中没有任何东西可以保证结果是唯一的。要获得唯一值,您要么必须保留所有以前的值,以便检查重复项,要么使用更长的代码以使重复项实际上是不可能的(例如 GUID)。该代码包含的信息少于 48 位,这比 GUID 的 128 位要少得多。

字符串只是随机的,虽然使用了加密强度随机生成器,但它被随机数据生成代码的方式所破坏。代码中存在一些问题:

  • 创建了一个 char 数组,该数组被丢弃并替换为另一个。
  • 一个字节的随机数据数组的创建完全没有明显的原因,因为它没有用于任何用途。
  • 使用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:是的,但好处是可以很容易地将随机数据转换为文本而不会添加任何偏差。
  • 几乎是独一无二的,我想你的意思是它碰撞的可能性很小?该语句中键的大小是 16 而不是 8?
  • @JuhaKangas:是的,对于如此大的数字,您获得随机复制的机会与内存芯片中数字中的位自发变化的机会大致相同。使用 16 字节而不是 8 是其中的一部分,使用每个随机字节中的所有 8 位而不是将其划分为小于 6 位是另一部分。
  • @JuhaKangas:请参阅en.wikipedia.org/wiki/Birthday_attack#Mathematics 以获取具有精确碰撞概率的表格。由于代码使用 16 字节的随机性,因此 128 位的行适用于这种情况。
【解决方案2】:

唯一性和随机性是相互排斥的概念。如果随机数生成器是真正随机的,那么它可以返回相同的值。如果值是真正唯一的,尽管它们可能不是确定性的,但它们肯定不是真正随机的,因为生成的每个值都会从允许值池中删除一个值。这意味着每次运行都会影响后续运行的结果,并且在某个时刻,池会被耗尽(当然,除非存在无限大小的允许值池的可能性,但在这样的池中避免冲突的唯一方法是使用确定性方法选择值)。

您显示的代码会生成非常随机的值,但不能 100% 保证是唯一的。跑了足够多之后,将会发生碰撞。

【讨论】:

  • 在这种情况下,一旦您生成大约 1000 万个字符串,就很可能发生冲突。
  • @CodesInChaos 谢谢你做数学,我很懒。
【解决方案3】:

我需要生成一个字母数字字符串的 7 个字符。通过一个小的搜索,我写了下面的代码。性能结果在上面上传

我使用hashtable Class 来保证唯一性,也使用RNGCryptoServiceProvider Class 来获得高质量的随机字符

results of generating 100.000 - 1.000.000 - 10.000.000 sample

生成唯一的字符串; thanks to 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);
        }
    }

【讨论】:

    【解决方案4】:

    使用严格的字母数字字符将您绘制的池限制为 62 个。使用完整的可打印字符集 (ASCII 32-126) 将您的池增加到 94 个,降低了冲突的可能性并消除了单独创建池。

    【讨论】:

    • 吹毛求疵:127 以上的字符不是 ASCII。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-11-21
    • 2010-10-18
    • 2014-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多