Geração de string aleatória única

97

Eu gostaria de gerar cadeias de caracteres únicas aleatórias como as geradas pela biblioteca MSDN. ( Objeto de erro ), por exemplo. Uma string como 't9zk6eay' deve ser gerada.

Kirtan
fonte
1
tente isso string randoms = Guid.NewGuid().ToString().Replace("-", string.Empty).Replace("+", string.Empty).Substring(0, 4);mais pode ser encontrado aqui
shaijut
1
Para algo ser totalmente único, deve ser baseado em algo não aleatório, como tempo, localização, etc. e, portanto, nunca pode ser totalmente aleatório. Um Guid pode parecer aleatório, mas na realidade não é. IMO, sua única esperança é torná-lo tão aleatório e complexo que, para todos os efeitos práticos, os valores sejam únicos (ou seja, tenham uma probabilidade extremamente baixa de colisão).
bytedev

Respostas:

84

Usar Guid seria uma maneira muito boa, mas para obter algo parecido com seu exemplo, você provavelmente deseja convertê-lo em uma string Base64:

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

Eu me livrei de "=" e "+" para chegar um pouco mais perto do seu exemplo, caso contrário, você obterá "==" no final da sua string e um "+" no meio. Aqui está um exemplo de string de saída:

"OZVV5TpP4U6wJthaCORZEQ"

Mark Synowiec
fonte
15
Você deve considerar a substituição de / também.
Jason Kealey
20
Um Guid não deve ser considerado uma string aleatória segura, pois a sequência pode ser adivinhada. Um Guid é projetado para evitar conflitos de chave, em vez de ser aleatório. Existem algumas boas discussões sobre a aleatoriedade de um Guid no estouro de pilha.
Daniel Bradley
Para uma explicação clara e curta do que Convert.ToBase64Stringse trata, dê uma olhada aqui .
jwaliszko,
2
Pode converter guid em base64 e substituir + e = aumentar a probabilidade de colisão?
Milan Aggarwal
5
@SimonEjsing Vou convidá-lo para uma cerveja se você puder realmente escrever um aplicativo que obtém colisões ao usar new Guid()sem "hackear" (adulterar o relógio ou estruturas de dados internas do Windows). Sinta-se à vontade para usar quantos núcleos, threads, primitivos de sincronização, etc., você quiser.
Lucero
175

Atualização 2016/1/23

Se você achar esta resposta útil, pode estar interessado em uma biblioteca simples de geração de senha (~ 500 SLOC) que publiquei :

Install-Package MlkPwgen

Então, você pode gerar strings aleatórias, como na resposta abaixo:

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

Uma vantagem da biblioteca é que o código é melhor fatorado para que você possa usar a aleatoriedade segura para mais do que gerar strings . Confira o site do projeto para mais detalhes.

Resposta Original

Já que ninguém forneceu um código seguro ainda, eu posto o seguinte caso alguém o considere ú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();
    }
}

Obrigado a Ahmad por apontar como fazer o código funcionar no .NET Core.

Michael Kropat
fonte
A solução @Keltex não estava funcionando direito para meh (estava retornando a mesma string após alguns usos). Esta solução funciona perfeitamente :)
JoanComasFdz
2
@LeeGrissom, o enviesamento é um aspecto importante. Digamos, por exemplo, que seu alfabeto contenha 255 caracteres e você obtenha um valor aleatório entre 0-255. Em um buffer de anel, os valores 0 e 255 corresponderiam ao mesmo caractere, o que distorceria o resultado em favor do primeiro caractere do alfabeto, seria menos aleatório. se isso importa depende da aplicação do curso.
Oskar Sjöberg
4
Quem está segmentando .netcore: substituir var rng = new RNGCryptoServiceProvider()porvar rng = RandomNumberGenerator.Create()
alt
1
Por que você calcula 'var outOfRangeStart = byteSize - (byteSize% allowedCharSet.Length);' para cada iteração? Você pode calculá-lo antes de 'usar'.
mtkachenko,
1
@BartCalixto Fixed. Obrigado!
Michael Kropat
38

