Генерация уникальной случайной строки

98

Я хотел бы сгенерировать случайные уникальные строки, подобные тем, которые генерируются библиотекой MSDN (например, объект ошибки ). Должна быть сгенерирована строка типа "t9zk6eay".

Киртан
источник
1
попробуйте это string randoms = Guid.NewGuid().ToString().Replace("-", string.Empty).Replace("+", string.Empty).Substring(0, 4);больше можно найти здесь
Shaijut
1
Чтобы что-то было полностью уникальным, оно должно быть основано на чем-то неслучайном, например, времени, местоположении и т. Д., И поэтому никогда не может быть полностью случайным. Гид может показаться случайным, но на самом деле это не так. ИМО, ваша единственная надежда - сделать его настолько случайным и сложным, чтобы для всех практических целей значения были уникальными (т.е. имели чрезвычайно низкую вероятность столкновения).
bytedev

Ответы:

85

Использование Guid было бы неплохим способом, но чтобы получить что-то похожее на ваш пример, вы, вероятно, захотите преобразовать его в строку Base64:

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

Я избавляюсь от «=» и «+», чтобы немного приблизиться к вашему примеру, иначе вы получите «==» в конце строки и «+» в середине. Вот пример выходной строки:

"OZVV5TpP4U6wJthaCORZEQ"

Марк Синовец
источник
15
Вам также следует подумать о замене /.
Джейсон Кили, 04
20
Guid не следует рассматривать как безопасную случайную строку, поскольку последовательность может быть угадана. Руководство предназначено для предотвращения конфликтов ключей, а не для случайного использования. Есть несколько хороших обсуждений случайности Guid при переполнении стека.
Дэниел Брэдли,
Для ясного и краткого объяснения того, о чем Convert.ToBase64Stringидет речь, загляните сюда .
jwaliszko
2
Может ли преобразование guid в base64 и замена + и = увеличить вероятность столкновения?
Милан Аггарвал
6
@SimonEjsing Я приглашаю вас выпить пива, если вы действительно можете написать приложение, которое получает коллизии при использовании new Guid()без "взлома" (подделки часов или внутренних структур данных Windows). Не стесняйтесь использовать столько ядер, потоков, примитивов синхронизации и т. Д., Сколько захотите.
Lucero
175

Обновление 2016/1/23

Если вы найдете этот ответ полезным, вас может заинтересовать опубликованная мною простая библиотека генерации паролей (~ 500 SLOC) :

Install-Package MlkPwgen

Затем вы можете генерировать случайные строки, как в ответе ниже:

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

Одним из преимуществ библиотеки является то, что код лучше разложен, поэтому вы можете использовать безопасную случайность не только для генерации строк . Посетите сайт проекта для получения более подробной информации.

Оригинальный ответ

Поскольку никто еще не предоставил безопасный код, я публикую следующее на тот случай, если кто-то сочтет это полезным.

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

Спасибо Ахмаду за то, что он указал, как заставить код работать в .NET Core.

Михаил Кропат
источник
Решение @Keltex не работало для меня (оно возвращало ту же строку после нескольких использований). Это решение отлично работает :)
JoanComasFdz 07
2
@LeeGrissom, смещение - важный аспект. Допустим, например, что ваш алфавит содержит 255 символов, и вы получаете случайное значение от 0 до 255. В кольцевом буфере и значение 0, и 255 будут соответствовать одному и тому же символу, что исказит результат в пользу первого символа в алфавите, это будет менее случайным. если это имеет значение, конечно, зависит от приложения.
Оскар Сьёберг,
4
Кто нацелен .netcore: Заменить var rng = new RNGCryptoServiceProvider()наvar rng = RandomNumberGenerator.Create()
amd
1
Почему вы вычисляете var outOfRangeStart = byteSize - (byteSize% allowedCharSet.Length); на каждую итерацию? Вы можете рассчитать его перед «использованием».
mtkachenko
1
@BartCalixto Исправлено. Спасибо!
Майкл Кропат
38

