Generación única de cadenas aleatorias

97

Me gustaría generar cadenas únicas aleatorias como las que genera la biblioteca MSDN. ( Objeto de error ), por ejemplo. Se debe generar una cadena como 't9zk6eay'.

Kirtan
fuente
1
pruebe esto string randoms = Guid.NewGuid().ToString().Replace("-", string.Empty).Replace("+", string.Empty).Substring(0, 4);más se puede encontrar aquí
shaijut
1
Para que algo sea totalmente único, debe basarse en algo que no sea aleatorio, como el tiempo, la ubicación, etc. y, por lo tanto, nunca puede ser completamente aleatorio. Un Guid puede parecer aleatorio, pero en realidad no lo es. En mi opinión, su única esperanza es hacerlo tan aleatorio y complejo que, para todos los propósitos prácticos, los valores serán únicos (es decir, que tengan una probabilidad de colisión extremadamente baja).
bytedev

Respuestas:

84

Usar Guid sería una manera bastante buena, pero para obtener algo que se parezca a su ejemplo, probablemente desee convertirlo en una cadena Base64:

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

Me deshago de "=" y "+" para acercarme un poco más a tu ejemplo; de lo contrario, obtienes "==" al final de tu cadena y un "+" en el medio. Aquí hay una cadena de salida de ejemplo:

"OZVV5TpP4U6wJthaCORZEQ"

Mark Synowiec
fuente
15
Debería considerar reemplazar / también.
Jason Kealey
20
Un Guid no debe considerarse como una cadena aleatoria segura ya que la secuencia se puede adivinar. Un Guid está diseñado para evitar conflictos clave, en lugar de ser aleatorio. Hay algunas buenas discusiones sobre la aleatoriedad de un Guid en el desbordamiento de pila.
Daniel Bradley
Para una explicación clara y breve de qué Convert.ToBase64Stringse trata, eche un vistazo aquí .
jwaliszko
2
¿Puede convertir guid en base64 y reemplazar + y = aumentar la probabilidad de colisión?
Milan Aggarwal
5
@SimonEjsing Te invitaré a una cerveza si realmente puedes escribir una aplicación que tenga colisiones cuando se usa new Guid()sin "hackear" (alterar el reloj o las estructuras de datos internas de Windows). No dude en utilizar tantos núcleos, subprocesos, primitivas de sincronización, etc., como desee.
Lucero
175

Actualización 2016/1/23

Si encuentra útil esta respuesta, es posible que le interese una biblioteca de generación de contraseñas simple (~ 500 SLOC) que publiqué :

Install-Package MlkPwgen

Luego puede generar cadenas aleatorias como en la respuesta a continuación:

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

Una ventaja de la biblioteca es que el código está mejor factorizado para que pueda usar la aleatoriedad segura para más que generar cadenas . Consulte el sitio del proyecto para obtener más detalles.

Respuesta original

Como nadie ha proporcionado un código seguro todavía, publico lo siguiente en caso de que alguien lo encuentre útil.

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

Gracias a Ahmad por señalar cómo hacer que el código funcione en .NET Core.

Michael Kropat
fuente
La solución @Keltex no estaba funcionando bien para mí (estaba devolviendo la misma cadena después de algunos usos). Esta solución funciona perfectamente :)
JoanComasFdz
2
@LeeGrissom, el sesgo es un aspecto importante. Digamos, por ejemplo, que su alfabeto contiene 255 caracteres y obtiene un valor aleatorio entre 0-255. En un búfer de anillo, tanto el valor 0 como el 255 corresponderían al mismo carácter, lo que sesgaría el resultado a favor del primer carácter del alfabeto, sería menos aleatorio. si esto importa depende de la aplicación, por supuesto.
Oskar Sjöberg
4
A quién apunta .netcore: Reemplazar var rng = new RNGCryptoServiceProvider()convar rng = RandomNumberGenerator.Create()
amd
1
¿Por qué calcula 'var outOfRangeStart = byteSize - (byteSize% allowedCharSet.Length);' para cada iteración? Puede calcularlo antes de 'usar'.
mtkachenko
1
@BartCalixto Fijo. ¡Gracias!
Michael Kropat
38

