65.9K
CodeProject 正在变化。 阅读更多。
Home

随机密码生成器:一个示例

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (2投票s)

2017年11月15日

CPOL

3分钟阅读

viewsIcon

19468

downloadIcon

440

using System.Security.Cryptography.RNGCryptoServiceProvider

引言

当需要生成需要更高随机性的随机数(更难预测/猜测/有偏差的分布)时,流行的 System.Random 并不是一个好选择。相反,RNGCryptoServiceProvider(基于 文档)实现了加密随机数生成器 (RNG),这是一个推荐的选择。为了比较这两种选择,优点和缺点,以及它们的使用方法,你可以谷歌搜索它们。本文旨在分享我使用需要加密安全的项目的工作经验,而 RNGCryptoServiceProvider 是我的选择。

注释

一个密码可能包含 4 种类型的字符

  1. 小写字母
  2. 大写字母
  3. 数字
  4. 符号

一些观察

  • 字符 'l' 有时会与数字 1 混淆。
  • 密码通常需要是 n 个字符的长度。它包含 x 个小写字母,y 个大写字母,z 个数字和 t 个符号字符(其中 x + y + z + t = n)。

计划是

  1. 生成一个由随机 x 个小写字母组成的字符串
  2. 生成一个由随机 y 个大写字母组成的字符串
  3. 生成一个由随机 z 个数字组成的字符串
  4. 生成一个由随机 t 个符号字符组成的字符串
  5. 将它们全部加在一起
  6. 如果需要,打乱它们的顺序

这项工作可以分为两个类:RandomService (步骤 1 - 4) 和 PasswordService (步骤 5 - 6)。

代码

确定可接受的字符集

首先,我们需要上面 4 种类型中每种类型的可能字符集。

与其简单地定义成这样

        public static string _LOWERCASEALPHABETICAL = "abcdefghijklmnopqrstuvwxyz";
        public static string _UPPERCASEALPHABETICAL = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        public static string _NUMERICAL = "0123456789";
        public static string _SYMBOL = "@%+!#$^?:.(){}[]~-_`";

我们可以创建一个辅助方法,能够过滤掉一些不需要的字符。当我们需要稍后更改不包含哪些字符时,这将很方便

        public static string GetCharRange(char begin, char end, string excludingChars = "")
        {
            var result = string.Empty;
            for (char c = begin; c <= end; c++)
            {
                result += c;
            }

            if (!string.IsNullOrEmpty(excludingChars))
            {
                var inclusiveChars = result.Except(excludingChars).ToArray();
                result = new string(inclusiveChars);
            }
            return result;
        }

下面的调用将从所有可能的小写字母集合中排除 'i' 和 'l'

        public static string _LOWERCASEALPHABETICAL = GetCharRange('a', 'z', "li"); // "abcdefghjkmnopqrstuvwxyz";
        public static string _UPPERCASEALPHABETICAL = GetCharRange('A', 'Z'); // "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        public static string _NUMERICAL = GetCharRange('0', '9'); // "0123456789";
        public static string _SYMBOL = "@%+!#$^?:.(){}[]~-_`";

有了以上 4 组可接受的字符,我们需要构建 4 个随机字符串,每个字符串包含一定数量的字符(x、y、z、t)。这些随机字符串中选择的每个字符都对应于该字符在上述 4 个集合中的索引。换句话说,这里的“随机”是随机索引(随机数),在字符数组的索引范围内。为了更清楚,字符串是字符数组。具体来说,这里的随机数是在 [0, 长度 - 1] 范围内的生成的数。

RandomService 类