Devo advertir que os GUIDs não são números aleatórios . Eles não devem ser usados ​​como base para gerar algo que você espera que seja totalmente aleatório (consulte http://en.wikipedia.org/wiki/Globally_Unique_Identifier ):

A criptoanálise do gerador WinAPI GUID mostra que, como a sequência de V4 GUIDs é pseudo-aleatória, dado o estado inicial, pode-se prever até os próximos 250.000 GUIDs retornados pela função UuidCreate. É por isso que os GUIDs não devem ser usados ​​em criptografia, por exemplo, como chaves aleatórias.

Em vez disso, apenas use o método C # Random. Algo assim ( código encontrado aqui ):

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

GUIDs são adequados se você deseja algo exclusivo (como um nome de arquivo ou chave exclusiva em um banco de dados), mas não são adequados para algo que você deseja que seja aleatório (como uma senha ou chave de criptografia). Portanto, depende da sua aplicação.

Editar . A Microsoft diz que Random também não é tão bom ( http://msdn.microsoft.com/en-us/library/system.random(VS.71).aspx ):

Para gerar um número aleatório criptograficamente seguro adequado para criar uma senha aleatória, por exemplo, use uma classe derivada de System.Security.Cryptography.RandomNumberGenerator, como System.Security.Cryptography.RNGCryptoServiceProvider.

Keltex
fonte
5
A classe aleatória C # também não é "aleatória" e inadequada para qualquer código criptográfico, uma vez que é um gerador aleatório clássico a partir de um número inicial específico. A mesma semente também retornará a mesma sequência de números retornada; a abordagem GUID já é muito melhor aqui (não "aleatória", mas "única").
Lucero,
3
@Lucero: Você está correto. A Microsoft recomenda, "Para gerar um número aleatório criptograficamente seguro adequado para criar uma senha aleatória, por exemplo, use uma classe derivada de System.Security.Cryptography.RandomNumberGenerator, como System.Security.Cryptography.RNGCryptoServiceProvider."
Keltex de
Bem, a questão já afirmou que ele quer strings únicas (pseudo-) aleatórias, então sem requisitos de criptografia ou mesmo a necessidade de seguir uma distribuição aleatória específica. Portanto, o GUID é provavelmente a abordagem mais fácil.
Joey,
1
A afirmação de que "dado o estado inicial, pode-se prever até os próximos 250.000 GUIDs" parece uma afirmação inerentemente verdadeira para qualquer PRNG ... Tenho certeza de que também não é seguro, mas não tenho certeza se há muito valor em gerar URLs verdadeiramente aleatórios, se é isso que o OP pretende. ;)
ojrac
1
(+1 de qualquer maneira - a educação PRNG é importante.)
ojrac
13

Simplifiquei a solução @Michael Kropats e fiz uma versão no 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
fonte
11

Não acho que sejam realmente aleatórios, mas acho que são alguns hashes.

Sempre que preciso de algum identificador aleatório, costumo usar um GUID e convertê-lo em sua representação "simples":

Guid.NewGuid().ToString("n");
Lucero
fonte
Como @Keltex apontou: A criptoanálise do gerador WinAPI GUID mostra que, como a seqüência de GUIDs V4 é pseudoaleatória, dado o estado inicial, pode-se prever até os próximos 250.000 GUIDs retornados pela função UuidCreate.
JoanComasFdz
4

Experimente a combinação entre 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]+", "");
DevC
fonte
3

Estou surpreso por não haver uma solução criptográfica em vigor. O GUID é exclusivo, mas não criptograficamente seguro . Veja este 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);

Caso você queira adicionar um Guid ao início:

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

Uma string alfanumérica mais limpa:

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

Solução Michael Kropats em 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
fonte
1

Isso funciona perfeito para mim

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

Isso foi solicitado em vários idiomas. Aqui está uma pergunta sobre senhas que deve ser aplicável aqui também.

Se você quiser usar as strings para encurtamento de URL, também precisará de um Dicionário <> ou verificação de banco de dados para ver se um ID gerado já foi usado.

Pontus Gagge
fonte
0

Se quiser strings alfanuméricas com caracteres minúsculos e maiúsculos ([a-zA-Z0-9]), você pode usar Convert.ToBase64String () para uma solução rápida e simples.

Quanto à exclusividade, verifique o problema do aniversário para calcular a probabilidade de uma colisão receber (A) o comprimento das strings geradas e (B) o número de strings geradas.

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
fonte
-1
  • não tenho certeza se os links da Microsoft são gerados aleatoriamente
  • dê uma olhada em new Guid (). ToString ()
Fabian Vilers
fonte
4
Você quer dizer Guid.NewGuid (). ToString () - Guid não tem um construtor público
cjk
3
Você provavelmente está certo, estava digitando sem verificar. Tenho certeza de que o autor da postagem original tem razão.
Fabian Vilers,
-1

Obtenha uma chave única usando o código 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);
}
Chris Doggett
fonte
Isso funciona perfeitamente, mas palavras aleatórias não contêm caracteres exclusivos. Os caracteres estão se repetindo, como 114e3 (dois 1's), eaaea (três as e dois e's), 60207 (dois 0's) e assim por diante. Como gerar uma string Random sem repetição de caracteres com combinação alfanumérica?
vijay
@vijay: Como está gerando dígitos hexadecimais, você está se limitando a 16 caracteres e 16! possíveis saídas. Strings aleatórias são apenas isso, aleatórias. Você poderia teoricamente obter uma string de todos os a's (aaaaaaaaaaaaaaa). É muito improvável, mas não mais do que qualquer outra string aleatória. Não sei por que você precisa dessa restrição, mas conforme você adiciona caracteres à string, coloque-os em um HashSet <T>, verifique sua existência e adicione-os à string ou ignore-os de acordo.
Chris Doggett