1290 Stimmen

Wie kann ich zufällige alphanumerische Zeichenfolgen erzeugen?

Wie kann ich eine zufällige 8 Zeichen alphanumerische Zeichenfolge in C# generieren?

2 Stimmen

Welcher Rest

8 Stimmen

O s o s

2 Stimmen

Es wäre schön, wenn die Sprachlokalisierung in diese Frage einbezogen würde. Vor allem, wenn Ihre Benutzeroberfläche für Chinesisch oder Bulgarisch geeignet sein soll!

2136voto

dtb Punkte 205441

Ich habe gehört, dass LINQ das neue Schwarz ist, also hier ist mein Versuch mit LINQ:

private static Random random = new Random();

public static string RandomString(int length)
{
    const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    return new string(Enumerable.Repeat(chars, length)
        .Select(s => s[random.Next(s.Length)]).ToArray());
}

(Hinweis: Die Verwendung des Random Klasse macht dies ungeeignet für sicherheitsrelevante Themen wie die Erstellung von Passwörtern oder Token. Verwenden Sie die RNGCryptoServiceProvider Klasse, wenn Sie einen starken Zufallszahlengenerator benötigen).

492voto

Dan Rigby Punkte 15975
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var stringChars = new char[8];
var random = new Random();

for (int i = 0; i < stringChars.Length; i++)
{
    stringChars[i] = chars[random.Next(chars.Length)];
}

var finalString = new String(stringChars);

Nicht so elegant wie die Linq-Lösung.

(Hinweis: Die Verwendung des Random Klasse macht dies ungeeignet für sicherheitsrelevante Themen wie die Erstellung von Passwörtern oder Token. Verwenden Sie die RNGCryptoServiceProvider Klasse, wenn Sie einen starken Zufallszahlengenerator benötigen).

442voto

Eric J. Punkte 143512

AKTUALISIERT für .NET 6. RNGCryptoServiceProvider ist als veraltet gekennzeichnet. Rufen Sie stattdessen RandomNumberGenerator.Create() . Der Code in der Antwort wurde entsprechend aktualisiert.

Aktualisiert aufgrund von Kommentaren. Die ursprüngliche Implementierung erzeugte in ~1,95% der Fälle a-h und in ~1,56% der Fälle die übrigen Zeichen. Die Aktualisierung erzeugt alle Zeichen in ~1,61 % der Zeit.

FRAMEWORK SUPPORT - .NET Core 3 (und zukünftige Plattformen, die den .NET Standard 2.1 oder höher unterstützen) bietet eine kryptografisch solide Methode RandomNumberGenerator.GetInt32() um eine zufällige ganze Zahl innerhalb eines gewünschten Bereichs zu erzeugen.

Im Gegensatz zu einigen der vorgestellten Alternativen ist diese Alternative kryptographisch einwandfrei .

using System;
using System.Security.Cryptography;
using System.Text;

namespace UniqueKey
{
    public class KeyGenerator
    {
        internal static readonly char[] chars =
            "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray(); 

        public static string GetUniqueKey(int size)
        {            
            byte[] data = new byte[4*size];
            using (var crypto = RandomNumberGenerator.Create())
            {
                crypto.GetBytes(data);
            }
            StringBuilder result = new StringBuilder(size);
            for (int i = 0; i < size; i++)
            {
                var rnd = BitConverter.ToUInt32(data, i * 4);
                var idx = rnd % chars.Length;

                result.Append(chars[idx]);
            }

            return result.ToString();
        }

        public static string GetUniqueKeyOriginal_BIASED(int size)
        {
            char[] chars =
                "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();
            byte[] data = new byte[size];
            using (RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider())
            {
                crypto.GetBytes(data);
            }
            StringBuilder result = new StringBuilder(size);
            foreach (byte b in data)
            {
                result.Append(chars[b % (chars.Length)]);
            }
            return result.ToString();
        }
    }
}

Auf der Grundlage einer Diskussion von Alternativen ici und auf der Grundlage der nachstehenden Kommentare aktualisiert/geändert.

Hier ist ein kleines Testprogramm, das die Verteilung der Zeichen in der alten und aktualisierten Ausgabe demonstriert. Für eine ausführliche Diskussion über die Analyse der Zufälligkeit finden Sie unter random.org.

