Jak wygenerować losowy ciąg 8 znaków alfanumerycznych w języku C #?
Random
klasie do generowania haseł. Siew Random
ma bardzo niską entropię, więc nie jest tak naprawdę bezpieczny. Użyj hasła kryptograficznego PRNG.
Jak wygenerować losowy ciąg 8 znaków alfanumerycznych w języku C #?
Random
klasie do generowania haseł. Siew Random
ma bardzo niską entropię, więc nie jest tak naprawdę bezpieczny. Użyj hasła kryptograficznego PRNG.
Odpowiedzi:
Słyszałem, że LINQ to nowa czerń, więc oto moja próba użycia LINQ:
private static Random random = new Random();
public static string RandomString(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[random.Next(s.Length)]).ToArray());
}
(Uwaga: użycie tej Random
klasy sprawia, że nie nadaje się do jakichkolwiek kwestii związanych z bezpieczeństwem , takich jak tworzenie haseł lub tokenów. Użyj RNGCryptoServiceProvider
klasy, jeśli potrzebujesz silnego generatora liczb losowych.)
return new string(Enumerable.Range(1, length).Select(_ => chars[random.Next(chars.Length)]).ToArray());
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var stringChars = new char[8];
var random = new Random();
for (int i = 0; i < stringChars.Length; i++)
{
stringChars[i] = chars[random.Next(chars.Length)];
}
var finalString = new String(stringChars);
Nie tak elegancki jak rozwiązanie Linq.
(Uwaga: Random
czyni to klasanie nadaje się do jakichkolwiek kwestii związanych z bezpieczeństwem , takich jak tworzenie haseł lub tokenów. Użyj RNGCryptoServiceProvider
klasy, jeśli potrzebujesz silnego generatora liczb losowych.)
GetRandomFileName
jest szybsze, ale nie pozwala na kontrolę używanych znaków, a maksymalna możliwa długość to 11 znaków. Guid
Rozwiązanie Douglasa jest błyskawiczne, ale postacie są ograniczone do A-F0-9, a maksymalna możliwa długość to 32 znaki.
GetRandomFileName
ale wtedy (a) stracisz przewagę wydajności i (b) kod stanie się bardziej skomplikowany.
System.Random
nie nadaje się do bezpieczeństwa.
AKTUALIZACJA na podstawie komentarzy. Oryginalna implementacja generowała ah ~ 1,95% czasu, a pozostałe znaki ~ 1,56% czasu. Aktualizacja generuje wszystkie znaki ~ 1,61% czasu.
WSPARCIE RAMOWE - .NET Core 3 (i przyszłe platformy obsługujące .NET Standard 2.1 lub nowszy) zapewnia kryptograficznie solidną metodę RandomNumberGenerator.GetInt32 () w celu wygenerowania losowej liczby całkowitej w żądanym zakresie.
W przeciwieństwie do niektórych przedstawionych alternatyw, ta jest kryptograficznie solidna .
using System;
using System.Security.Cryptography;
using System.Text;
namespace UniqueKey
{
public class KeyGenerator
{
internal static readonly char[] chars =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
public static string GetUniqueKey(int size)
{
byte[] data = new byte[4*size];
using (RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider())
{
crypto.GetBytes(data);
}
StringBuilder result = new StringBuilder(size);
for (int i = 0; i < size; i++)
{
var rnd = BitConverter.ToUInt32(data, i * 4);
var idx = rnd % chars.Length;
result.Append(chars[idx]);
}
return result.ToString();
}
public static string GetUniqueKeyOriginal_BIASED(int size)
{
char[] chars =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
byte[] data = new byte[size];
using (RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider())
{
crypto.GetBytes(data);
}
StringBuilder result = new StringBuilder(size);
foreach (byte b in data)
{
result.Append(chars[b % (chars.Length)]);
}
return result.ToString();
}
}
}
Na podstawie dyskusji o alternatywach tutaj i zaktualizowanej / zmodyfikowanej na podstawie poniższych komentarzy.
Oto mała uprząż testowa, która demonstruje rozkład znaków w starych i zaktualizowanych wynikach. W celu dogłębnej dyskusji na temat analizy losowości odwiedź random.org.
using System;
using System.Collections.Generic;
using System.Linq;
using UniqueKey;
namespace CryptoRNGDemo
{
class Program
{
const int REPETITIONS = 1000000;
const int KEY_SIZE = 32;
static void Main(string[] args)
{
Console.WriteLine("Original BIASED implementation");
PerformTest(REPETITIONS, KEY_SIZE, KeyGenerator.GetUniqueKeyOriginal_BIASED);
Console.WriteLine("Updated implementation");
PerformTest(REPETITIONS, KEY_SIZE, KeyGenerator.GetUniqueKey);
Console.ReadKey();
}
static void PerformTest(int repetitions, int keySize, Func<int, string> generator)
{
Dictionary<char, int> counts = new Dictionary<char, int>();
foreach (var ch in UniqueKey.KeyGenerator.chars) counts.Add(ch, 0);
for (int i = 0; i < REPETITIONS; i++)
{
var key = generator(KEY_SIZE);
foreach (var ch in key) counts[ch]++;
}
int totalChars = counts.Values.Sum();
foreach (var ch in UniqueKey.KeyGenerator.chars)
{
Console.WriteLine($"{ch}: {(100.0 * counts[ch] / totalChars).ToString("#.000")}%");
}
}
}
}
RNGCSP
?) Używanie mod do indeksowania w chars
tablicy oznacza, że otrzymasz stronnicze wyjście, chyba że chars.Length
stanie się dzielnikiem 256.
4*maxSize
losowych bajtów, a następnie użycie (UInt32)(BitConverter.ToInt32(data,4*i)% chars.Length
. Użyłbym również GetBytes
zamiast GetNonZeroBytes
. I w końcu możesz usunąć pierwsze połączenie z GetNonZeroBytes
. Nie wykorzystujesz jego wyniku.
Rozwiązanie 1 - największy „zasięg” o najbardziej elastycznej długości
string get_unique_string(int string_length) {
using(var rng = new RNGCryptoServiceProvider()) {
var bit_count = (string_length * 6);
var byte_count = ((bit_count + 7) / 8); // rounded up
var bytes = new byte[byte_count];
rng.GetBytes(bytes);
return Convert.ToBase64String(bytes);
}
}
To rozwiązanie ma większy zasięg niż używanie GUID, ponieważ GUID ma kilka stałych bitów, które są zawsze takie same i dlatego nie są losowe, na przykład 13 znaków w hex jest zawsze „4” - przynajmniej w GUID wersji 6.
To rozwiązanie pozwala również generować ciąg o dowolnej długości.
Rozwiązanie 2 - Jedna linia kodu - odpowiednia dla maksymalnie 22 znaków
Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Substring(0, 8);
Nie można generować ciągów, dopóki Rozwiązanie 1, a ciąg nie ma tego samego zakresu z powodu ustalonych bitów w identyfikatorach GUID, ale w wielu przypadkach to wystarczy.
Rozwiązanie 3 - Nieco mniej kodu
Guid.NewGuid().ToString("n").Substring(0, 8);
Przeważnie trzymam to tutaj do celów historycznych. Zużywa nieco mniej kodu, co jest kosztem posiadania mniejszego zasięgu - ponieważ używa hexa zamiast base64, potrzeba więcej znaków do przedstawienia tego samego zakresu w porównaniu z innymi rozwiązaniami.
Co oznacza większą szansę na kolizję - przetestowanie go przy 100 000 iteracjach 8 ciągów znaków wygenerowało jeden duplikat.
Oto przykład, który ukradłem z przykładu Sama Allena w Dot Net Perls
Jeśli potrzebujesz tylko 8 znaków, użyj Path.GetRandomFileName () w przestrzeni nazw System.IO. Sam mówi, że użycie „Path.GetRandomFileName tutaj jest czasem lepsze, ponieważ używa RNGCryptoServiceProvider dla lepszej losowości. Jednak jest ograniczone do 11 losowych znaków”.
GetRandomFileName zawsze zwraca ciąg 12 znaków z kropką na 9 znaku. Musisz więc usunąć kropkę (ponieważ nie jest to przypadek), a następnie pobrać 8 znaków z ciągu. Właściwie możesz wziąć pierwsze 8 znaków i nie martwić się o kropkę.
public string Get8CharacterRandomString()
{
string path = Path.GetRandomFileName();
path = path.Replace(".", ""); // Remove period.
return path.Substring(0, 8); // Return 8 character string
}
PS: dzięki Sam
Głównymi celami mojego kodu są:
Pierwszą właściwość uzyskuje się, biorąc 64-bitową wartość modulo wielkości alfabetu. W przypadku małych alfabetów (takich jak 62 znaki z pytania) prowadzi to do nieznacznego błędu. Drugą i trzecią właściwość uzyskuje się za pomocą RNGCryptoServiceProvider
zamiast System.Random
.
using System;
using System.Security.Cryptography;
public static string GetRandomAlphanumericString(int length)
{
const string alphanumericCharacters =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
"abcdefghijklmnopqrstuvwxyz" +
"0123456789";
return GetRandomString(length, alphanumericCharacters);
}
public static string GetRandomString(int length, IEnumerable<char> characterSet)
{
if (length < 0)
throw new ArgumentException("length must not be negative", "length");
if (length > int.MaxValue / 8) // 250 million chars ought to be enough for anybody
throw new ArgumentException("length is too big", "length");
if (characterSet == null)
throw new ArgumentNullException("characterSet");
var characterArray = characterSet.Distinct().ToArray();
if (characterArray.Length == 0)
throw new ArgumentException("characterSet must not be empty", "characterSet");
var bytes = new byte[length * 8];
var result = new char[length];
using (var cryptoProvider = new RNGCryptoServiceProvider())
{
cryptoProvider.GetBytes(bytes);
}
for (int i = 0; i < length; i++)
{
ulong value = BitConverter.ToUInt64(bytes, i * 8);
result[i] = characterArray[value % (uint)characterArray.Length];
}
return new string(result);
}
Najprostszy:
public static string GetRandomAlphaNumeric()
{
return Path.GetRandomFileName().Replace(".", "").Substring(0, 8);
}
Możesz uzyskać lepszą wydajność, jeśli na stałe kodujesz tablicę znaków i polegasz na System.Random
:
public static string GetRandomAlphaNumeric()
{
var chars = "abcdefghijklmnopqrstuvwxyz0123456789";
return new string(chars.Select(c => chars[random.Next(chars.Length)]).Take(8).ToArray());
}
Jeśli kiedykolwiek będziesz się martwić, angielskie alfabety mogą się kiedyś zmienić i możesz stracić biznes, możesz uniknąć twardego kodowania, ale powinno działać nieco gorzej (porównywalne do Path.GetRandomFileName
podejścia)
public static string GetRandomAlphaNumeric()
{
var chars = 'a'.To('z').Concat('0'.To('9')).ToList();
return new string(chars.Select(c => chars[random.Next(chars.Length)]).Take(8).ToArray());
}
public static IEnumerable<char> To(this char start, char end)
{
if (end < start)
throw new ArgumentOutOfRangeException("the end char should not be less than start char", innerException: null);
return Enumerable.Range(start, end - start + 1).Select(i => (char)i);
}
Dwa ostatnie podejścia wyglądają lepiej, jeśli można je na System.Random
przykład ustawić jako metodę rozszerzenia .
chars.Select
jest bardzo brzydkie, ponieważ polega na tym, że rozmiar wyjściowy to co najwyżej rozmiar alfabetu.
'a'.To('z')
podejście?
chars.Select()
Weź (n) `działa tylko, jeśli chars.Count >= n
. Wybór sekwencji, której tak naprawdę nie używasz, jest nieco nieintuicyjny, szczególnie z tym ukrytym ograniczeniem długości. Wolałbym użyć Enumerable.Range
lub Enumerable.Repeat
. 2) Komunikat o błędzie „Końcowy znak powinien być mniejszy niż początkowy znak” jest nieprawidłowy / nie ma go not
.
chars.Count
jest sposób > n
. Nie dostaję też nieintuicyjnej części. To sprawia, że wszystkie zastosowania są Take
nieintuicyjne, prawda? Nie wierzę w to. Dzięki za wskazanie literówki.
Tylko kilka porównań wydajności różnych odpowiedzi w tym wątku:
// what's available
public static string possibleChars = "abcdefghijklmnopqrstuvwxyz";
// optimized (?) what's available
public static char[] possibleCharsArray = possibleChars.ToCharArray();
// optimized (precalculated) count
public static int possibleCharsAvailable = possibleChars.Length;
// shared randomization thingy
public static Random random = new Random();
// http://stackoverflow.com/a/1344242/1037948
public string LinqIsTheNewBlack(int num) {
return new string(
Enumerable.Repeat(possibleCharsArray, num)
.Select(s => s[random.Next(s.Length)])
.ToArray());
}
// http://stackoverflow.com/a/1344258/1037948
public string ForLoop(int num) {
var result = new char[num];
while(num-- > 0) {
result[num] = possibleCharsArray[random.Next(possibleCharsAvailable)];
}
return new string(result);
}
public string ForLoopNonOptimized(int num) {
var result = new char[num];
while(num-- > 0) {
result[num] = possibleChars[random.Next(possibleChars.Length)];
}
return new string(result);
}
public string Repeat(int num) {
return new string(new char[num].Select(o => possibleCharsArray[random.Next(possibleCharsAvailable)]).ToArray());
}
// http://stackoverflow.com/a/1518495/1037948
public string GenerateRandomString(int num) {
var rBytes = new byte[num];
random.NextBytes(rBytes);
var rName = new char[num];
while(num-- > 0)
rName[num] = possibleCharsArray[rBytes[num] % possibleCharsAvailable];
return new string(rName);
}
//SecureFastRandom - or SolidSwiftRandom
static string GenerateRandomString(int Length) //Configurable output string length
{
byte[] rBytes = new byte[Length];
char[] rName = new char[Length];
SolidSwiftRandom.GetNextBytesWithMax(rBytes, biasZone);
for (var i = 0; i < Length; i++)
{
rName[i] = charSet[rBytes[i] % charSet.Length];
}
return new string(rName);
}
Testowane w LinqPad. Dla łańcucha o rozmiarze 10 generuje:
- od Linq = chdgmevhcy [10]
- z pętli = gtnoaryhxr [10]
- z Select = rsndbztyby [10]
- from GenerateRandomString = owyefjjakj [10]
- from SecureFastRandom = VzougLYHYP [10]
- z SecureFastRandom-NoCache = oVQXNGmO1S [10]
I numery wydajności wydają się nieznacznie różnić, bardzo rzadko NonOptimized
jest rzeczywiście szybciej, a czasem ForLoop
i GenerateRandomString
przełączyć kto jest w czołówce.
- LinqIsTheNewBlack (10000x) = upłynęło 96762 tyknięć (9,6762 ms)
- ForLoop (10000x) = upłynęło 28970 tyknięć (2,897 ms)
- ForLoopNonOptimized (10000x) = upłynęło 33336 tyknięć (3,3336 ms)
- Powtórzenie (10000x) = upłynęło 78547 tyknięć (7,8547 ms)
- GenerateRandomString (10000x) = upłynęło 27416 tyknięć (2,7416 ms)
- SecureFastRandom (10000x) = upłynęło 13176 tyknięć (5ms) najniższy [Inna maszyna]
- SecureFastRandom-NoCache (10000x) = 39541 tyknięć upłynęło (17ms) najniższy [Inna maszyna]
var many = 10000; Assert.AreEqual(many, new bool[many].Select(o => EachRandomizingMethod(10)).Distinct().Count());
, gdzie zamieniasz na EachRandomizingMethod
... każdą metodę
Jedna linia kodu Membership.GeneratePassword()
załatwia sprawę :)
Oto demo tego samego.
Kod napisany przez Erica J. jest dość niechlujny (jasne jest, że pochodzi on sprzed 6 lat ... prawdopodobnie nie napisałby dzisiaj tego kodu), a nawet są pewne problemy.
W przeciwieństwie do niektórych przedstawionych alternatyw, ta jest kryptograficznie solidna.
Nieprawdziwe ... Hasło zawiera błąd (jak napisano w komentarzu), bcdefgh
jest nieco bardziej prawdopodobny niż inne ( a
nie dlatego, że przez GetNonZeroBytes
to nie generuje bajtów o wartości zero, więc błąd jest ponieważ a
jest to zrównoważone), więc nie jest tak naprawdę kryptograficznie.
To powinno rozwiązać wszystkie problemy.
public static string GetUniqueKey(int size = 6, string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
{
using (var crypto = new RNGCryptoServiceProvider())
{
var data = new byte[size];
// If chars.Length isn't a power of 2 then there is a bias if
// we simply use the modulus operator. The first characters of
// chars will be more probable than the last ones.
// buffer used if we encounter an unusable random byte. We will
// regenerate it in this buffer
byte[] smallBuffer = null;
// Maximum random number that can be used without introducing a
// bias
int maxRandom = byte.MaxValue - ((byte.MaxValue + 1) % chars.Length);
crypto.GetBytes(data);
var result = new char[size];
for (int i = 0; i < size; i++)
{
byte v = data[i];
while (v > maxRandom)
{
if (smallBuffer == null)
{
smallBuffer = new byte[1];
}
crypto.GetBytes(smallBuffer);
v = smallBuffer[0];
}
result[i] = chars[v % chars.Length];
}
return new string(result);
}
}
Używamy również niestandardowego ciągu losowego, ale zaimplementowaliśmy go jako pomocnika łańcucha, dzięki czemu zapewnia on pewną elastyczność ...
public static string Random(this string chars, int length = 8)
{
var randomString = new StringBuilder();
var random = new Random();
for (int i = 0; i < length; i++)
randomString.Append(chars[random.Next(chars.Length)]);
return randomString.ToString();
}
Stosowanie
var random = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".Random();
lub
var random = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".Random(16);
Mój prosty kod jednoliniowy działa dla mnie :)
string random = string.Join("", Guid.NewGuid().ToString("n").Take(8).Select(o => o));
Response.Write(random.ToUpper());
Response.Write(random.ToLower());
Aby rozwinąć ten ciąg dla dowolnej długości łańcucha
public static string RandomString(int length)
{
//length = length < 0 ? length * -1 : length;
var str = "";
do
{
str += Guid.NewGuid().ToString().Replace("-", "");
}
while (length > str.Length);
return str.Substring(0, length);
}
Inną opcją może być użycie Linqa i agregacja losowych znaków w budującym ciągi znaków.
var chars = "abcdefghijklmnopqrstuvwxyz123456789".ToArray();
string pw = Enumerable.Range(0, passwordLength)
.Aggregate(
new StringBuilder(),
(sb, n) => sb.Append((chars[random.Next(chars.Length)])),
sb => sb.ToString());
Pytanie: Dlaczego mam marnować czas Enumerable.Range
na pisanie zamiast pisania "ABCDEFGHJKLMNOPQRSTUVWXYZ0123456789"
?
using System;
using System.Collections.Generic;
using System.Linq;
public class Test
{
public static void Main()
{
var randomCharacters = GetRandomCharacters(8, true);
Console.WriteLine(new string(randomCharacters.ToArray()));
}
private static List<char> getAvailableRandomCharacters(bool includeLowerCase)
{
var integers = Enumerable.Empty<int>();
integers = integers.Concat(Enumerable.Range('A', 26));
integers = integers.Concat(Enumerable.Range('0', 10));
if ( includeLowerCase )
integers = integers.Concat(Enumerable.Range('a', 26));
return integers.Select(i => (char)i).ToList();
}
public static IEnumerable<char> GetRandomCharacters(int count, bool includeLowerCase)
{
var characters = getAvailableRandomCharacters(includeLowerCase);
var random = new Random();
var result = Enumerable.Range(0, count)
.Select(_ => characters[random.Next(characters.Count)]);
return result;
}
}
Odpowiedź: Magiczne ciągi są ZŁE. Czy ktokolwiek zauważył, że nie ma „I
w mojej strunie na górze ”? Moja matka nauczyła mnie nie używać magicznych sznurków z tego właśnie powodu ...
nb 1: Jak wiele osób mówiło @dtb, nie używaj System.Random
jeśli potrzebujesz bezpieczeństwa kryptograficznego ...
Uwaga 2: Ta odpowiedź nie jest najskuteczniejsza ani najkrótsza, ale chciałem, aby przestrzeń oddzieliła odpowiedź od pytania. Celem mojej odpowiedzi jest bardziej ostrzeżenie przed magicznymi łańcuchami niż dostarczenie wymyślnej, innowacyjnej odpowiedzi.
I
?”
[A-Z0-9]
. Jeśli przypadkowo ciąg losowy obejmuje [A-HJ-Z0-9]
tylko wynik, nie obejmuje on pełnego dopuszczalnego zakresu, co może być problematyczne.
I
. Czy to dlatego, że jest o jedną postać mniej, a to ułatwia złamanie? Jakie są statystyki haseł do zgryzania, które zawierają 35 znaków w zakresie niż 36. Myślę, że wolałbym zaryzykować ... lub po prostu sprawdzić zakres znaków ... niż zawrzeć wszystkie dodatkowe śmieci w moim kodzie. Ale to ja. To znaczy, żeby nie być dziurą w tyłku, mówię tylko. Czasami myślę, że programiści mają tendencję do wybierania bardzo skomplikowanej drogi ze względu na to, że są bardzo skomplikowani.
I
i O
z tego typu losowych ciągów, aby uniknąć pomylenia ich przez ludzi z 1
i 0
. Jeśli nie zależy ci na łańcuchu czytelnym dla człowieka, dobrze, ale jeśli jest to coś, co ktoś może potrzebować wpisać, to naprawdę mądrze jest usunąć te znaki.
Nieco czystsza wersja rozwiązania DTB.
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var random = new Random();
var list = Enumerable.Repeat(0, 8).Select(x=>chars[random.Next(chars.Length)]);
return string.Join("", list);
Twoje preferencje dotyczące stylu mogą się różnić.
Po przejrzeniu innych odpowiedzi i rozważeniu komentarzy CodeInChaos, a także CodeInChaos wciąż stronniczej (choć mniejszej) odpowiedzi, pomyślałem, że konieczne jest ostateczne rozwiązanie cięcia i wklejania . Tak więc, aktualizując moją odpowiedź, postanowiłem wyjść na całość.
Aby uzyskać aktualną wersję tego kodu, odwiedź nowe repozytorium Hg na Bitbucket: https://bitbucket.org/merarischroeder/secureswiftrandom . Zalecam skopiowanie i wklejenie kodu z: https://bitbucket.org/merarischroeder/secureswiftrandom/src/6c14b874f34a3f6576b0213379ecdf0ffc7496ea/Code/Alivate.SolidSwiftRandom/SolidSwiftRandom.cs?atevdefault. (upewnij się, kliknij przycisk Raw, aby ułatwić kopiowanie i upewnić się, że masz najnowszą wersję, myślę, że ten link prowadzi do konkretnej wersji kodu, a nie najnowszej).
Zaktualizowane uwagi:
Zakończ rozwiązanie pytania:
static char[] charSet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".ToCharArray();
static int byteSize = 256; //Labelling convenience
static int biasZone = byteSize - (byteSize % charSet.Length);
public string GenerateRandomString(int Length) //Configurable output string length
{
byte[] rBytes = new byte[Length]; //Do as much before and after lock as possible
char[] rName = new char[Length];
SecureFastRandom.GetNextBytesMax(rBytes, biasZone);
for (var i = 0; i < Length; i++)
{
rName[i] = charSet[rBytes[i] % charSet.Length];
}
return new string(rName);
}
Ale potrzebujesz mojej nowej (nieprzetestowanej) klasy:
/// <summary>
/// My benchmarking showed that for RNGCryptoServiceProvider:
/// 1. There is negligable benefit of sharing RNGCryptoServiceProvider object reference
/// 2. Initial GetBytes takes 2ms, and an initial read of 1MB takes 3ms (starting to rise, but still negligable)
/// 2. Cached is ~1000x faster for single byte at a time - taking 9ms over 1MB vs 989ms for uncached
/// </summary>
class SecureFastRandom
{
static byte[] byteCache = new byte[1000000]; //My benchmark showed that an initial read takes 2ms, and an initial read of this size takes 3ms (starting to raise)
static int lastPosition = 0;
static int remaining = 0;
/// <summary>
/// Static direct uncached access to the RNGCryptoServiceProvider GetBytes function
/// </summary>
/// <param name="buffer"></param>
public static void DirectGetBytes(byte[] buffer)
{
using (var r = new RNGCryptoServiceProvider())
{
r.GetBytes(buffer);
}
}
/// <summary>
/// Main expected method to be called by user. Underlying random data is cached from RNGCryptoServiceProvider for best performance
/// </summary>
/// <param name="buffer"></param>
public static void GetBytes(byte[] buffer)
{
if (buffer.Length > byteCache.Length)
{
DirectGetBytes(buffer);
return;
}
lock (byteCache)
{
if (buffer.Length > remaining)
{
DirectGetBytes(byteCache);
lastPosition = 0;
remaining = byteCache.Length;
}
Buffer.BlockCopy(byteCache, lastPosition, buffer, 0, buffer.Length);
lastPosition += buffer.Length;
remaining -= buffer.Length;
}
}
/// <summary>
/// Return a single byte from the cache of random data.
/// </summary>
/// <returns></returns>
public static byte GetByte()
{
lock (byteCache)
{
return UnsafeGetByte();
}
}
/// <summary>
/// Shared with public GetByte and GetBytesWithMax, and not locked to reduce lock/unlocking in loops. Must be called within lock of byteCache.
/// </summary>
/// <returns></returns>
static byte UnsafeGetByte()
{
if (1 > remaining)
{
DirectGetBytes(byteCache);
lastPosition = 0;
remaining = byteCache.Length;
}
lastPosition++;
remaining--;
return byteCache[lastPosition - 1];
}
/// <summary>
/// Rejects bytes which are equal to or greater than max. This is useful for ensuring there is no bias when you are modulating with a non power of 2 number.
/// </summary>
/// <param name="buffer"></param>
/// <param name="max"></param>
public static void GetBytesWithMax(byte[] buffer, byte max)
{
if (buffer.Length > byteCache.Length / 2) //No point caching for larger sizes
{
DirectGetBytes(buffer);
lock (byteCache)
{
UnsafeCheckBytesMax(buffer, max);
}
}
else
{
lock (byteCache)
{
if (buffer.Length > remaining) //Recache if not enough remaining, discarding remaining - too much work to join two blocks
DirectGetBytes(byteCache);
Buffer.BlockCopy(byteCache, lastPosition, buffer, 0, buffer.Length);
lastPosition += buffer.Length;
remaining -= buffer.Length;
UnsafeCheckBytesMax(buffer, max);
}
}
}
/// <summary>
/// Checks buffer for bytes equal and above max. Must be called within lock of byteCache.
/// </summary>
/// <param name="buffer"></param>
/// <param name="max"></param>
static void UnsafeCheckBytesMax(byte[] buffer, byte max)
{
for (int i = 0; i < buffer.Length; i++)
{
while (buffer[i] >= max)
buffer[i] = UnsafeGetByte(); //Replace all bytes which are equal or above max
}
}
}
W przypadku historii - moje starsze rozwiązanie dla tej odpowiedzi wykorzystałem obiekt Random:
private static char[] charSet =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".ToCharArray();
static rGen = new Random(); //Must share, because the clock seed only has Ticks (~10ms) resolution, yet lock has only 20-50ns delay.
static int byteSize = 256; //Labelling convenience
static int biasZone = byteSize - (byteSize % charSet.Length);
static bool SlightlyMoreSecurityNeeded = true; //Configuration - needs to be true, if more security is desired and if charSet.Length is not divisible by 2^X.
public string GenerateRandomString(int Length) //Configurable output string length
{
byte[] rBytes = new byte[Length]; //Do as much before and after lock as possible
char[] rName = new char[Length];
lock (rGen) //~20-50ns
{
rGen.NextBytes(rBytes);
for (int i = 0; i < Length; i++)
{
while (SlightlyMoreSecurityNeeded && rBytes[i] >= biasZone) //Secure against 1/5 increased bias of index[0-7] values against others. Note: Must exclude where it == biasZone (that is >=), otherwise there's still a bias on index 0.
rBytes[i] = rGen.NextByte();
rName[i] = charSet[rBytes[i] % charSet.Length];
}
}
return new string(rName);
}
Wydajność:
Sprawdź także:
Te linki to inne podejście. Buforowanie można dodać do tej nowej bazy kodu, ale najważniejsze było zbadanie różnych podejść do usuwania stronniczości oraz porównanie prędkości i zalet / wad.
charSet.Length
zamiast 62
. 2) Statyczny Random
bez blokady oznacza, że ten kod nie jest bezpieczny dla wątków. 3) zmniejszenie 0-255 mod 62 wprowadza wykrywalne odchylenie. 4) Nie możesz używać ToString
na tablicy char, która zawsze zwraca "System.Char[]"
. Musisz użyć new String(rName)
zamiast tego.
System.Random
), a następnie ostrożnie unikać stronniczości we własnym kodzie. Przychodzi na myśl wyrażenie „polerowanie gówna”.
Okropne, wiem, ale po prostu nie mogłem się powstrzymać:
namespace ConsoleApplication2
{
using System;
using System.Text.RegularExpressions;
class Program
{
static void Main(string[] args)
{
Random adomRng = new Random();
string rndString = string.Empty;
char c;
for (int i = 0; i < 8; i++)
{
while (!Regex.IsMatch((c=Convert.ToChar(adomRng.Next(48,128))).ToString(), "[A-Za-z0-9]"));
rndString += c;
}
Console.WriteLine(rndString + Environment.NewLine);
}
}
}
Szukałem bardziej szczegółowej odpowiedzi, w której chcę kontrolować format losowego ciągu i natknąłem się na ten post. Na przykład: tablice rejestracyjne (samochodów) mają określony format (w zależności od kraju) i chciałem stworzyć losowe tablice rejestracyjne.
Postanowiłem do tego napisać własną metodę rozszerzenia Random. (ma to na celu ponowne użycie tego samego obiektu losowego, ponieważ w scenariuszach wielowątkowych można mieć podwójne). Utworzyłem listę ( https://gist.github.com/SamVanhoutte/808845ca78b9c041e928 ), ale skopiuję tutaj również klasę rozszerzenia:
void Main()
{
Random rnd = new Random();
rnd.GetString("1-###-000").Dump();
}
public static class RandomExtensions
{
public static string GetString(this Random random, string format)
{
// Based on http://stackoverflow.com/questions/1344221/how-can-i-generate-random-alphanumeric-strings-in-c
// Added logic to specify the format of the random string (# will be random string, 0 will be random numeric, other characters remain)
StringBuilder result = new StringBuilder();
for(int formatIndex = 0; formatIndex < format.Length ; formatIndex++)
{
switch(format.ToUpper()[formatIndex])
{
case '0': result.Append(getRandomNumeric(random)); break;
case '#': result.Append(getRandomCharacter(random)); break;
default : result.Append(format[formatIndex]); break;
}
}
return result.ToString();
}
private static char getRandomCharacter(Random random)
{
string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
return chars[random.Next(chars.Length)];
}
private static char getRandomNumeric(Random random)
{
string nums = "0123456789";
return nums[random.Next(nums.Length)];
}
}
Teraz w smaku jednowarstwowym.
private string RandomName()
{
return new string(
Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 13)
.Select(s =>
{
var cryptoResult = new byte[4];
using (var cryptoProvider = new RNGCryptoServiceProvider())
cryptoProvider.GetBytes(cryptoResult);
return s[new Random(BitConverter.ToInt32(cryptoResult, 0)).Next(s.Length)];
})
.ToArray());
}
RNGCryptoServiceProvider
po użyciu należy je usunąć.
Spróbuj połączyć dwie części: unikalną (sekwencję, licznik lub datę) i losową
public class RandomStringGenerator
{
public static string Gen()
{
return ConvertToBase(DateTime.UtcNow.ToFileTimeUtc()) + GenRandomStrings(5); //keep length fixed at least of one part
}
private static string GenRandomStrings(int strLen)
{
var result = string.Empty;
var Gen = new RNGCryptoServiceProvider();
var data = new byte[1];
while (result.Length < strLen)
{
Gen.GetNonZeroBytes(data);
int code = data[0];
if (code > 48 && code < 57 || // 0-9
code > 65 && code < 90 || // A-Z
code > 97 && code < 122 // a-z
)
{
result += Convert.ToChar(code);
}
}
return result;
}
private static string ConvertToBase(long num, int nbase = 36)
{
var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; //if you wish make algorithm more secure - change order of letter here
// check if we can convert to another base
if (nbase < 2 || nbase > chars.Length)
return null;
int r;
var newNumber = string.Empty;
// in r we have the offset of the char that was converted to the new base
while (num >= nbase)
{
r = (int) (num % nbase);
newNumber = chars[r] + newNumber;
num = num / nbase;
}
// the last number to convert
newNumber = chars[(int)num] + newNumber;
return newNumber;
}
}
Testy:
[Test]
public void Generator_Should_BeUnigue1()
{
//Given
var loop = Enumerable.Range(0, 1000);
//When
var str = loop.Select(x=> RandomStringGenerator.Gen());
//Then
var distinct = str.Distinct();
Assert.AreEqual(loop.Count(),distinct.Count()); // Or Assert.IsTrue(distinct.Count() < 0.95 * loop.Count())
}
<=
i >=
zamiast <
i >
. 3) Dodałbym niepotrzebne nawiasy wokół &&
wyrażeń, aby wyjaśnić, że mają one pierwszeństwo, ale oczywiście jest to tylko wybór stylistyczny.
Rozwiązanie bez użycia Random
:
var chars = Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 8);
var randomStr = new string(chars.SelectMany(str => str)
.OrderBy(c => Guid.NewGuid())
.Take(8).ToArray());
Oto wariant rozwiązania Erica J, tj. Dźwięk kryptograficzny, dla WinRT (aplikacja ze Sklepu Windows):
public static string GenerateRandomString(int length)
{
var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
var result = new StringBuilder(length);
for (int i = 0; i < length; ++i)
{
result.Append(CryptographicBuffer.GenerateRandomNumber() % chars.Length);
}
return result.ToString();
}
Jeśli wydajność ma znaczenie (szczególnie gdy długość jest wysoka):
public static string GenerateRandomString(int length)
{
var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
var result = new System.Text.StringBuilder(length);
var bytes = CryptographicBuffer.GenerateRandom((uint)length * 4).ToArray();
for (int i = 0; i < bytes.Length; i += 4)
{
result.Append(BitConverter.ToUInt32(bytes, i) % chars.Length);
}
return result.ToString();
}
Wiem, że to nie jest najlepszy sposób. Ale możesz spróbować.
string str = Path.GetRandomFileName(); //This method returns a random file name of 11 characters
str = str.Replace(".","");
Console.WriteLine("Random string: " + str);
Nie wiem, jak kryptograficznie to brzmi, ale jest on bardziej czytelny i zwięzły niż bardziej skomplikowane rozwiązania (imo) i powinien być bardziej „losowy” niż System.Random
oparty na rozwiązaniach.
return alphabet
.OrderBy(c => Guid.NewGuid())
.Take(strLength)
.Aggregate(
new StringBuilder(),
(builder, c) => builder.Append(c))
.ToString();
Nie mogę zdecydować, czy uważam, że ta wersja czy następna jest „ładniejsza”, ale dają dokładnie takie same wyniki:
return new string(alphabet
.OrderBy(o => Guid.NewGuid())
.Take(strLength)
.ToArray());
To prawda, że nie jest zoptymalizowany pod kątem szybkości, więc jeśli misja ma generować miliony losowych ciągów co sekundę, spróbuj innego!
UWAGA: To rozwiązanie nie pozwala na powtarzanie symboli w alfabecie, a alfabet MUSI mieć taki sam lub większy rozmiar niż ciąg wyjściowy, co sprawia, że takie podejście jest mniej pożądane w niektórych okolicznościach, wszystko zależy od przypadku użycia.
public static class StringHelper
{
private static readonly Random random = new Random();
private const int randomSymbolsDefaultCount = 8;
private const string availableChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
private static int randomSymbolsIndex = 0;
public static string GetRandomSymbols()
{
return GetRandomSymbols(randomSymbolsDefaultCount);
}
public static string GetRandomSymbols(int count)
{
var index = randomSymbolsIndex;
var result = new string(
Enumerable.Repeat(availableChars, count)
.Select(s => {
index += random.Next(s.Length);
if (index >= s.Length)
index -= s.Length;
return s[index];
})
.ToArray());
randomSymbolsIndex = index;
return result;
}
}
random.Next
bezpośrednio z wyniku ? Komplikuje kod i nie osiąga niczego użytecznego.
Oto mechanizm generowania losowego ciągu alfanumerycznego (używam go do generowania haseł i danych testowych) bez definiowania alfabetu i liczb,
CleanupBase64 usunie niezbędne części ciągu i będzie rekurencyjnie dodawał losowe litery alfanumeryczne.
public static string GenerateRandomString(int length)
{
var numArray = new byte[length];
new RNGCryptoServiceProvider().GetBytes(numArray);
return CleanUpBase64String(Convert.ToBase64String(numArray), length);
}
private static string CleanUpBase64String(string input, int maxLength)
{
input = input.Replace("-", "");
input = input.Replace("=", "");
input = input.Replace("/", "");
input = input.Replace("+", "");
input = input.Replace(" ", "");
while (input.Length < maxLength)
input = input + GenerateRandomString(maxLength);
return input.Length <= maxLength ?
input.ToUpper() : //In my case I want capital letters
input.ToUpper().Substring(0, maxLength);
}
GenerateRandomString
i wykonałeś połączenie GetRandomString
z wewnątrz SanitiseBase64String
. Również zostały uznane SanitiseBase64String
i wywołanie CleanUpBase64String
w GenerateRandomString
.
nie jestem w 100% pewien, ponieważ nie testowałem KAŻDEJ opcji tutaj, ale spośród tych, które testowałem, ta jest najszybsza. odmierzył czas stoperem i pokazał 9-10 tyknięć, więc jeśli prędkość jest ważniejsza niż bezpieczeństwo, spróbuj tego:
private static Random random = new Random();
public static string Random(int length)
{
var stringChars = new char[length];
for (int i = 0; i < length; i++)
{
stringChars[i] = (char)random.Next(0x30, 0x7a);
return new string(stringChars);
}
}