// *********************************************************************** // Assembly : FCS.Lib // Author : FH // Created : 2020-07-01 // // Last Modified By : FH // Last Modified On : 2021-02-24 // *********************************************************************** // // Copyright © FCS 2015-2022 // // // *********************************************************************** using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; namespace FCS.Lib { /// /// Class Generators /// generates varioous kinds of random strings. /// public static class Generators { /// /// Shorts the URL generator. /// /// System.String. public static string ShortUrlGenerator() { return ShortUrlGenerator(6); } /// /// Randoms the string. /// /// The lengt h. /// System.String. /// derived from https://sourceforge.net/projects/shorturl-dotnet/ public static string ShortUrlGenerator(int length) { const string charsLower = "cdfghjkmnpqrstvwxyz"; const string charsUpper = "BCDFGHJKLMNPQRSTVWXYZ-_"; const string charsNumeric = "23456789"; // Create a local array containing supported short-url characters // grouped by types. var charGroups = new[] { charsLower.ToCharArray(), charsUpper.ToCharArray(), charsNumeric.ToCharArray() }; // Use this array to track the number of unused characters in each // character group. var charsLeftInGroup = new int[charGroups.Length]; // Initially, all characters in each group are not used. for (var i = 0; i < charsLeftInGroup.Length; i++) charsLeftInGroup[i] = charGroups[i].Length; // Use this array to track (iterate through) unused character groups. var leftGroupsOrder = new int[charGroups.Length]; // Initially, all character groups are not used. for (var i = 0; i < leftGroupsOrder.Length; i++) leftGroupsOrder[i] = i; // Using our private randomizer var random = RandomSeed(); // This array will hold short-url characters. // Allocate appropriate memory for the short-url. var shortUrl = new char[random.Next(length, length)]; // Index of the last non-processed group. var lastLeftGroupsOrderIdx = leftGroupsOrder.Length - 1; // Generate short-url characters one at a time. for (var i = 0; i < shortUrl.Length; i++) { // If only one character group remained unprocessed, process it; // otherwise, pick a random character group from the unprocessed // group list. To allow a special character to appear in the // first position, increment the second parameter of the Next // function call by one, i.e. lastLeftGroupsOrderIdx + 1. int nextLeftGroupsOrderIdx; if (lastLeftGroupsOrderIdx == 0) nextLeftGroupsOrderIdx = 0; else nextLeftGroupsOrderIdx = random.Next(0, lastLeftGroupsOrderIdx); // Get the actual index of the character group, from which we will // pick the next character. var nextGroupIdx = leftGroupsOrder[nextLeftGroupsOrderIdx]; // Get the index of the last unprocessed characters in this group. var lastCharIdx = charsLeftInGroup[nextGroupIdx] - 1; // If only one unprocessed character is left, pick it; otherwise, // get a random character from the unused character list. var nextCharIdx = lastCharIdx == 0 ? 0 : random.Next(0, lastCharIdx + 1); // Add this character to the short-url. shortUrl[i] = charGroups[nextGroupIdx][nextCharIdx]; // If we processed the last character in this group, start over. if (lastCharIdx == 0) { charsLeftInGroup[nextGroupIdx] = charGroups[nextGroupIdx].Length; } // There are more unprocessed characters left. else { // Swap processed character with the last unprocessed character // so that we don't pick it until we process all characters in // this group. if (lastCharIdx != nextCharIdx) { var temp = charGroups[nextGroupIdx][lastCharIdx]; charGroups[nextGroupIdx][lastCharIdx] = charGroups[nextGroupIdx][nextCharIdx]; charGroups[nextGroupIdx][nextCharIdx] = temp; } // Decrement the number of unprocessed characters in // this group. charsLeftInGroup[nextGroupIdx]--; } // If we processed the last group, start all over. if (lastLeftGroupsOrderIdx == 0) { lastLeftGroupsOrderIdx = leftGroupsOrder.Length - 1; } // There are more unprocessed groups left. else { // Swap processed group with the last unprocessed group // so that we don't pick it until we process all groups. if (lastLeftGroupsOrderIdx != nextLeftGroupsOrderIdx) { var temp = leftGroupsOrder[lastLeftGroupsOrderIdx]; leftGroupsOrder[lastLeftGroupsOrderIdx] = leftGroupsOrder[nextLeftGroupsOrderIdx]; leftGroupsOrder[nextLeftGroupsOrderIdx] = temp; } // Decrement the number of unprocessed groups. lastLeftGroupsOrderIdx--; } } // Convert password characters into a string and return the result. return new string(shortUrl); } /// /// Generates the username. /// /// The options. /// System.String. public static string GenerateUsername(StringOptions options = null) { options ??= new StringOptions { RequiredLength = 10, RequireDigit = true, RequireLowercase = true, RequireUppercase = true, RequiredUniqueChars = 4, RequireNonLetterOrDigit = false, RequireNonAlphanumeric = false }; return GenerateRandomString(options); } /// /// Generates the password. /// /// The options. /// System.String. public static string GeneratePassword(StringOptions options = null) { options ??= new StringOptions { RequiredLength = 10, RequireDigit = true, RequireLowercase = true, RequireUppercase = true, RequiredUniqueChars = 4, RequireNonLetterOrDigit = true, RequireNonAlphanumeric = true }; return GenerateRandomString(options); } /// /// Generates the random text. /// /// The length. /// System.String. public static string GenerateRandomText(int length) { const string consonants = "bdfghjklmnprstvyBDFGHJKLMNPRSTVY"; const string vowels = "aeiouAEIOU"; var rndString = ""; var randomNum = RandomSeed(); while (rndString.Length < length) { rndString += consonants[randomNum.Next(consonants.Length)]; if (rndString.Length < length) rndString += vowels[randomNum.Next(vowels.Length)]; } return rndString; } /// /// Generates the random password. /// /// The options. /// System.String. public static string GenerateRandomString(StringOptions options = null) { options ??= new StringOptions { RequiredLength = 10, RequireDigit = true, RequireLowercase = true, RequireUppercase = true, RequiredUniqueChars = 4, RequireNonLetterOrDigit = true, RequireNonAlphanumeric = true }; var randomChars = new[] { "ABCDEFGHJKLMNOPQRSTUVWXYZ", // uppercase "abcdefghijkmnopqrstuvwxyz", // lowercase "0123456789", // digits "!@$?_-" // non-alphanumeric }; // Using our private randomizer var rand = RandomSeed(); var chars = new List(); if (options.RequireUppercase) chars.Insert(rand.Next(0, chars.Count), randomChars[0][rand.Next(0, randomChars[0].Length)]); if (options.RequireLowercase) chars.Insert(rand.Next(0, chars.Count), randomChars[1][rand.Next(0, randomChars[1].Length)]); if (options.RequireDigit) chars.Insert(rand.Next(0, chars.Count), randomChars[2][rand.Next(0, randomChars[2].Length)]); if (options.RequireNonAlphanumeric) chars.Insert(rand.Next(0, chars.Count), randomChars[3][rand.Next(0, randomChars[3].Length)]); var rcs = randomChars[rand.Next(0, randomChars.Length)]; for (var i = chars.Count; i < options.RequiredLength || chars.Distinct().Count() < options.RequiredUniqueChars; i++) chars.Insert(rand.Next(0, chars.Count), rcs[rand.Next(0, rcs.Length)]); return new string(chars.ToArray()); } /// /// Randoms the seed. /// /// Random. /// derived from https://sourceforge.net/projects/shorturl-dotnet/ public static Random RandomSeed() { // As the default randomizer is based on the current time // so it produces the same "random" number within a second // Use a crypto randomizer to create the seed value // 4-byte array to fill with random bytes var randomBytes = new byte[4]; // Generate 4 random bytes. using (var rng = new RNGCryptoServiceProvider()) { rng.GetBytes(randomBytes); } // Convert 4 bytes into a 32-bit integer value. var seed = ((randomBytes[0] & 0x7f) << 24) | (randomBytes[1] << 16) | (randomBytes[2] << 8) | randomBytes[3]; // Return a truly randomized random generator return new Random(seed); } } }