Generazione unica di stringhe casuali


97

Vorrei generare stringhe univoche casuali come quelle generate dalla libreria MSDN. ( Error Object ), per esempio. Dovrebbe essere generata una stringa come "t9zk6eay".


1
prova questo di string randoms = Guid.NewGuid().ToString().Replace("-", string.Empty).Replace("+", string.Empty).Substring(0, 4);più può essere trovato qui
shaijut

1
Perché qualcosa sia totalmente unico deve essere basato su qualcosa di non casuale, come il tempo, il luogo, ecc. E quindi non può mai essere completamente casuale. Un Guid può sembrare casuale ma in realtà non lo è. IMO la tua unica speranza è di renderlo così casuale e complesso che per tutti gli scopi pratici i valori saranno unici (cioè hanno una probabilità di collisione estremamente bassa).
bytedev

Risposte:


84

Usare Guid sarebbe un modo abbastanza buono, ma per ottenere qualcosa di simile al tuo esempio, probabilmente vorrai convertirlo in una stringa Base64:

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

Mi sbarazzo di "=" e "+" per avvicinarmi un po 'al tuo esempio, altrimenti ottieni "==" alla fine della stringa e un "+" nel mezzo. Ecco una stringa di output di esempio:

"OZVV5TpP4U6wJthaCORZEQ"


15
Dovresti considerare di sostituire anche /.
Jason Kealey

20
Un Guid non dovrebbe essere considerato come una stringa casuale sicura poiché la sequenza può essere indovinata. Un Guid è progettato per evitare conflitti chiave, piuttosto che essere casuale. Ci sono alcune buone discussioni sulla casualità di un Guid sullo stack overflow.
Daniel Bradley

Per una spiegazione chiara e breve di cosa Convert.ToBase64Stringsi tratta, dai un'occhiata qui .
jwaliszko

2
Può convertire GUID in base64 e sostituire + e = aumentare la probabilità di collisione?
Milan Aggarwal

5
@ SimonEjsing Ti invito a una birra se puoi effettivamente scrivere un'applicazione che ottiene collisioni quando viene utilizzata new Guid()senza "hacking" (manomissione dell'orologio o delle strutture dati interne di Windows). Sentiti libero di usare tutti i core, thread, primitive di sincronizzazione ecc. Che desideri.
Lucero

175

Aggiornamento 2016/1/23

Se trovi utile questa risposta, potresti essere interessato a una semplice libreria di generazione di password (~ 500 SLOC) che ho pubblicato :

Install-Package MlkPwgen

Quindi puoi generare stringhe casuali proprio come nella risposta qui sotto:

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

Un vantaggio della libreria è che il codice è meglio scomposto in modo da poter utilizzare la casualità sicura per qualcosa di più della generazione di stringhe . Controlla il sito del progetto per maggiori dettagli.

Risposta originale

Poiché nessuno ha ancora fornito un codice sicuro, inserisco quanto segue nel caso qualcuno lo trovi utile.

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();
    }
}

Grazie ad Ahmad per aver sottolineato come far funzionare il codice su .NET Core.


La soluzione @Keltex non funzionava bene per meh (restituiva la stessa stringa dopo pochi utilizzi). Questa soluzione funziona perfettamente :)
JoanComasFdz

2
@LeeGrissom, il biasing è un aspetto importante. Diciamo ad esempio che il tuo alfabeto contiene 255 caratteri e ottieni un valore casuale compreso tra 0 e 255. In un buffer circolare sia il valore 0 che 255 corrisponderebbero allo stesso carattere che distorcerebbe il risultato a favore del primo carattere dell'alfabeto, sarebbe meno casuale. se questo conta dipende dall'applicazione ovviamente.
Oskar Sjöberg

4
A chi è rivolto .netcore: sostituire var rng = new RNGCryptoServiceProvider()convar rng = RandomNumberGenerator.Create()
amd

1
Perché calcoli "var outOfRangeStart = byteSize - (byteSize% allowedCharSet.Length);" per ogni iterazione? Puoi calcolarlo prima di "usare".
mtkachenko

1
@BartCalixto fisso. Grazie!
Michael Kropat

38

Vorrei avvertire che i GUID non sono numeri casuali . Non dovrebbero essere usati come base per generare qualcosa che ti aspetti essere totalmente casuale (vedi http://en.wikipedia.org/wiki/Globally_Unique_Identifier ):

La crittoanalisi del generatore di GUID WinAPI mostra che, poiché la sequenza dei GUID V4 è pseudo-casuale, dato lo stato iniziale si possono prevedere fino ai successivi 250.000 GUID restituiti dalla funzione UuidCreate. Questo è il motivo per cui i GUID non dovrebbero essere usati nella crittografia, ad esempio come chiavi casuali.

Usa invece il metodo C # Random. Qualcosa di simile ( codice trovato qui ):

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();
}

I GUID vanno bene se vuoi qualcosa di unico (come un nome di file o una chiave univoci in un database), ma non sono adatti a qualcosa che vuoi che sia casuale (come una password o una chiave di crittografia). Quindi dipende dalla tua applicazione.