using System;
using System.Collections.Generic;
using System.Linq;
using UniqueKey;

namespace CryptoRNGDemo
{
    class Program
    {

        const int REPETITIONS = 1000000;
        const int KEY_SIZE = 32;

        static void Main(string[] args)
        {
            Console.WriteLine("Original BIASED implementation");
            PerformTest(REPETITIONS, KEY_SIZE, KeyGenerator.GetUniqueKeyOriginal_BIASED);

            Console.WriteLine("Updated implementation");
            PerformTest(REPETITIONS, KEY_SIZE, KeyGenerator.GetUniqueKey);
            Console.ReadKey();
        }

        static void PerformTest(int repetitions, int keySize, Func<int, string> generator)
        {
            Dictionary<char, int> counts = new Dictionary<char, int>();
            foreach (var ch in UniqueKey.KeyGenerator.chars) counts.Add(ch, 0);

            for (int i = 0; i < REPETITIONS; i++)
            {
                var key = generator(KEY_SIZE); 
                foreach (var ch in key) counts[ch]++;
            }

            int totalChars = counts.Values.Sum();
            foreach (var ch in UniqueKey.KeyGenerator.chars)
            {
                Console.WriteLine($"{ch}: {(100.0 * counts[ch] / totalChars).ToString("#.000")}%");
            }
        }
    }
}

Aktualisierung 25.7.2022

Aufgrund einer Frage in den Kommentaren war ich neugierig, ob die Verteilung wirklich zufällig ist.

Ich bin kein Statistiker, aber ich könnte wahrscheinlich einen im Fernsehen spielen. Wenn sich ein tatsächlicher Statistiker einmischen möchte, wäre das sehr willkommen.

Es gibt 62 mögliche Ausgabewerte (A-Za-Z0-9) und int.MaxValue Zahlen, die zur Auswahl eines Array-Index verwendet werden. int.MaxValue % 62 ist 1, d. h. ein Zeichen wird 1:4 Milliarden Mal häufiger ausgewählt als die anderen. Wir könnten diese Auswahlverzerrung weiter reduzieren, indem wir das Array der Ausgabewerte vor der Indizierung zufällig drehen.

Ein T-Test oder ein anderes statistisches Maß wäre der beste Ansatz, um festzustellen, ob die Ausgabeergebnisse verzerrt sind, aber das kann ich in meiner Mittagspause nicht durchführen, also überlasse ich Ihnen stattdessen eine Änderung des obigen Codes, der die Abweichung von der Erwartung misst. Beachten Sie, dass sie gegen Null tendiert.

using System.Security.Cryptography;
using System.Text;

const int REPETITIONS = 1_000_000;
const int KEY_SIZE = 32;
int TASK_COUNT = Environment.ProcessorCount - 1;

var expectedPercentage = 100.0 / KeyGenerator.chars.Length;

var done = false;
var iterationNr = 1;
var totalRandomSymbols = 0L;

var grandTotalCounts = new Dictionary<char, long>();
foreach (var ch in KeyGenerator.chars) grandTotalCounts.Add(ch, 0);

while (!done)
{
    var experiments = Enumerable.Range(0, TASK_COUNT).Select(i => Task.Run(Experiment)).ToArray();
    Task.WaitAll(experiments);
    var totalCountsThisRun = experiments.SelectMany(e => e.Result)
        .GroupBy(e => e.Key)
        .Select(e => new { e.Key, Count = e.Select(_ => _.Value).Sum() })
        .ToDictionary(e => e.Key, e => e.Count);

    foreach (var ch in KeyGenerator.chars)
        grandTotalCounts[ch] += totalCountsThisRun[ch];

    var totalChars = grandTotalCounts.Values.Sum();
    totalRandomSymbols += totalChars;

    var distributionScores = KeyGenerator.chars.Select(ch =>
    new
    {
        Symbol = ch,
        OverUnder = (100.0 * grandTotalCounts[ch] / totalChars) - expectedPercentage

    });

    Console.WriteLine($"Iteration {iterationNr++}. Total random symbols: {totalRandomSymbols:N0}");
    foreach (var chWithValue in distributionScores.OrderByDescending(c => c.OverUnder))
    {
        Console.WriteLine($"{chWithValue.Symbol}: {chWithValue.OverUnder:#.00000}%");
    }

    done = Console.KeyAvailable;        
}

Dictionary<char, long> Experiment()
{
    var counts = new Dictionary<char, long>();
    foreach (var ch in KeyGenerator.chars) counts.Add(ch, 0);

    for (int i = 0; i < REPETITIONS; i++)
    {
        var key = KeyGenerator.GetUniqueKey(KEY_SIZE);
        foreach (var ch in key) counts[ch]++;
    }

    return counts;
}

public class KeyGenerator
{
    internal static readonly char[] chars =
        "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".ToCharArray();

