Unikalne losowe generowanie ciągów

98

Chciałbym generować losowe, unikalne ciągi, takie jak te generowane przez bibliotekę MSDN ( na przykład obiekt błędu ). Powinien zostać wygenerowany ciąg typu „t9zk6eay”.

Kirtan
źródło
1
spróbuj tego string randoms = Guid.NewGuid().ToString().Replace("-", string.Empty).Replace("+", string.Empty).Substring(0, 4);więcej można znaleźć tutaj
shaijut
1
Aby coś było całkowicie wyjątkowe, musi opierać się na czymś nieprzypadkowym, takim jak czas, lokalizacja itp., A zatem nigdy nie może być w pełni losowe. Guid może wydawać się przypadkowy, ale w rzeczywistości tak nie jest. IMO Twoją jedyną nadzieją jest uczynienie go tak przypadkowym i złożonym, że ze wszystkich praktycznych powodów wartości będą niepowtarzalne (tj. Będą miały niezwykle niskie prawdopodobieństwo kolizji).
bytedev

Odpowiedzi:

85

Używanie Guid byłoby całkiem dobrym sposobem, ale aby uzyskać coś podobnego do twojego przykładu, prawdopodobnie chcesz przekonwertować go na ciąg Base64:

    Guid g = Guid.NewGuid();
    string GuidString = Convert.ToBase64String(g.ToByteArray());
    GuidString = GuidString.Replace("=","");
    GuidString = GuidString.Replace("+","");

Pozbywam się „=” i „+”, aby zbliżyć się trochę do twojego przykładu, w przeciwnym razie otrzymasz „==” na końcu ciągu i „+” w środku. Oto przykładowy ciąg wyjściowy:

„OZVV5TpP4U6wJthaCORZEQ”

Mark Synowiec
źródło
15
Powinieneś rozważyć wymianę / też.
Jason Kealey
20
Guid nie powinien być traktowany jako bezpieczny ciąg losowy, ponieważ można odgadnąć sekwencję. Guid jest przeznaczony do unikania kluczowych konfliktów, a nie do przypadkowości. Istnieje kilka dobrych dyskusji na temat losowości Guida w przypadku przepełnienia stosu.
Daniel Bradley,
Aby uzyskać jasne i krótkie wyjaśnienie, o co Convert.ToBase64Stringchodzi, zajrzyj tutaj .
jwaliszko
2
Czy można przekonwertować guid na base64 i zastąpić + i = zwiększając prawdopodobieństwo kolizji?
Milan Aggarwal,
5
@SimonEjsing Zapraszam na piwo, jeśli rzeczywiście potrafisz napisać aplikację, która powoduje kolizje podczas używania new Guid()bez „hakowania” (manipulowania zegarem lub wewnętrznymi strukturami danych systemu Windows). Możesz swobodnie używać dowolnej liczby rdzeni, wątków, elementów podstawowych synchronizacji itp.
Lucero
175

Aktualizacja 2016/1/23

Jeśli uznasz tę odpowiedź za przydatną, możesz być zainteresowany prostą (~ 500 SLOC) biblioteką generowania haseł, którą opublikowałem :

Install-Package MlkPwgen

Następnie możesz wygenerować losowe ciągi, tak jak w odpowiedzi poniżej:

var str = PasswordGenerator.Generate(length: 10, allowed: Sets.Alphanumerics);

Jedną z zalet biblioteki jest to, że kod jest lepiej rozłożony na czynniki, dzięki czemu można używać bezpiecznej losowości nie tylko do generowania ciągów . Sprawdź na stronie projektu o więcej szczegółów.

Oryginalna odpowiedź

Ponieważ nikt jeszcze nie podał bezpiecznego kodu, zamieszczam następujący kod na wypadek, gdyby ktoś uznał go za przydatny.

string RandomString(int length, string allowedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") {
    if (length < 0) throw new ArgumentOutOfRangeException("length", "length cannot be less than zero.");
    if (string.IsNullOrEmpty(allowedChars)) throw new ArgumentException("allowedChars may not be empty.");

    const int byteSize = 0x100;
    var allowedCharSet = new HashSet<char>(allowedChars).ToArray();
    if (byteSize < allowedCharSet.Length) throw new ArgumentException(String.Format("allowedChars may contain no more than {0} characters.", byteSize));

    // Guid.NewGuid and System.Random are not particularly random. By using a
    // cryptographically-secure random number generator, the caller is always
    // protected, regardless of use.
    using (var rng = System.Security.Cryptography.RandomNumberGenerator.Create()) {
        var result = new StringBuilder();
        var buf = new byte[128];
        while (result.Length < length) {
            rng.GetBytes(buf);
            for (var i = 0; i < buf.Length && result.Length < length; ++i) {
                // Divide the byte into allowedCharSet-sized groups. If the
                // random value falls into the last group and the last group is
                // too small to choose from the entire allowedCharSet, ignore
                // the value in order to avoid biasing the result.
                var outOfRangeStart = byteSize - (byteSize % allowedCharSet.Length);
                if (outOfRangeStart <= buf[i]) continue;
                result.Append(allowedCharSet[buf[i] % allowedCharSet.Length]);
            }
        }
        return result.ToString();
    }
}

