生成唯一的密码
本文关键字:密码 唯一 | 更新日期: 2023-09-27 18:03:39
我正在制作一个应用程序来生成密码,现在我已经编写了一个单元测试来测试当你生成2个密码时它们是唯一的,但我得到的问题是它们不是唯一的,但只是相同的。
单元测试:
[TestMethod]
public void PasswordGeneratorShouldRenderUniqueNextPassword()
{
// Create an instance, and generate two passwords
var generator = new PasswordGenerator();
var firstPassword = generator.Generate(8);
var secondPassword = generator.Generate(8);
// Verify that both passwords are unique
Assert.AreNotEqual(firstPassword, secondPassword);
}
我猜这里有问题:
for (int i = 0; i < length; i++)
{
int x = random.Next(0, length);
if (!password.Contains(chars.GetValue(x).ToString()))
password += chars.GetValue(x);
else
i--;
}
if (length < password.Length) password = password.Substring(0, length);
return password;
随机: Random random = new Random((int)DateTime.Now.Ticks);
如果您很快生成两个密码,它们将在同一时间生成。
如果您只想生成一个随机的人类可读密码,请查看这里。如果你想知道为什么Random
不适合这个目的,以及如何做一些更合适的事情,请继续阅读。
最快的方法是使用
Random()
的默认构造函数,它会为你做播种。
查看文档后,默认构造函数使用基于时间的种子,因此您在使用它时会遇到同样的问题。无论如何,Random
类太可预测,不能用于安全的密码生成。
如果你正在寻找更多的力量,你可以这样做,
using System.Security.Cryptography;
static string GetPassword(int length = 13)
{
var rng = new RNGCryptoServiceProvider();
var buffer = new byte[length * sizeof(char)];
rng.GetNonZeroBytes(buffer);
return new string(Encoding.Unicode.GetChars(buffer));
}
然而,如果你希望人们能够阅读、记住和输入你生成的密码,你应该在可能的字符范围内限制一下。
我已经更新了这一部分,以给出一个详细的,现代的,公正的答案。
如果你想把输出限制为一组特定的字符,你可以这样做:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
/// <summary>
/// Get a random password.
/// </summary>
/// <param name="valid">A list of valid password chars.</param>
/// <param name="length">The length of the password.</returns>
/// <returns>A random password.</returns>
public static string GetPassword(IList<char> valid, int length = 13)
{
return new string(GetRandomSelection(valid, length).ToArray());
}
/// <summary>
/// Gets a random selection from <paramref name="valid"/>.
/// </summary>
/// <typeparam name="T">The item type.</typeparam>
/// <param name="valid">List of valid possibilities.</param>
/// <param name="length">The length of the result sequence.</param>
/// <returns>A random sequence</returns>
private static IEnumerable<T> GetRandomSelection<T>(
IList<T> valid,
int length)
{
// The largest multiple of valid.Count less than ulong.MaxValue.
// This upper limit prevents bias in the results.
var max = ulong.MaxValue - (ulong.MaxValue % (ulong)valid.Count);
// A finite sequence of random ulongs.
var ulongs = RandomUInt64Sequence(max, length).Take(length);
// A sequence of indecies.
var indecies = ulongs.Select((u => (int)(u % (ulong)valid.Count)));
return indecies.Select(i => valid[i]);
}
/// <summary>
/// An infinite sequence of random <see cref="ulong"/>s.
/// </summary>
/// <param name="max">
/// The maximum inclusive <see cref="ulong"/> to return.
/// </param>
/// <param name="poolSize">
/// The size, in <see cref="ulong"/>s, of the pool used to
/// optimize <see cref="RNGCryptoServiceProvider"/> calls.
/// </param>
/// <returns>A random <see cref="ulong"/> sequence.</returns>
private static IEnumerable<ulong RandomUInt64Sequence(
ulong max = UInt64.MaxValue,
int poolSize = 100)
{
var rng = new RNGCryptoServiceProvider();
var pool = new byte[poolSize * sizeof(ulong)];
while (true)
{
rng.GetBytes(pool);
for (var i = 0; i < poolSize; i++)
{
var candidate = BitConvertor.ToUInt64(pool, i * sizeof(ulong));
if (candidate > max)
{
continue;
}
yield return candidate;
}
}
}
您可以像这样使用此代码,首先您需要一组有效的char
a,可以在您的密码中,
var validChars = new[] { 'A', 'B', 'C' };
为了说明,我只包含了3个char
,在实践中,您可能希望包含更多的char
。然后,要生成一个随机密码8 char
s长,您可以拨打此呼叫。
var randomPassword = GetPassword(validChars, 8);
在实践中,您可能希望您的密码至少为13个char
秒。
你的问题是你正在使用默认的随机构造函数,它使用当前日期/时间作为种子。DateTime。滴答的分辨率是100纳秒。这很快,但对于单元测试来说还不够快,因为单元测试需要在不到100 ns的时间内生成两个密码。
一个解决方案是在密码生成器中使用静态Random实例。
public class PasswordGenerator
{
private static Random random = new Random();
public string Generate()
{
for (int i = 0; i < length; i++)
{
int x = random.Next(0, length);
if (!password.Contains(chars.GetValue(x).ToString()))
password += chars.GetValue(x);
else
i--;
}
if (length < password.Length) password = password.Substring(0, length);
return password;
}
}
DateTime.Now.Ticks
不是很准确,虽然它看起来代表了非常小的时间片段,但它实际上代表了几毫秒。
由于您的密码算法可能需要十分之一毫秒,这导致DateTime.Now.Ticks
具有相同的值。
两种选择是提供一种方法来提供一个种子(这将允许您使用第三个随机数生成器来创建种子)或传递一个随机对象(这将确保在相同的种子上顺序创建两个,创建不同的值)。
我将在PasswordGenerator
构造函数中创建Random
对象,以确保每次调用Generate
方法时,您将获得一个(或多或少)随机的数字。
public PassworGenerator()
{
random = new Random(/* seed */);
}