3

我正在制作一个生成密码的应用程序,现在我已经编写了一个单元测试来测试你何时生成 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);
4

4 回答 4

5

如果您非常快速地生成两个密码,它们将在同一个刻度上生成。


如果您只想生成一个随机的人类可读密码,请看这里。如果您想知道为什么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 个chars,实际上您希望char包含更多的 s。然后,要生成一个 8 秒长的随机密码char,您可以进行此调用。

var randomPassword = GetPassword(validChars, 8);

在实践中,您可能希望您的密码至少为 13char秒。

于 2013-03-13T16:03:55.107 回答
0

您的问题是您是您的问题是您使用的是默认的随机构造函数,它使用当前日期/时间作为种子。DateTime.Ticks 的分辨率为 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;
    }
}
于 2013-03-13T16:05:53.473 回答
0

DateTime.Now.Ticks不是很准确,虽然它看起来代表了一个非常小的时间片,但它实际上代表了几毫秒。

由于您的密码算法可能需要十分之一毫秒,这导致DateTime.Now.Ticks具有相同的值。

两种选择是提供一种提供种子的方法(这将允许您使用第三个随机数生成器来创建种子)或传入一个随机对象(这将确保这两个对象是从同一个种子顺序创建的,创建不同的值)。

于 2013-03-13T16:05:53.667 回答
0

我会Random在构造函数中创建对象PasswordGenerator,以确保每次调用该Generate方法时,都会得到一个(或多或少)随机的数字。

public PassworGenerator()
{
    random = new Random(/* seed */);
}
于 2013-03-13T16:06:04.073 回答