Dziękuję Ahmadowi za wskazanie, jak uzyskać kod działający na .NET Core.

Michael Kropat
źródło
Rozwiązanie @Keltex nie działało dobrze dla mnie (zwracało ten sam ciąg po kilku użyciach). To rozwiązanie działa idealnie :)
JoanComasFdz
2
@LeeGrissom, uprzedzenie jest ważnym aspektem. Powiedzmy na przykład, że twój alfabet zawiera 255 znaków i otrzymujesz losową wartość z przedziału 0-255. W buforze pierścieniowym zarówno wartość 0, jak i 255 odpowiadałyby temu samemu znakowi, który wypaczyłby wynik na korzyść pierwszego znaku alfabetu, byłby mniej losowy. czy ma to znaczenie, zależy oczywiście od zastosowania.
Oskar Sjöberg
4
Kto kierowania .netcore: Wymień var rng = new RNGCryptoServiceProvider()zvar rng = RandomNumberGenerator.Create()
amd
1
Dlaczego obliczasz „var outOfRangeStart = byteSize - (byteSize% allowedCharSet.Length);” dla każdej iteracji? Możesz to obliczyć przed „użyciem”.
mtkachenko
1
@BartCalixto Fixed. Dzięki!
Michael Kropat,
38

Ostrzegam, że identyfikatory GUID nieliczbami losowymi . Nie powinny być używane jako podstawa do generowania niczego, co według Ciebie będzie całkowicie losowe (patrz http://en.wikipedia.org/wiki/Globally_Unique_Identifier ):

Kryptanaliza generatora GUID WinAPI pokazuje, że skoro sekwencja identyfikatorów GUID V4 jest pseudolosowa, to biorąc pod uwagę stan początkowy, można przewidzieć do 250 000 identyfikatorów GUID zwróconych przez funkcję UuidCreate. Z tego powodu identyfikatory GUID nie powinny być używane w kryptografii, np. Jako klucze losowe.

Zamiast tego po prostu użyj metody C # Random. Coś takiego ( kod znaleziony tutaj ):

private string RandomString(int size)
{
  StringBuilder builder = new StringBuilder();
  Random random = new Random();
  char ch ;
  for(int i=0; i<size; i++)
  {
    ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65))) ;
    builder.Append(ch);
  }
  return builder.ToString();
}

Identyfikatory GUID są dobre, jeśli chcesz czegoś unikalnego (na przykład unikatowej nazwy pliku lub klucza w bazie danych), ale nie nadają się do czegoś, co chcesz, aby były losowe (jak hasło lub klucz szyfrowania). Więc to zależy od twojej aplikacji.

Edytuj . Microsoft twierdzi, że Random też nie jest taki wspaniały ( http://msdn.microsoft.com/en-us/library/system.random(VS.71).aspx ):

Aby wygenerować bezpieczną kryptograficznie liczbę losową odpowiednią do tworzenia losowego hasła, na przykład użyj klasy pochodnej System.Security.Cryptography.RandomNumberGenerator, takiej jak System.Security.Cryptography.RNGCryptoServiceProvider.

Keltex
źródło
5
Klasa losowa C # również nie jest „losowa” i nie nadaje się do żadnego kodu kryptograficznego, ponieważ jest klasycznym generatorem losowym rozpoczynającym się od określonej liczby początkowej. To samo ziarno zwróci również tę samą sekwencję zwróconych liczb; podejście GUID jest już tutaj znacznie lepsze (nie „losowe”, ale „unikalne”).
Lucero
3
@Lucero: Masz rację. Firma Microsoft zaleca: „Aby wygenerować losową liczbę zabezpieczoną kryptograficznie, odpowiednią do tworzenia losowego hasła, na przykład użyj klasy pochodnej System.Security.Cryptography.RandomNumberGenerator, takiej jak System.Security.Cryptography.RNGCryptoServiceProvider.”
Keltex
Cóż, pytanie już mówiło, że chce (pseudo) losowych unikalnych ciągów, więc nie ma wymagań kryptograficznych ani nawet potrzeby przestrzegania określonej losowej dystrybucji. Tak więc GUID jest prawdopodobnie najłatwiejszym podejściem.
Joey
1
Stwierdzenie, że „biorąc pod uwagę stan początkowy, można przewidzieć do 250 000 kolejnych identyfikatorów GUID”, wydaje się być z natury prawdziwym stwierdzeniem dla każdego PRNG ... Jestem pewien, że również nie jest bezpieczne, ale nie jestem pewien, czy generowanie ma dużą wartość prawdziwie losowe adresy URL, jeśli o to chodzi w OP. ;)
ojrac 08.04.2009
1
(W każdym razie +1 - edukacja PRNG jest ważna.)
ojrac 08.04.2009
13

