我在想如何在C#中生成一个强大且安全的密码。
我在谷歌上搜索了一下,在维基百科上看到了这个公式,其中L
是密码长度,N
是可能的符号数量:
同时,我发现这个问题,但由于某种原因,Membership.GeneratePassword
方法只会返回一个带有1位数字的随机数,完全不是一个密码。其它所有解决方案都很慢(>= 0.5秒)。
我需要帮助来实现这个公式(我不知道从哪里开始)。您也可以建议另一种解决方案或解释为什么GeneratePassword
方法不起作用。
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
我不确定我从哪里找到的,但这是一个生成高熵、真正随机的字符串并可用作密码的类。
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);
针对你关于那个公式的问题:
这个公式的意思是,从一个包含N个符号的字母表中选取长度为L的密码,等价于从一个只包含两个符号的字母表中选取长度为H的密码。因此,如果你有64个符号(例如:abc...xyzABC...XYZ01...89_!),并且密码长度为10个字符,则这相当于从"ab"字母表中选取长度为10 log2 64 = 60个字符的密码。
"log"是指数运算的逆运算。2的6次方等于64,因此,64的"log two"等于6。
我不知道这是否能帮到你,但以下是我用来生成既随机又强壮密码的方法。实现和理解起来都非常简单快捷,而且不像上面那个会员提供者的方法那样过度。
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);
}
System.Random
不应用于生成密码,因为它更容易被预测,也不是用于此目的。相反,请使用 System.Security.Cryptography.RNGCryptoServiceProvider
。请参阅 https://dev59.com/30bRa4cB1Zd3GeqPxiNO#411985 和 https://dev59.com/MnRB5IYBdhLWcg3wET1J#8996788。 - Despertar永远永远不要在需要真正安全/随机和高熵的任何生成过程中使用Random(),否则我会用草叉追着你!!只有RNGCryptoServiceProvider()是安全的。
- Darkproductpublic static string RandomString(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789&?%$@";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[Random.Next(s.Length)]).ToArray());
}
对于不允许用户生成密码的系统来说,实际上很容易:任何密码的安全性都取决于其长度。当然,不包括那些把便条贴在显示器上的人。
您可能希望最大化生成密码的字符集。但是,限制生成的密码会大大减少搜索空间,因此使密码不够安全。同样,这仅适用于用户无法选择自己的密码的情况。
如果您同时处理生成的密码和用户创建的密码,则显然所有赌注都取消了。然后,您可能希望以尽可能多的不同类别的字符生成密码,以类似于强用户选择的密码。理想情况下,它应符合用户创建的密码必须通过的相同约束(如果有)。
以下方法快速且效果良好。
特殊字符
小写字母
大写字母
数字字符
请注意,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);
}
输出长度为6
的5X
个示例:
@n.c7c
&T,T0S
2b$9$H
AEEcE0
6x6VB3
输出长度为20
的5X
个示例:
41@8?@K4@y@uSu$K31zS
L750T01T#00A17Tq5O+^
%#z#@BEBEbG4xU2AUx26
Ry90j4RyW6.VBRV0-60!
Z84SJ!t075%7a8!nn84M
输出长度为100
的5X
个示例:
!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^mKu22n0h1uPJaUQ^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
随意添加或删除字符串中的字符。
Random.Next
视为不安全的随机性。SonarQube建议改为生成具有密码学强度的伪随机数。var randomGenerator = RandomNumberGenerator.Create();
var data = new byte[16];
randomGenerator.GetBytes(data);
return BitConverter.ToString(data);
Membership.GeneratePassword
是基于System.Security.Cryptography.RNGCryptoServiceProvider
实现的,因此你可以使用它来实现自己的GeneratePassword。这里有一个例子:http://www.obviex.com/Samples/Password.aspx - Alon Gubkin