Modifica . Microsoft afferma che nemmeno Random è eccezionale ( http://msdn.microsoft.com/en-us/library/system.random(VS.71).aspx ):

Per generare un numero casuale crittograficamente sicuro adatto per la creazione di una password casuale, ad esempio, utilizzare una classe derivata da System.Security.Cryptography.RandomNumberGenerator come System.Security.Cryptography.RNGCryptoServiceProvider.


5
Anche la classe random C # non è "random" e non è adatta a nessun codice crittografico, poiché è un classico generatore casuale a partire da uno specifico numero seed. Lo stesso seme restituirà anche la stessa sequenza di numeri restituiti; l'approccio GUID è già molto meglio qui (non "casuale" ma "unico").
Lucero

3
@ Lucero: hai ragione. Microsoft consiglia: "Per generare un numero casuale crittograficamente sicuro adatto per la creazione di una password casuale, ad esempio, utilizzare una classe derivata da System.Security.Cryptography.RandomNumberGenerator come System.Security.Cryptography.RNGCryptoServiceProvider."
Keltex

Bene, la domanda ha già affermato che vuole stringhe univoche (pseudo-) casuali, quindi nessun requisito crittografico o anche la necessità di seguire una specifica distribuzione casuale. Quindi GUID è probabilmente l'approccio più semplice.
Joey

1
L'affermazione che "dato lo stato iniziale si possono prevedere fino ai successivi 250.000 GUID" sembra un'affermazione intrinsecamente vera per qualsiasi PRNG ... Sono sicuro che non sia sicuro, ma non sono sicuro che ci sia molto valore nel generare URL veramente casuali, se questo è ciò che l'OP sta cercando. ;)
ojrac

1
(+1 comunque - l'istruzione PRNG è importante.)
ojrac

13

Ho semplificato la soluzione @Michael Kropats e ho realizzato una versione in stile LINQ.

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();
    }   
}

11

Non penso che siano davvero casuali, ma la mia ipotesi è che siano degli hash.

Ogni volta che ho bisogno di un identificatore casuale, di solito uso un GUID e lo converto nella sua rappresentazione "nuda":

Guid.NewGuid().ToString("n");

Come ha sottolineato @Keltex: Cryptanalysis of the WinAPI GUID generator mostra che, poiché la sequenza dei GUID V4 è pseudo-casuale, dato lo stato iniziale si possono prevedere fino ai successivi 250.000 GUID restituiti dalla funzione UuidCreate.
JoanComasFdz

4

Prova la combinazione tra Guid e Time.Ticks

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

3

Sono sorpreso del motivo per cui non è disponibile una soluzione CrytpoGraphic. GUID è unico ma non crittograficamente sicuro . Vedi questo 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);

Nel caso in cui desideri anteporre una guida:

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

Una stringa alfanumerica più pulita:

result = Regex.Replace(result,"[^A-Za-z0-9]","");
Console.WriteLine(result);

1

Soluzione di Michael Kropats in 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

1

Questo funziona perfettamente per me

    private string GeneratePasswordResetToken()
    {
        string token = Guid.NewGuid().ToString();
        var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(token);
        return Convert.ToBase64String(plainTextBytes);
    }

0

Questo è stato richiesto per varie lingue. Ecco una domanda sulle password che dovrebbero essere applicabili anche qui.

Se desideri utilizzare le stringhe per l'accorciamento dell'URL, avrai bisogno anche di un dizionario <> o di un controllo del database per vedere se un ID generato è già stato utilizzato.


0

Se vuoi stringhe alfanumeriche con caratteri minuscoli e maiuscoli ([a-zA-Z0-9]), puoi usare Convert.ToBase64String () per una soluzione rapida e semplice.

Per quanto riguarda l'unicità, controlla il problema del compleanno per calcolare la probabilità che una collisione sia data (A) la lunghezza delle stringhe generate e (B) il numero di stringhe generate.

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)

-1
  • non sono sicuro che i collegamenti di Microsoft siano generati casualmente
  • dare un'occhiata a new Guid (). ToString ()

4
Vuoi dire Guid.NewGuid (). ToString () - Guid non ha un costruttore pubblico
cjk

3
Probabilmente hai ragione, stavi digitando senza verificare. Sono sicuro che il poster originale ha il punto.
Fabian Vilers

-1

Ottieni chiave univoca utilizzando il codice hash 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);
}

Funziona perfettamente ma le parole casuali non contengono caratteri unici. I caratteri si ripetono, come 114e3 (due 1), eaaea (tre a e due e), 60207 (due 0) e così via. Come generare una stringa casuale senza ripetizione di caratteri con combinazione alfanumerica?
vijay

@vijay: poiché emette cifre esadecimali, ti limiti a 16 caratteri e 16! possibili uscite. Le stringhe casuali sono proprio questo, casuali. In teoria potresti ottenere una stringa di tutte le a (aaaaaaaaaaaaaaa). È molto improbabile, ma non più di qualsiasi altra stringa casuale. Non sono sicuro del motivo per cui avresti bisogno di quel vincolo, ma mentre aggiungi caratteri alla stringa, inseriscili in un HashSet <T>, controlla la loro esistenza e aggiungili alla stringa o saltali di conseguenza.
Chris Doggett
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.