Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ported Password Generator to DevToys 2.0 #1001

Merged
merged 5 commits into from
Dec 16, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions src/app/dev/DevToys.Tools/DevToys.Tools.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,16 @@
<AutoGen>True</AutoGen>
<DependentUpon>HashAndChecksumGenerator.resx</DependentUpon>
</Compile>
<Compile Update="Tools\Generators\Password\PasswordGenerator.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>PasswordGenerator.resx</DependentUpon>
</Compile>
<Compile Update="Tools\Generators\UUID\UUIDGenerator.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>UUIDGenerator.resx</DependentUpon>
</Compile>
<Compile Update="Tools\Graphic\ColorBlindnessSimulator\ColorBlindnessSimulator.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
Expand Down Expand Up @@ -175,6 +185,14 @@
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>HashAndChecksumGenerator.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Tools\Generators\Password\PasswordGenerator.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>PasswordGenerator.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Tools\Generators\UUID\UUIDGenerator.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>UUIDGenerator.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Update="Tools\Graphic\ColorBlindnessSimulator\ColorBlindnessSimulator.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>ColorBlindnessSimulator.Designer.cs</LastGenOutput>
Expand Down
131 changes: 131 additions & 0 deletions src/app/dev/DevToys.Tools/Helpers/Core/CryptoRandom.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using System.Security.Cryptography;

namespace DevToys.Tools.Helpers.Core;

/// <summary>
/// A class that mimics the standard Random class in the .NET Framework - but uses a random number generator internally.
/// Taken from IdentityModel (ref.: https://github.com/IdentityModel/IdentityModel/ )
/// Taken from PasswordGenerator (ref.: https://github.com/Darkseal/PasswordGenerator/blob/master/CryptoRandom.cs )
/// </summary>
internal sealed class CryptoRandom : Random
{
private static readonly RandomNumberGenerator Rng = RandomNumberGenerator.Create();
private readonly byte[] _uint32Buffer = new byte[4];

/// <summary>
/// Output format for unique IDs
/// </summary>
private enum OutputFormat
{
/// <summary>
/// URL-safe Base64
/// </summary>
Base64Url,
/// <summary>
/// Base64
/// </summary>
Base64,
/// <summary>
/// Hex
/// </summary>
Hex
}

/// <summary>
/// Initializes a new instance of the <see cref="CryptoRandom"/> class.
/// </summary>
internal CryptoRandom()
{
}

/// <summary>
/// Returns a nonnegative random number.
/// </summary>
/// <returns>
/// A 32-bit signed integer greater than or equal to zero and less than <see cref="F:System.Int32.MaxValue"/>.
/// </returns>
public override int Next()
{
Rng.GetBytes(_uint32Buffer);
return BitConverter.ToInt32(_uint32Buffer, 0) & 0x7FFFFFFF;
}

/// <summary>
/// Returns a nonnegative random number less than the specified maximum.
/// </summary>
/// <param name="maxValue">The exclusive upper bound of the random number to be generated. <paramref name="maxValue"/> must be greater than or equal to zero.</param>
/// <returns>
/// A 32-bit signed integer greater than or equal to zero, and less than <paramref name="maxValue"/>; that is, the range of return values ordinarily includes zero but not <paramref name="maxValue"/>. However, if <paramref name="maxValue"/> equals zero, <paramref name="maxValue"/> is returned.
/// </returns>
/// <exception cref="T:System.ArgumentOutOfRangeException">
/// <paramref name="maxValue"/> is less than zero.
/// </exception>
public override int Next(int maxValue)
{
if (maxValue < 0)
throw new ArgumentOutOfRangeException(nameof(maxValue));

return Next(0, maxValue);
}

/// <summary>
/// Returns a random number within a specified range.
/// </summary>
/// <param name="minValue">The inclusive lower bound of the random number returned.</param>
/// <param name="maxValue">The exclusive upper bound of the random number returned. <paramref name="maxValue"/> must be greater than or equal to <paramref name="minValue"/>.</param>
/// <returns>
/// A 32-bit signed integer greater than or equal to <paramref name="minValue"/> and less than <paramref name="maxValue"/>; that is, the range of return values includes <paramref name="minValue"/> but not <paramref name="maxValue"/>. If <paramref name="minValue"/> equals <paramref name="maxValue"/>, <paramref name="minValue"/> is returned.
/// </returns>
/// <exception cref="T:System.ArgumentOutOfRangeException">
/// <paramref name="minValue"/> is greater than <paramref name="maxValue"/>.
/// </exception>
public override int Next(int minValue, int maxValue)
{
if (minValue > maxValue)
throw new ArgumentOutOfRangeException(nameof(minValue));

if (minValue == maxValue)
return minValue;

long diff = maxValue - minValue;

while (true)
{
Rng.GetBytes(_uint32Buffer);
uint rand = BitConverter.ToUInt32(_uint32Buffer, 0);

long max = 1 + (long)uint.MaxValue;
long remainder = max % diff;
if (rand < max - remainder)
return (int)(minValue + rand % diff);
}
}

/// <summary>
/// Returns a random number between 0.0 and 1.0.
/// </summary>
/// <returns>
/// A double-precision floating point number greater than or equal to 0.0, and less than 1.0.
/// </returns>
public override double NextDouble()
{
Rng.GetBytes(_uint32Buffer);
uint rand = BitConverter.ToUInt32(_uint32Buffer, 0);
return rand / (1.0 + uint.MaxValue);
}

/// <summary>
/// Fills the elements of a specified array of bytes with random numbers.
/// </summary>
/// <param name="buffer">An array of bytes to contain random numbers.</param>
/// <exception cref="T:System.ArgumentNullException">
/// <paramref name="buffer"/> is null.
/// </exception>
public override void NextBytes(byte[] buffer)
{
if (buffer == null)
throw new ArgumentNullException(nameof(buffer));

Rng.GetBytes(buffer);
}
}
107 changes: 107 additions & 0 deletions src/app/dev/DevToys.Tools/Helpers/PasswordGeneratorHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using System.Text;
using DevToys.Tools.Helpers.Core;