    public static string GetUniqueKey(int size)
    {
        byte[] data = new byte[4 * size];
        using (var crypto = RandomNumberGenerator.Create())
        {
            crypto.GetBytes(data);
        }
        StringBuilder result = new StringBuilder(size);
        for (int i = 0; i < size; i++)
        {
            var rnd = BitConverter.ToUInt32(data, i * 4);
            var idx = rnd % chars.Length;

            result.Append(chars[idx]);
        }

        return result.ToString();
    }
}

233voto

Douglas Punkte 2386

Lösung 1 - größte "Reichweite" mit flexibelster Länge

string get_unique_string(int string_length) {
    using(var rng = new RNGCryptoServiceProvider()) {
        var bit_count = (string_length * 6);
        var byte_count = ((bit_count + 7) / 8); // rounded up
        var bytes = new byte[byte_count];
        rng.GetBytes(bytes);
        return Convert.ToBase64String(bytes);
    }
}

Diese Lösung hat eine größere Reichweite als die Verwendung einer GUID, da eine GUID einige feste Bits hat, die immer gleich sind und daher nicht zufällig sind, z. B. ist das 13-Zeichen in Hex immer "4" - zumindest in einer GUID der Version 6.

Mit dieser Lösung können Sie auch eine Zeichenkette beliebiger Länge erzeugen.

Lösung 2 - Eine Codezeile - für bis zu 22 Zeichen

Convert.ToBase64String(Guid.NewGuid().ToByteArray()).Substring(0, 8);

Sie können keine Zeichenketten erzeugen, die so lang sind wie Lösung 1 und die Zeichenkette hat nicht den gleichen Bereich aufgrund fester Bits in GUIDs, aber in vielen Fällen wird dies die Aufgabe erfüllen.

Lösung 3 - Etwas weniger Code

Guid.NewGuid().ToString("n").Substring(0, 8);

Ich behalte das hier vor allem aus historischen Gründen. Es verwendet etwas weniger Code, das kommt aber auf Kosten von weniger Reichweite - weil es Hex anstelle von Base64 verwendet, braucht es mehr Zeichen, um den gleichen Bereich im Vergleich zu den anderen Lösungen zu repräsentieren.

Das bedeutet, dass die Wahrscheinlichkeit von Kollisionen größer ist - beim Testen mit 100.000 Iterationen von 8-Zeichen-Strings wurde ein Duplikat erzeugt.

97voto

Adam Porad Punkte 13624

Hier ist ein Beispiel, das ich von Sam Allen geklaut habe. Dot Net Perls

Wenn Sie nur 8 Zeichen benötigen, dann verwenden Sie Path.GetRandomFileName() im System.IO-Namensraum. Sam sagt, dass die "Path.GetRandomFileName-Methode hier manchmal besser ist, weil sie RNGCryptoServiceProvider für eine bessere Zufälligkeit verwendet. Allerdings ist sie auf 11 Zufallszeichen begrenzt."

GetRandomFileName gibt immer eine 12-stellige Zeichenkette mit einem Punkt am 9. Zeichen zurück. Sie müssen also den Punkt entfernen (da dieser nicht zufällig ist) und dann 8 Zeichen aus der Zeichenfolge nehmen. Eigentlich könnten Sie auch nur die ersten 8 Zeichen nehmen und sich nicht um den Punkt kümmern.

public string Get8CharacterRandomString()
{
    string path = Path.GetRandomFileName();
    path = path.Replace(".", ""); // Remove period.
    return path.Substring(0, 8);  // Return 8 character string
}

PS: Danke Sam

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X