-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ported Password Generator to DevToys 2.0 (#1001)
* Ported Password Generator to DevToys 2.0 * Added unit tests
- Loading branch information
Showing
8 changed files
with
1,296 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
107
src/app/dev/DevToys.Tools/Helpers/PasswordGeneratorHelper.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
Oops, something went wrong.