namespace DevToys.Tools.Helpers;

internal static class PasswordGeneratorHelper
{
/// <summary>
/// All non-alphanumeric characters.
/// </summary>
internal const string NonAlphanumeric = "!@#$%^&*";

/// <summary>
/// All lower case ASCII characters.
/// </summary>
internal const string LowercaseLetters = "abcdefghijkmnopqrstuvwxyz";

/// <summary>
/// All upper case ASCII characters.
/// </summary>
internal const string UppercaseLetters = "ABCDEFGHJKLMNOPQRSTUVWXYZ";

/// <summary>
/// All digits.
/// </summary>
internal const string Digits = "0123456789";

internal static string GeneratePassword(
int length,
bool hasUppercase,
bool hasLowercase,
bool hasNumbers,
bool hasSpecialCharacters,
char[]? excludedCharacters)
{
if (length <= 0)
{
return string.Empty;
}

// Combine all character sets together.
string[] randomChars = new[] {
string.Empty,
string.Empty,
string.Empty,
string.Empty
};

var rand = new CryptoRandom();
var newPasswordCharacters = new List<char>();

if (hasUppercase)
{
randomChars[0] = RemoveExcludedCharacters(UppercaseLetters, excludedCharacters);
}

if (hasLowercase)
{
randomChars[1] = RemoveExcludedCharacters(LowercaseLetters, excludedCharacters);
}

if (hasNumbers)
{
randomChars[2] = RemoveExcludedCharacters(Digits, excludedCharacters);
}

if (hasSpecialCharacters)
{
randomChars[3] = RemoveExcludedCharacters(NonAlphanumeric, excludedCharacters);
}

randomChars = randomChars.Where(r => r.Length > 0).ToArray();

// Only continue if the user hasn't excluded everything.
if (randomChars.Length != 0)
{
for (int j = 0; j < length; j++)
{
string rcs = randomChars[rand.Next(0, randomChars.Length)];
newPasswordCharacters.Insert(rand.Next(0, newPasswordCharacters.Count), rcs[rand.Next(0, rcs.Length)]);
}
}

return new string(newPasswordCharacters.ToArray());
}

private static string RemoveExcludedCharacters(string input, char[]? excludedCharacters)
{
if (excludedCharacters == null || excludedCharacters.Length == 0)
{
return input;
}

var excludedSet = new HashSet<char>(excludedCharacters); // HashSet provides a faster lookup than Array.Contains().
var stringBuilder = new StringBuilder();

foreach (char c in input)
{
if (!excludedSet.Contains(c))
{
stringBuilder.Append(c);
}
}

return stringBuilder.ToString();
}
}
Loading