我们设计这个类来使用 RNGCryptoServiceProvider 生成随机数

    public static class RandomService
    {
        public static readonly RNGCryptoServiceProvider _rngProvider = new RNGCryptoServiceProvider();

        public static int NextInt32()
        {
            var randomBuffer = new byte[4];
            _rngProvider.GetBytes(randomBuffer);
            return BitConverter.ToInt32(randomBuffer, 0);
        }

        // Generate a random real number within range [0.0, 1.0]
        public static double Next()
        {
            var buffer = new byte[sizeof(uint)];
            _rngProvider.GetBytes(buffer); // Fill the buffer
            uint random = BitConverter.ToUInt32(buffer, 0);
            return random / (1.0 + uint.MaxValue);
        }

        // Generate an int within range [min, max - 1] if max > min, and min if min == max
        public static int Next(int min, int max)
        {
            if (min > max) throw new ArgumentException($"Second input ({max}) must be greater than first input ({min}))");
            return (int)(min + (max - min) * Next());
        }

        // Generate an int within range [0, max - 1] if max > 1, and 0 if max == {0, 1}
        public static int Next(int max) => Next(0, max);
    }

PasswordService 类

现在,我们可以通过使用生成的随机数(来自 RandomService 类)作为随机索引从可接受的字符集中获取字符来生成随机密码。下面的方法 GetRandomString(string, int) 旨在实现我们所讨论的内容

    public static class PasswordService
    {
        public static string _LOWERCASEALPHABETICAL = GetCharRange('a', 'z', "li"); // "abcdefghjkmnopqrstuvwxyz";
        public static string _UPPERCASEALPHABETICAL = GetCharRange('A', 'Z'); // "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        public static string _NUMERICAL = GetCharRange('0', '9'); // "0123456789";
        public static string _SYMBOL = "@%+!#$^?:.(){}[]~-_`";

        public static string RandomUpperCase(int length) => GetRandomString(_UPPERCASEALPHABETICAL, length);
        public static string RandomLowerCase(int length) => GetRandomString(_LOWERCASEALPHABETICAL, length);
        public static string RandomNumerical(int length) => GetRandomString(_NUMERICAL, length);
        public static string RandomSymbol(int length) => GetRandomString(_SYMBOL, length);

        public static string GetCharRange(char begin, char end, string excludingChars = "")
        {
            ... shown above
        }

        private static string GetRandomString(string possibleChars, int len)
        {
            var result = string.Empty;
            for (var position = 0; position < len; position++)
            {
                var index = RandomService.Next(possibleChars.Length);
                result += possibleChars[index];
            }
            return result;
        }

        ...
    }

最后,我们根据所需的数字 x、y、z 和 t 生成密码

    public static class PasswordService
    {
        ... 

        public static string GenerateRandomPassword(int numOfLowerCase, int numOfUpperCase, int numOfNumeric, int numOfSymbol)
        {
            string ordered = RandomLowerCase(numOfLowerCase)
                + RandomUpperCase(numOfUpperCase)
                + RandomNumerical(numOfNumeric)
                + RandomSymbol(numOfSymbol);

            string shuffled = new string(ordered.OrderBy(n => RandomService.NextInt32()).ToArray());

            return shuffled;
        }
    }

请注意,在没有打乱顺序的情况下,密码的字符按类型(小写、大写、数字或符号)排序。有几种打乱顺序的方法,例如,Fisher-Yates 算法。这个程序使用 LINQ 扩展方法根据随机键对密码字符串的元素进行排序。

如果第一个字符不允许是符号,则有几种解决方法,例如,在打乱顺序后在第一个位置插入一个新的随机字母数字字符。

测试结果

        static void GetAndShowPassword(int numOfPass, int lower, int upper, int num, int symbol)
        {
            WriteLine($"{numOfPass} generated passwords containing: {lower} lower, {upper} upper, {num} numerical, and {symbol} symbol character(s):");

            for (int i = 0; i < numOfPass; i++)
            {
                string pass = PasswordService.GenerateRandomPassword(lower, upper, num, symbol);
                WriteLine($"\t{pass}");
            }

            WriteLine();
        }
        static void Main(string[] args)
        {
            GetAndShowPassword(4, lower: 2, upper: 2, num: 2, symbol: 2);
            GetAndShowPassword(5, lower: 0, upper: 4, num: 4, symbol: 0);
            GetAndShowPassword(6, lower: 0, upper: 4, num: 4, symbol: 2);
            ReadKey();
        }

END

© . All rights reserved.