在C#中生成一个强密码?

33

我在想如何在C#中生成一个强大且安全的密码。

我在谷歌上搜索了一下,在维基百科上看到了这个公式,其中L是密码长度,N是可能的符号数量:

alt text

同时,我发现这个问题,但由于某种原因,Membership.GeneratePassword方法只会返回一个带有1位数字的随机数,完全不是一个密码。其它所有解决方案都很慢(>= 0.5秒)。

我需要帮助来实现这个公式(我不知道从哪里开始)。您也可以建议另一种解决方案或解释为什么GeneratePassword方法不起作用。

9个回答

86
我刚刚在 LinqPad 中尝试了以下内容:
System.Web.Security.Membership.GeneratePassword(25, 10)

这是我得到的密码:

[XTJ_67g.i/ag1rL)6_Yv>*+%

或者,如果那不够安全,可以尝试这个:

System.Web.Security.Membership.GeneratePassword(128, 100)

当我运行它三次时,得到了以下结果:

|c^.:?m)#q+(]V;}[Z(})/?-;$]+@!|^/8*_9.$&.&!(?=^!Wx?[@%+&-@b;)>N;&+*w[>$2+_$%l;+h+#zhs^{e?&=*(}X_%|:}]]}*X[+)Er%J/-=;Q0{:+=%c7:^$
/:_)hxF+*){2|;(>:*N^+!_&|}B.$})?[V=[+v({-:-@9-Z$j?.[-}(@MHx+}(}Mz_S(7#4}{..>@G|!+++{+C=|_}=+r^@&$0;L*|kz-;$++/N3$=}?;%&]]*/^#^!+
:*{]-x^$g{|?*))_=B@^.#%L;g|+)#[nq}?y(_(m;]S^I$*q=l-[_/?}&-!k^(+[_{Z|&:^%!_)!=p%=)=wYd-#.UP$%s1{*l%+[%?!c+7=@=.;{+M)!^}&d/]{];(&}

顺便提一下,这只用了不到一秒钟。框架就是你的朋友。

请参见http://msdn.microsoft.com/en-us/library/system.web.security.membership.generatepassword.aspx


4
@alon你做错了,相信我。这种方法在任何计算机上的任何.NET安装程序中都是可靠的,而且是不会出错的。 - user1228
2
@Will - 我不认为“你做错了”是一个很好的回答。 :) - billpg
3
我认为Membership.GeneratePassword是基于System.Security.Cryptography.RNGCryptoServiceProvider实现的,因此你可以使用它来实现自己的GeneratePassword。这里有一个例子:http://www.obviex.com/Samples/Password.aspx - Alon Gubkin
4
虽然这种密码格式在数学意义上很强,但完全忽略了人的因素。任何采用这种格式的密码都需要被记录或存储,这将带来更多问题。最好使用一个口令短语“HelpMeObiWanKanobieYou'reMyOnlyHope”... - AndyM
5
除了该程序集在.NET Core中不可用且不是.NET Standard的一部分之外,此方法可在任何.NET安装上运行且稳定可靠。 - Adam Williams
显示剩余11条评论

16

我不确定我从哪里找到的,但这是一个生成高熵、真正随机的字符串并可用作密码的类。

using System.Security.Cryptography;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

public class PasswordGenerator
{
    public int MinimumLengthPassword { get; private set; }
    public int MaximumLengthPassword { get; private set; }
    public int MinimumLowerCaseChars { get; private set; }
    public int MinimumUpperCaseChars { get; private set; }
    public int MinimumNumericChars { get; private set; }
    public int MinimumSpecialChars { get; private set; }

    public static string AllLowerCaseChars { get; private set; }
    public static string AllUpperCaseChars { get; private set; }
    public static string AllNumericChars { get; private set; }
    public static string AllSpecialChars { get; private set; }
    private readonly string _allAvailableChars;

    private readonly RandomSecureVersion _randomSecure = new RandomSecureVersion();
    private int _minimumNumberOfChars;

    static PasswordGenerator()
    {
        // Define characters that are valid and reject ambiguous characters such as ilo, IO and 1 or 0
        AllLowerCaseChars = GetCharRange('a', 'z', exclusiveChars: "ilo");
        AllUpperCaseChars = GetCharRange('A', 'Z', exclusiveChars: "IO");
        AllNumericChars = GetCharRange('2', '9');
        AllSpecialChars = "!@#%*()$?+-=";

    }

    public PasswordGenerator(
        int minimumLengthPassword = 15,
        int maximumLengthPassword = 20,
        int minimumLowerCaseChars = 2,
        int minimumUpperCaseChars = 2,
        int minimumNumericChars = 2,
        int minimumSpecialChars = 2)
    {
        if (minimumLengthPassword < 15)
        {
            throw new ArgumentException("The minimumlength is smaller than 15.",
                "minimumLengthPassword");
        }

        if (minimumLengthPassword > maximumLengthPassword)
        {
            throw new ArgumentException("The minimumLength is bigger than the maximum length.",
                "minimumLengthPassword");
        }

        if (minimumLowerCaseChars < 2)
        {
            throw new ArgumentException("The minimumLowerCase is smaller than 2.",
                "minimumLowerCaseChars");
        }

        if (minimumUpperCaseChars < 2)
        {
            throw new ArgumentException("The minimumUpperCase is smaller than 2.",
                "minimumUpperCaseChars");
        }

        if (minimumNumericChars < 2)
        {
            throw new ArgumentException("The minimumNumeric is smaller than 2.",
                "minimumNumericChars");
        }

        if (minimumSpecialChars < 2)
        {
            throw new ArgumentException("The minimumSpecial is smaller than 2.",
                "minimumSpecialChars");
        }

        _minimumNumberOfChars = minimumLowerCaseChars + minimumUpperCaseChars +
                                minimumNumericChars + minimumSpecialChars;

        if (minimumLengthPassword < _minimumNumberOfChars)
        {
            throw new ArgumentException(
                "The minimum length of the password is smaller than the sum " +
                "of the minimum characters of all catagories.",
                "maximumLengthPassword");
        }

        MinimumLengthPassword = minimumLengthPassword;
        MaximumLengthPassword = maximumLengthPassword;

        MinimumLowerCaseChars = minimumLowerCaseChars;
        MinimumUpperCaseChars = minimumUpperCaseChars;
        MinimumNumericChars = minimumNumericChars;
        MinimumSpecialChars = minimumSpecialChars;

        _allAvailableChars =
            OnlyIfOneCharIsRequired(minimumLowerCaseChars, AllLowerCaseChars) +
            OnlyIfOneCharIsRequired(minimumUpperCaseChars, AllUpperCaseChars) +
            OnlyIfOneCharIsRequired(minimumNumericChars, AllNumericChars) +
            OnlyIfOneCharIsRequired(minimumSpecialChars, AllSpecialChars);
    }

    private string OnlyIfOneCharIsRequired(int minimum, string allChars)
    {
        return minimum > 0 || _minimumNumberOfChars == 0 ? allChars : string.Empty;
    }

    public string Generate()
    {
        var lengthOfPassword = _randomSecure.Next(MinimumLengthPassword, MaximumLengthPassword);

        // Get the required number of characters of each catagory and 
        // add random charactes of all catagories
        var minimumChars = GetRandomString(AllLowerCaseChars, MinimumLowerCaseChars) +
                        GetRandomString(AllUpperCaseChars, MinimumUpperCaseChars) +
                        GetRandomString(AllNumericChars, MinimumNumericChars) +
                        GetRandomString(AllSpecialChars, MinimumSpecialChars);
        var rest = GetRandomString(_allAvailableChars, lengthOfPassword - minimumChars.Length);
        var unshuffeledResult = minimumChars + rest;

        // Shuffle the result so the order of the characters are unpredictable
        var result = unshuffeledResult.ShuffleTextSecure();
        return result;
    }

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

    private static string GetCharRange(char minimum, char maximum, string exclusiveChars = "")
    {
        var result = string.Empty;
        for (char value = minimum; value <= maximum; value++)
        {
            result += value;
        }
        if (!string.IsNullOrEmpty(exclusiveChars))
        {
            var inclusiveChars = result.Except(exclusiveChars).ToArray();
            result = new string(inclusiveChars);
        }
        return result;
    }
}

internal static class Extensions
{
    private static readonly Lazy<RandomSecureVersion> RandomSecure =
        new Lazy<RandomSecureVersion>(() => new RandomSecureVersion());
    public static IEnumerable<T> ShuffleSecure<T>(this IEnumerable<T> source)
    {
        var sourceArray = source.ToArray();
        for (int counter = 0; counter < sourceArray.Length; counter++)
        {
            int randomIndex = RandomSecure.Value.Next(counter, sourceArray.Length);
            yield return sourceArray[randomIndex];

            sourceArray[randomIndex] = sourceArray[counter];
        }
    }

    public static string ShuffleTextSecure(this string source)
    {
        var shuffeldChars = source.ShuffleSecure().ToArray();
        return new string(shuffeldChars);
    }
}

internal class RandomSecureVersion
{
    //Never ever ever never use Random() in the generation of anything that requires true security/randomness
    //and high entropy or I will hunt you down with a pitchfork!! Only RNGCryptoServiceProvider() is safe.
    private readonly RNGCryptoServiceProvider _rngProvider = new RNGCryptoServiceProvider();

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

    public int Next(int maximumValue)
    {
        // Do not use Next() % maximumValue because the distribution is not OK
        return Next(0, maximumValue);
    }

    public int Next(int minimumValue, int maximumValue)
    {
        var seed = Next();

        //  Generate uniformly distributed random integers within a given range.
        return new Random(seed).Next(minimumValue, maximumValue);
    }
}

在你的代码中这样使用:

var generator = new PasswordGenerator();
string password = generator.Generate();
Console.WriteLine(password);

这需要一些调整,但是已经是一个很好的开始了。 - mellis481

10

针对你关于那个公式的问题:

这个公式的意思是,从一个包含N个符号的字母表中选取长度为L的密码,等价于从一个只包含两个符号的字母表中选取长度为H的密码。因此,如果你有64个符号(例如:abc...xyzABC...XYZ01...89_!),并且密码长度为10个字符,则这相当于从"ab"字母表中选取长度为10 log2 64 = 60个字符的密码。

"log"是指数运算的逆运算。2的6次方等于64,因此,64的"log two"等于6。


5

我不知道这是否能帮到你,但以下是我用来生成既随机又强壮密码的方法。实现和理解起来都非常简单快捷,而且不像上面那个会员提供者的方法那样过度。

    private string Token(byte Length) {
        char[] Chars = new char[] {
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
        };
        string String = string.Empty;
        Random Random = new Random();

        for (byte a = 0; a < Length; a++) {
            String += Chars[Random.Next(0, 61)];
        };

        return (String);
    }

8
System.Random不应用于生成密码,因为它更容易被预测,也不是用于此目的。相反,请使用 System.Security.Cryptography.RNGCryptoServiceProvider。请参阅 https://dev59.com/30bRa4cB1Zd3GeqPxiNO#411985 和 https://dev59.com/MnRB5IYBdhLWcg3wET1J#8996788。 - Despertar
1
我不建议自己设计安全性。使用像.NET这样的内置安全框架将为您提供经过加密专家深思熟虑的经过彻底测试的代码。自己设计很容易犯很多错误。 - Jordan Morris
我喜欢Richard回答中代码里的注释:永远永远不要在需要真正安全/随机和高熵的任何生成过程中使用Random(),否则我会用草叉追着你!!只有RNGCryptoServiceProvider()是安全的。 - Darkproduct
我同意之前评论的人。还有一件事:由于 Random.Next(0,61),生成的字符串中永远不会出现 '9'。根据 Random.Next 的定义,生成的数字始终小于 61。最好使用 Random.Next(0,Chars.Length)。 - 75ntamas

2
我使用以下字符中的随机字符串:
public static string RandomString(int length)
    {
        const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789&?%$@";
        return new string(Enumerable.Repeat(chars, length)
          .Select(s => s[Random.Next(s.Length)]).ToArray());
    }

1
为什么不直接用一些字符填充一个数组,然后随机选择其中一些字符。您可以将它们分成几组,以确保包含字母、数字和特殊字符。
您还需要选择适当的长度以及要包含每个字符组的数量,就这样。我认为您不需要一些复杂的公式。

1
什么都不是绝对安全的,但如果您将登录限制为每小时5次,您可以减少暴力攻击的风险。 - LiamB
2
挑选随机字符还有什么比这更困难的呢? - anthares
@anthares 我同意,Alon 这是什么用途? - LiamB
不,我不会限制每小时登录次数,这对用户来说非常烦人。相反,我会在3次尝试后加入验证码。 - Alon Gubkin
@Alon那种方法会以相同的方式工作。因此,暴力攻击已被限制。 - LiamB
@Alon,在阅读了所有这些内容之后,我很好奇为什么你需要如此过度的东西?在我的应用程序中,涉及大量个人客户信息,我只是使用下面发布的密码生成器,并具有不同的长度。很高兴看到你对保护您的应用程序感兴趣,但您还必须考虑易用性。最终,如果有人想要黑你,无论您的密码有多强,他们都会设法做到。 - Gup3rSuR4c

1

对于不允许用户生成密码的系统来说,实际上很容易:任何密码的安全性都取决于其长度。当然,不包括那些把便条贴在显示器上的人。

您可能希望最大化生成密码的字符集。但是,限制生成的密码会大大减少搜索空间,因此使密码不够安全。同样,这仅适用于用户无法选择自己的密码的情况。

如果您同时处理生成的密码和用户创建的密码,则显然所有赌注都取消了。然后,您可能希望以尽可能多的不同类别的字符生成密码,以类似于强用户选择的密码。理想情况下,它应符合用户创建的密码必须通过的相同约束(如果有)。


0

以下方法快速且效果良好。

  1. 生成长度为所需长度除以4的随机特殊字符
  2. 生成长度为所需长度除以3的随机小写字母
  3. 生成长度为所需长度除以3的随机大写字母
  4. 生成长度为所需长度除以2的随机数字字符
  5. 将以上四个随机数组连接起来
  6. 从连接后的数组中随机选取所需长度的字符
  7. 返回新字符串

请注意,Random是一个全局静态变量。

public static Random random = new Random();

public static string RandomString(int length)
{
    var specialLength = (int)Math.Ceiling(length / 4d);
    var lowerUpperLength = (int)Math.Ceiling(length / 3d);
    var numericLength = (int)Math.Ceiling(length / 2d);

    var special = Enumerable.Repeat("!@#$%^&|+-.,?", specialLength)
        .Select(chars => chars[random.Next(chars.Length)]).ToArray();
    var lower = Enumerable.Repeat("abcdefghijklmnopqrstuvwxyz", lowerUpperLength)
        .Select(chars => chars[random.Next(chars.Length)]).ToArray();
    var upper = Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZ", lowerUpperLength)
        .Select(chars => chars[random.Next(chars.Length)]).ToArray();
    var numeric = Enumerable.Repeat("0123456789", numericLength)
        .Select(chars => chars[random.Next(chars.Length)]).ToArray();
    
    var scrambledConcat = special.Concat(lower)
        .Concat(upper).Concat(numeric)
        .ToArray();
    
    var scrambledChars = Enumerable.Repeat(scrambledConcat, length)
        .Select(chars => chars[random.Next(chars.Length)]).ToArray();
    
    return new string(scrambledChars);
}

输出长度为65X个示例:

@n.c7c

&T,T0S

2b$9$H

AEEcE0

6x6VB3

输出长度为205X个示例:

41@8?@K4@y@uSu$K31zS

L750T01T#00A17Tq5O+^

%#z#@BEBEbG4xU2AUx26

Ry90j4RyW6.VBRV0-60!

Z84SJ!t075%7a8!nn84M

输出长度为1005X个示例:

!9i7vz297Dlq$ffL35qzi4j63UJ9Nv53^9K@6-t6N17@jy73@VK80^y423H1bLiaHx0q9Ba5b?&9@4154lo?$6@e5L9e6-B00X63

-Ykk9%Mt08Ky4TW426rI53k12F#z6G8WWuR0|Bh?w,mY4XkU2eA8%Wz565to5m42Z6|84%UF-^8N3Uv#$72#d65#BkNtcn%3i8M.

f58vaQg0@VB6sgXeChud@^8f1e63q3e@68ep0d6Eg,861Xq8@dgBZfs2L3d3@7$djZZ232Q&V6q?psX$VB6tSsqZ3HVZk67qA06q

&uZecy^nA?2m4a#&M4?M9%hj7d1dmPUqlIiUaj4Z4zKZInG33uQk161sD4?m4e3B^mKu22n0h1uPJ&#97UQ^ys519^XE1,9UE+4J

4%q,B#0Vi5%BjfG@43Mo^k6+24P3ek#32L94s3%!Yki3^53%iCmP,.K%4+m.bw06K,JfU4e2P8WJ0^%40%^K150iz44-0fn?U8L0

随意添加或删除字符串中的字符。


0
我最初使用了类似于this answer的随机字符串,但被SonarQube标记了。SonarQube将Random.Next视为不安全的随机性。SonarQube建议改为生成具有密码学强度的伪随机数。
var randomGenerator = RandomNumberGenerator.Create();
var data = new byte[16];
randomGenerator.GetBytes(data);
return BitConverter.ToString(data);

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