Les advierto que los GUID no son números aleatorios . No deben usarse como base para generar nada que espere que sea totalmente aleatorio (consulte http://en.wikipedia.org/wiki/Globally_Unique_Identifier ):

El criptoanálisis del generador de GUID de WinAPI muestra que, dado que la secuencia de GUID de V4 es pseudoaleatoria, dado el estado inicial se pueden predecir hasta los siguientes 250 000 GUID devueltos por la función UuidCreate. Es por eso que los GUID no deben usarse en criptografía, por ejemplo, como claves aleatorias.

En su lugar, simplemente use el método C # Random. Algo como esto ( código que se encuentra aquí ):

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

Los GUID están bien si desea algo único (como un nombre de archivo único o una clave en una base de datos), pero no son buenos para algo que desea que sea aleatorio (como una contraseña o una clave de cifrado). Entonces depende de tu aplicación.

Editar . Microsoft dice que Random tampoco es tan bueno ( http://msdn.microsoft.com/en-us/library/system.random(VS.71).aspx ):

Para generar un número aleatorio criptográficamente seguro adecuado para crear una contraseña aleatoria, por ejemplo, utilice una clase derivada de System.Security.Cryptography.RandomNumberGenerator como System.Security.Cryptography.RNGCryptoServiceProvider.

Keltex
fuente
5
La clase aleatoria de C # tampoco es "aleatoria" y no es adecuada para ningún código criptográfico, ya que es un generador aleatorio clásico a partir de un número de semilla específico. La misma semilla también devolverá la misma secuencia de números; el enfoque GUID ya está mucho mejor aquí (no "aleatorio" sino "único").
Lucero
3
@Lucero: Tienes razón. Microsoft recomienda, "Para generar un número aleatorio criptográficamente seguro adecuado para crear una contraseña aleatoria, por ejemplo, use una clase derivada de System.Security.Cryptography.RandomNumberGenerator como System.Security.Cryptography.RNGCryptoServiceProvider".
Keltex
Bueno, la pregunta ya decía que quiere cadenas únicas (pseudo) aleatorias, por lo que no hay requisitos de cifrado o incluso la necesidad de seguir una distribución aleatoria específica. Entonces, GUID es probablemente el enfoque más fácil.
Joey
1
La afirmación de que "dado el estado inicial se pueden predecir hasta los próximos 250 000 GUID" parece una afirmación intrínsecamente verdadera para cualquier PRNG ... Estoy seguro de que tampoco es seguro, pero no estoy seguro de que haya mucho valor en generar URL verdaderamente aleatorias, si eso es lo que busca el OP. ;)
ojrac
1
(+1 de todos modos; la educación de PRNG es importante).
ojrac
13

Simplifiqué la solución de @Michael Kropats e hice una versión al estilo 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();
    }   
}
Oskar Sjöberg
fuente
11

No creo que realmente sean aleatorios, pero supongo que son algunos hash.

Siempre que necesito algún identificador aleatorio, normalmente uso un GUID y lo convierto a su representación "desnuda":

Guid.NewGuid().ToString("n");
Lucero
fuente
Como señaló @Keltex: El criptoanálisis del generador de GUID de WinAPI muestra que, dado que la secuencia de GUID de V4 es pseudoaleatoria, dado el estado inicial se pueden predecir hasta los siguientes 250 000 GUID devueltos por la función UuidCreate.
JoanComasFdz
4

Pruebe la combinación entre Guid y Time.

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

Me sorprende que no exista una solución CrytpoGraphic. GUID es único pero no criptográficamente seguro . Vea este violín Dotnet.

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

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

En caso de que desee anteponer un Guid:

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

Una cadena alfanumérica más limpia:

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

Solución de Michael Kropats en 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
fuente
1

Esto funciona perfecto para mi

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

Esto se ha solicitado para varios idiomas. Aquí hay una pregunta sobre las contraseñas que también debería ser aplicable aquí.

Si desea utilizar las cadenas para acortar la URL, también necesitará un Diccionario <> o una verificación de la base de datos para ver si ya se ha utilizado una ID generada.

Pontus Gagge
fuente
0

Si desea cadenas alfanuméricas con minúsculas y mayúsculas ([a-zA-Z0-9]), puede usar Convert.ToBase64String () para una solución rápida y sencilla.

En cuanto a la singularidad, consulte el problema de cumpleaños para calcular la probabilidad de que se dé una colisión (A) la longitud de las cadenas generadas y (B) la cantidad de cadenas generadas.

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
fuente
-1
  • no estoy seguro de que el enlace de Microsoft se genere aleatoriamente
  • eche un vistazo al nuevo Guid (). ToString ()
Fabian Vilers
fuente
4
Te refieres a Guid.NewGuid (). ToString () - Guid no tiene un constructor público
cjk
3
Probablemente tienes razón, estaba escribiendo sin verificar. Estoy seguro de que el póster original tiene razón.
Fabian Vilers
-1

Obtenga una clave única usando el código GUID Hash

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
fuente
Esto funciona perfectamente, pero las palabras aleatorias no contienen caracteres únicos. Los caracteres se repiten, como 114e3 (dos unos), eaaea (tres a y dos e), 60207 (dos ceros) y así sucesivamente. ¿Cómo generar una cadena aleatoria sin repetición de caracteres con combinación alfanumérica?
vijay
@vijay: Ya que está generando dígitos hexadecimales, ¡te estás limitando a 16 caracteres y 16! posibles salidas. Las cadenas aleatorias son solo eso, aleatorias. Teóricamente, podrías obtener una cadena de todas las a (aaaaaaaaaaaaaaa). Es muy improbable, pero no más que cualquier otra cadena aleatoria. No estoy seguro de por qué necesitaría esa restricción, pero a medida que agrega caracteres a la cadena, colóquelos en un HashSet <T>, verifique su existencia y agréguelos a la cadena u omítelos en consecuencia.
Chris Doggett