Я бы предупредил, что идентификаторы GUID не случайные числа . Их не следует использовать в качестве основы для генерации чего-либо, что, как вы ожидаете, будет абсолютно случайным (см. Http://en.wikipedia.org/wiki/Globally_Unique_Identifier ):

Криптоанализ генератора GUID WinAPI показывает, что, поскольку последовательность GUID V4 является псевдослучайной, при начальном состоянии можно предсказать до следующих 250 000 GUID, возвращаемых функцией UuidCreate. Вот почему GUID не следует использовать в криптографии, например, как случайные ключи.

Вместо этого просто используйте метод C # Random. Что-то вроде этого ( код здесь ):

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

GUID подходят, если вам что-то нужно уникальное (например, уникальное имя файла или ключ в базе данных), но они не подходят для чего-то, что вы хотите быть случайным (например, пароля или ключа шифрования). Так что это зависит от вашего приложения.

редактировать . Microsoft говорит, что Random тоже не так хорош ( http://msdn.microsoft.com/en-us/library/system.random(VS.71).aspx ):

Чтобы сгенерировать криптографически безопасное случайное число, подходящее, например, для создания случайного пароля, используйте класс, производный от System.Security.Cryptography.RandomNumberGenerator, например System.Security.Cryptography.RNGCryptoServiceProvider.

Keltex
источник
5
Случайный класс C # также не является «случайным» и непригоден для любого криптокода, поскольку это классический генератор случайных чисел, начинающийся с определенного начального числа. То же семя также вернет ту же последовательность возвращенных чисел; подход GUID здесь уже намного лучше (не «случайный», а «уникальный»).
Lucero
3
@Lucero: Вы правы. Microsoft рекомендует: «Чтобы сгенерировать криптографически безопасное случайное число, подходящее для создания случайного пароля, например, используйте класс, производный от System.Security.Cryptography.RandomNumberGenerator, такой как System.Security.Cryptography.RNGCryptoServiceProvider».
Keltex
Что ж, в вопросе уже говорилось, что ему нужны (псевдо) случайные уникальные строки, поэтому нет требований к криптографии или даже необходимости следовать определенному случайному распределению. Так что GUID, вероятно, самый простой подход.
Джоуи,
1
Утверждение, что «учитывая начальное состояние, можно предсказать до следующих 250 000 GUID», кажется истинным утверждением для любого ГПСЧ ... Я уверен, что это также небезопасно, но я не уверен, что есть большая ценность в создании действительно случайные URL-адреса, если это то, для чего нужен OP. ;)
ojrac 08
1
(+1 в любом случае - образование ГПСЧ важно.)
ojrac
13

Я упростил решение @Michael Kropats и сделал версию в стиле 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

Я не думаю, что они действительно случайны, но предполагаю, что это какие-то хеши.

Когда мне нужен какой-то случайный идентификатор, я обычно использую GUID и конвертирую его в "голое" представление:

Guid.NewGuid().ToString("n");
Lucero
источник
Как указал @Keltex: Криптоанализ генератора GUID WinAPI показывает, что, поскольку последовательность GUID V4 является псевдослучайной, с учетом начального состояния можно предсказать до следующих 250 000 GUID, возвращаемых функцией UuidCreate.
JoanComasFdz 07
4

Попробуйте комбинацию Guid и Time.Ticks

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

Я удивлен, почему нет решения CrytpoGraphic. GUID уникален, но не является криптографически безопасным . См. Эту скрипку Dotnet.

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

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

Если вы хотите подготовить Guid:

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

Более чистая буквенно-цифровая строка:

result = Regex.Replace(result,"[^A-Za-z0-9]","");
Console.WriteLine(result);
тика
источник
1

Решение Майкла Кропаца в 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
Джерси29
источник
1

Это работает идеально для меня

    private string GeneratePasswordResetToken()
    {
        string token = Guid.NewGuid().ToString();
        var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(token);
        return Convert.ToBase64String(plainTextBytes);
    }
MarlinG
источник
0

Это было запрошено для разных языков. Вот один вопрос о паролях, которые также должны быть применимы здесь.

Если вы хотите использовать строки для сокращения URL-адресов, вам также понадобится Dictionary <> или проверка базы данных, чтобы узнать, был ли уже использован сгенерированный идентификатор.

Pontus Gagge
источник
0

Если вам нужны буквенно-цифровые строки со строчными буквами и символами верхнего регистра ([a-zA-Z0-9]), вы можете использовать Convert.ToBase64String () для быстрого и простого решения.

Что касается уникальности, проверьте задачу дня рождения, чтобы вычислить, насколько вероятно возникновение столкновения (A) длина сгенерированных строк и (B) количество сгенерированных строк.

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
  • не уверен, что ссылка Microsoft генерируется случайным образом
  • взгляните на новый Guid (). ToString ()
Фабиан Вилерс
источник
4
Вы имеете в виду Guid.NewGuid (). ToString () - Guid не имеет общедоступного конструктора
cjk
3
Вы, вероятно, правы, набирали без проверки. Я уверен, что в оригинальном плакате есть смысл.
Фабиан Вилерс
-1

Получить уникальный ключ с помощью хеш-кода 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);
}
Крис Доггетт
источник
Это работает отлично, но случайные слова не содержат уникальных символов. Символы повторяются, например, 114e3 (две единицы), eaaea (три буквы a и две буквы e), 60207 (два нуля) и так далее. Как сгенерировать случайную строку без повторения символов с буквенно-цифровой комбинацией?
vijay
@vijay: Поскольку он выводит шестнадцатеричные цифры, вы ограничиваете себя 16 символами и 16! возможные выходы. Случайные строки просто случайны. Теоретически вы можете получить строку из всех а (аааааааааааааа). Это очень маловероятно, но не более, чем любая другая случайная строка. Я не уверен, зачем вам это ограничение, но когда вы добавляете символы в строку, вставляйте их в HashSet <T>, проверяйте их существование и добавляйте их в строку или пропускайте соответственно.
Крис Доггетт,