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

2 つのパスワードを非常に迅速に生成すると、それらは同じティックで生成されます。


人間が読めるランダムなパスワードを生成したいだけの場合は、こちらをご覧ください。Randomがこの目的に適さない理由と、より適切な方法を知りたい場合は、読み進めてください。


最も簡単な方法は、デフォルトのコンストラクターを使用するRandom()ことです。これにより、シード処理が行われます。

documentationを確認した後、デフォルトのコンストラクターは時間ベースのシードを使用するため、その使用で同じ問題が発生します。とにかく、この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の s だけを含めましたが、実際にはもっと多くcharの s を含める必要があります。次に、8 秒間のランダムなパスワードを生成するには、次のchar呼び出しを行います。

var randomPassword = GetPassword(validChars, 8);

実際には、パスワードを少なくとも 13 秒にしたいでしょうchar

于 2013-03-13T16:03:55.107 に答える
0

問題は、現在の日付/時刻をシードとして使用するデフォルトの Random コンストラクターを使用していることです。DateTime.Ticks の分解能は 100 ナノ秒です。これは高速ですが、両方のパスワードを 100 ns 未満で生成する単体テストには十分ではありません。

1 つの解決策は、パスワード ジェネレーターで静的な 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非常に短い時間のスライスを表しているように見えますが、実際には数ミリ秒を表しています。

パスワード アルゴリズムはおそらく 10 分の 1 ミリ秒かかるためDateTime.Now.Ticks、同じ値になります。

2 つの選択肢は、シードを与える方法 (3 番目の乱数ジェネレーターを使用してシードを作成できるようにする方法) を提供する方法と、ランダム オブジェクトを渡す方法 (2 つが同じシードから順次作成され、異なる値を作成することを保証する方法) です。 )。

于 2013-03-13T16:05:53.667 に答える
0

メソッドを呼び出すたびに(多かれ少なかれ) ランダムな数値が得られるようRandomに、コンストラクター内でオブジェクトを作成します。PasswordGeneratorGenerate

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