Uprościłem rozwiązanie @Michael Kropats i stworzyłem wersję LINQ-esque.

string RandomString(int length, string alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
{       
    var outOfRange = byte.MaxValue + 1 - (byte.MaxValue + 1) % alphabet.Length;

    return string.Concat(
        Enumerable
            .Repeat(0, int.MaxValue)
            .Select(e => RandomByte())
            .Where(randomByte => randomByte < outOfRange)
            .Take(length)
            .Select(randomByte => alphabet[randomByte % alphabet.Length])
    );
}

byte RandomByte()
{
    using (var randomizationProvider = new RNGCryptoServiceProvider())
    {
        var randomBytes = new byte[1];
        randomizationProvider.GetBytes(randomBytes);
        return randomBytes.Single();
    }   
}
Oskar Sjöberg
źródło
11

Nie sądzę, że są one naprawdę przypadkowe, ale przypuszczam, że to kilka skrótów.

Zawsze, gdy potrzebuję jakiegoś losowego identyfikatora, zwykle używam identyfikatora GUID i konwertuję go na jego „nagą” reprezentację:

Guid.NewGuid().ToString("n");
Lucero
źródło
Jak zauważył @Keltex: Kryptanaliza generatora GUID WinAPI pokazuje, że skoro sekwencja identyfikatorów GUID V4 jest pseudolosowa, to biorąc pod uwagę stan początkowy, można przewidzieć do 250 000 identyfikatorów GUID zwróconych przez funkcję UuidCreate.
JoanComasFdz
4

Wypróbuj kombinację Guid i Time.Ticks

 var randomNumber = Convert.ToBase64String(Guid.NewGuid().ToByteArray()) + DateTime.Now.Ticks;
     randomNumber = System.Text.RegularExpressions.Regex.Replace(randomNumber, "[^0-9a-zA-Z]+", "");
DevC
źródło
3

Dziwię się, dlaczego nie ma rozwiązania CrytpoGraphic. Identyfikator GUID jest unikalny, ale nie jest bezpieczny kryptograficznie . Zobacz to Dotnet Fiddle.

var bytes = new byte[40]; // byte size
using (var crypto = new RNGCryptoServiceProvider())
  crypto.GetBytes(bytes);

var base64 = Convert.ToBase64String(bytes);
Console.WriteLine(base64);

W przypadku, gdy chcesz dołączyć na początku Guid:

var result = Guid.NewGuid().ToString("N") + base64;
Console.WriteLine(result);

Czystszy ciąg alfanumeryczny:

result = Regex.Replace(result,"[^A-Za-z0-9]","");
Console.WriteLine(result);
tika
źródło
1

Rozwiązanie Michaela Kropatsa w VB.net

Private Function RandomString(ByVal length As Integer, Optional ByVal allowedChars As String = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") As String
    If length < 0 Then Throw New ArgumentOutOfRangeException("length", "length cannot be less than zero.")
    If String.IsNullOrEmpty(allowedChars) Then Throw New ArgumentException("allowedChars may not be empty.")


    Dim byteSize As Integer = 256
    Dim hash As HashSet(Of Char) = New HashSet(Of Char)(allowedChars)
    'Dim hash As HashSet(Of String) = New HashSet(Of String)(allowedChars)
    Dim allowedCharSet() = hash.ToArray

    If byteSize < allowedCharSet.Length Then Throw New ArgumentException(String.Format("allowedChars may contain no more than {0} characters.", byteSize))


    ' Guid.NewGuid and System.Random are not particularly random. By using a
    ' cryptographically-secure random number generator, the caller is always
    ' protected, regardless of use.
    Dim rng = New System.Security.Cryptography.RNGCryptoServiceProvider()
    Dim result = New System.Text.StringBuilder()
    Dim buf = New Byte(128) {}
    While result.Length < length
        rng.GetBytes(buf)
        Dim i
        For i = 0 To buf.Length - 1 Step +1
            If result.Length >= length Then Exit For
            ' Divide the byte into allowedCharSet-sized groups. If the
            ' random value falls into the last group and the last group is
            ' too small to choose from the entire allowedCharSet, ignore
            ' the value in order to avoid biasing the result.
            Dim outOfRangeStart = byteSize - (byteSize Mod allowedCharSet.Length)
            If outOfRangeStart <= buf(i) Then
                Continue For
            End If
            result.Append(allowedCharSet(buf(i) Mod allowedCharSet.Length))
        Next
    End While
    Return result.ToString()
End Function
jhersey29
źródło
1

To działa idealnie dla mnie

    private string GeneratePasswordResetToken()
    {
        string token = Guid.NewGuid().ToString();
        var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(token);
        return Convert.ToBase64String(plainTextBytes);
    }
MarlinG
źródło
0

O to pytano w różnych językach. Oto jedno pytanie dotyczące haseł, które również powinny mieć zastosowanie.

Jeśli chcesz użyć ciągów do skracania adresów URL, będziesz również potrzebować Dictionary <> lub sprawdzenia bazy danych, aby zobaczyć, czy wygenerowany identyfikator nie został już użyty.

Pontus Gagge
źródło
0

Jeśli potrzebujesz ciągów alfanumerycznych z małymi i dużymi literami ([a-zA-Z0-9]), możesz użyć Convert.ToBase64String (), aby uzyskać szybkie i proste rozwiązanie.

Jeśli chodzi o unikalność, sprawdź problem z datą urodzenia, aby obliczyć prawdopodobieństwo kolizji (A) długość wygenerowanych ciągów i (B) liczba wygenerowanych ciągów.

Random random = new Random();

int outputLength = 10;
int byteLength = (int)Math.Ceiling(3f / 4f * outputLength); // Base64 uses 4 characters for every 3 bytes of data; so in random bytes we need only 3/4 of the desired length
byte[] randomBytes = new byte[byteLength];
string output;
do
{
    random.NextBytes(randomBytes); // Fill bytes with random data
    output = Convert.ToBase64String(randomBytes); // Convert to base64
    output = output.Substring(0, outputLength); // Truncate any superfluous characters and/or padding
} while (output.Contains('/') || output.Contains('+')); // Repeat if we contain non-alphanumeric characters (~25% chance if length=10; ~50% chance if length=20; ~35% chance if length=32)
Timo
źródło
-1
  • nie jestem pewien, czy łącze Microsoft jest generowane losowo
  • spójrz na nową Guid (). ToString ()
Fabian Vilers
źródło
4
Masz na myśli Guid.NewGuid (). ToString () - Guid nie ma publicznego konstruktora
cjk
3
Prawdopodobnie masz rację, pisałeś bez weryfikacji. Jestem pewien, że oryginalny plakat ma rację.
Fabian Vilers,
-1

Uzyskaj unikalny klucz za pomocą kodu skrótu GUID

public static string GetUniqueKey(int length)
{
    string guidResult = string.Empty;

    while (guidResult.Length < length)
    {
        // Get the GUID.
        guidResult += Guid.NewGuid().ToString().GetHashCode().ToString("x");
    }

    // Make sure length is valid.
    if (length <= 0 || length > guidResult.Length)
        throw new ArgumentException("Length must be between 1 and " + guidResult.Length);

    // Return the first length bytes.
    return guidResult.Substring(0, length);
}
Chris Doggett
źródło
Działa to doskonale, ale przypadkowe słowa nie zawierają unikalnych znaków. Znaki się powtarzają, np. 114e3 (dwa jedynki), eaea (trzy a i dwa e), 60207 (dwa 0) i tak dalej. Jak wygenerować ciąg losowy bez powtórzeń znaków z kombinacją alfanumeryczną?
vijay
@vijay: Ponieważ wyświetla cyfry szesnastkowe, ograniczasz się do 16 znaków i 16! możliwe wyjścia. Losowe ciągi są po prostu losowe. Teoretycznie możesz otrzymać ciąg wszystkich a (aaaaaaaaaaaaaaa). To bardzo nieprawdopodobne, ale nie bardziej niż jakikolwiek inny losowy ciąg. Nie jestem pewien, dlaczego potrzebujesz tego ograniczenia, ale dodając znaki do ciągu, umieść je w HashSet <T>, sprawdź ich istnienie i dodaj je do ciągu lub odpowiednio je pomiń.
Chris Doggett