3

I am making a app to generate passwords, now i have write a unit test to test when you generate 2 passwords they are unique, but i got the problem they are not unique but just the same.

Unit test:

[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);
}

Something in here is wrong i guess:

 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 random = new Random((int)DateTime.Now.Ticks);

4 Answers4

5

If you generate two passwords very quickly they will be generated on the same tick.


If you just want to generate a random human readable password, look here. If you want to know why Random is not good for this purpose and how you might do something more appropriate read on.


The quickest thing to do is use the default constructor of Random(), that will do the seeding for you.

After checking the documentation, the default constructor uses a time based seed, so you'd suffer the same issues with its use. Anyway, the Random class is too predictable to use for safe password generation.

If you are looking for a little more strength you could do this,

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));
}

However, if you want humans to be able to read, remember and type your generated passwords you should be a little more limited in your range of possible characters.


I've updated this part to give a detailed, modern, unbiased answer.

If you want to limit output to a certain set of chars you can do something like this.

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;
        }
    }
}

You can use this code like this, first you need a set of valid chara that could be in your password,

var validChars = new[] { 'A', 'B', 'C' };

for ilustration i've included just 3 chars, in practice you'd want many more chars to be included. Then, to genrate a random password 8 chars long you make this call.

var randomPassword = GetPassword(validChars, 8);

In practice, you probably want your passwords to be at least 13 chars.

Community
  • 1
  • 1
Jodrell
  • 34,946
  • 5
  • 87
  • 124
0

Your problem is that you are Your problem is that you are using the default Random constrctor, which uses the current date/time as a seed. DateTime.Ticks has a resolution of 100 nanoseconds. This is quick, but not quick enough for your unit test, which is generating both passswords in less than 100 ns.

One solution is to use a static Random instance in your password generator.

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;
    }
}
RB.
  • 36,301
  • 12
  • 91
  • 131
0

DateTime.Now.Ticks is not very accurate, while it appears to represent a very small slice of time, it actually represents several milliseconds.

Since your password algorithm probably takes a tenth of a millisecond, this is causing DateTime.Now.Ticks to have the same value.

Two alternatives are to provide a way to give a seed (that would let you use a third random number generator to create seeds) or pass in a random object (that would ensure that the two are created sequentially off the same seed, creating different values).

Guvante
  • 18,775
  • 1
  • 33
  • 64
0

I would create the Random object within the PasswordGenerator constructor, to ensure that every time you call the Generate method, you will get a number that is (more or less) random.

public PassworGenerator()
{
    random = new Random(/* seed */);
}
Nolonar
  • 5,962
  • 3
  • 36
  • 55