ฉันจะสร้างสตริงตัวอักษรผสมตัวเลขแบบสุ่มได้อย่างไร


967

ฉันจะสร้างสตริงตัวอักษรและตัวเลขแบบสุ่ม 8 ตัวใน C # ได้อย่างไร


2
คุณมีข้อ จำกัด อะไรบ้างในชุดตัวละคร? แค่ตัวอักษรภาษาอังกฤษและ 0-9? กรณีผสม?
Eric J.


9
โปรดทราบว่าคุณไม่ควรใช้วิธีการใด ๆ ตามRandomชั้นเรียนเพื่อสร้างรหัสผ่าน การเพาะของRandomเอนโทรปีมีค่าต่ำมากจึงไม่ปลอดภัย ใช้ PRNG เข้ารหัสลับสำหรับรหัสผ่าน
CodesInChaos

2
ยินดีที่ได้รวมการแปลภาษาไว้ในคำถามนี้ โดยเฉพาะอย่างยิ่งหากกุยของคุณจำเป็นต้องรองรับภาษาจีนหรือบัลแกเรีย!
Peter Jamsmenson

15
บางสิ่งที่มี upvotes มากมายและคำตอบที่มีคุณภาพมากมายนี้ไม่ควรถูกทำเครื่องหมายว่าปิด ฉันลงคะแนนว่าจะเปิดใหม่
John Coleman

คำตอบ:


1684

ฉันได้ยินว่า LINQ เป็นสีดำใหม่ดังนั้นนี่คือความพยายามของฉันที่ใช้ 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());
}

(หมายเหตุ: การใช้Randomคลาสทำให้สิ่งนี้ไม่เหมาะสมสำหรับความปลอดภัยใด ๆ ที่เกี่ยวข้องเช่นการสร้างรหัสผ่านหรือโทเค็นใช้RNGCryptoServiceProviderคลาสหากคุณต้องการตัวสร้างตัวเลขสุ่มที่รัดกุม)


24
@Alex: ฉันได้ทำการทดสอบอย่างรวดเร็วไม่กี่ครั้งและดูเหมือนว่าจะเพิ่มขึ้นในลักษณะเชิงเส้นเมื่อสร้างสตริงที่ยาวขึ้น ต้องบอกว่าคำตอบของแดนริกบีนั้นเร็วกว่าคำตอบนี้เกือบสองเท่า
LukeH

6
ดี. หากเกณฑ์ของคุณคือการใช้ linq และมีการเล่าเรื่องรหัสหมัดแล้วมันเป็นหัวเข่าของผึ้ง ทั้งการเล่าเรื่องรหัสและเส้นทางการดำเนินการจริงค่อนข้างไม่มีประสิทธิภาพและโดยอ้อม อย่าเข้าใจฉันผิดฉันเป็น hipster รหัสขนาดใหญ่ (ฉันรักหลาม) แต่มันก็เป็นเครื่องทองคำคนบ้านนอก
eremzeit

5
ในขณะที่เทคนิคนี้ตอบคำถามมันเป็นผลลัพธ์ที่ทำให้เข้าใจผิดมาก การสร้างตัวละครแบบสุ่ม 8 ตัวดูเหมือนจะมีผลลัพธ์มากมายในขณะที่สิ่งนี้จะให้ผลลัพธ์ที่แตกต่างกันถึง 2 พันล้าน และในทางปฏิบัติยิ่งน้อยลง คุณควรเพิ่มคำเตือน Big FAT เพื่อไม่ให้ใช้สิ่งนี้เพื่อความปลอดภัยที่เกี่ยวข้อง
CodesInChaos

41
@xaisoft: ตัวอักษรพิมพ์เล็กถูกทิ้งไว้เป็นแบบฝึกหัดสำหรับผู้อ่าน
dtb

15
บรรทัดต่อไปนี้คือหน่วยความจำ (และเวลา) ที่มีประสิทธิภาพมากกว่าที่ระบุreturn new string(Enumerable.Range(1, length).Select(_ => chars[random.Next(chars.Length)]).ToArray());
Tyson Williams

375
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);

ไม่หรูหราเท่าโซลูชัน Linq

(หมายเหตุ: การใช้Randomคลาสทำให้สิ่งนี้ไม่เหมาะสมสำหรับความปลอดภัยใด ๆ ที่เกี่ยวข้องเช่นการสร้างรหัสผ่านหรือโทเค็นใช้RNGCryptoServiceProviderคลาสหากคุณต้องการตัวสร้างตัวเลขสุ่มที่รัดกุม)


4
@Alex: นี่ไม่ใช่คำตอบที่เร็วที่สุด แต่เป็นคำตอบที่ "เร็วที่สุด" (เช่นคำตอบที่ควบคุมอักขระที่ใช้และความยาวของสตริง)
LukeH

2
@Alex: GetRandomFileNameโซลูชันของ Adam Porad นั้นเร็วกว่า แต่ไม่อนุญาตให้มีการควบคุมอักขระที่ใช้และความยาวสูงสุดที่เป็นไปได้คือ 11 chars Guidวิธีแก้ปัญหาของดักลาสเร็วมาก แต่ตัวละครถูก จำกัด ไว้ที่ A-F0-9 และความยาวสูงสุดที่ใช้ได้คือ 32 ตัวอักษร
ลุ

1
@ อดัม: ใช่คุณสามารถต่อยอดผลการโทรหลายครั้งGetRandomFileNameแต่ (a) คุณสูญเสียความได้เปรียบในการทำงานและ (b) รหัสของคุณจะซับซ้อนกว่านี้
ลุ

1
@xaisoft สร้างอินสแตนซ์ของคุณของวัตถุ Random () นอกวงของคุณ หากคุณสร้างอินสแตนซ์ของ Random () จำนวนมากในช่วงเวลาสั้น ๆ การเรียกไปที่. Next () จะส่งกลับค่าเดียวกับ Random () ใช้การเพาะเมล็ดตามเวลา
Dan Rigby

2
@xaisoft อย่าใช้คำตอบนี้เพื่อความปลอดภัยที่สำคัญเช่นรหัสผ่าน System.Randomไม่เหมาะสำหรับความปลอดภัย
CodesInChaos

333

อัปเดตตามความคิดเห็น การใช้งานดั้งเดิมสร้างขึ้น ah ~ 1.95% ของเวลาและอักขระที่เหลืออยู่ ~ 1.56% ของเวลา การอัปเดตจะสร้างตัวละครทั้งหมด ~ 1.61% ของเวลา

FRAMEWORK SUPPORT -. NET Core 3 (และแพลตฟอร์มในอนาคตที่รองรับ. NET Standard 2.1 หรือสูงกว่า) มีวิธีการเข้ารหัสแบบเสียงRandomNumberGenerator.GetInt32 ()เพื่อสร้างจำนวนเต็มแบบสุ่มภายในช่วงที่ต้องการ

ซึ่งแตกต่างจากบางส่วนของทางเลือกที่นำเสนอนี้เป็นเสียงที่เข้ารหัส

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 (RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider())
            {
                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();
        }
    }
}

อ้างอิงจากการสนทนาของทางเลือกที่นี่และปรับปรุง / แก้ไขตามความคิดเห็นด้านล่าง

นี่คือชุดทดสอบขนาดเล็กที่แสดงการกระจายตัวอักขระในเอาต์พุตเก่าและที่อัปเดต สำหรับการสนทนาเชิงลึกเกี่ยวกับการวิเคราะห์การสุ่มลองดู 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")}%");
            }
        }
    }
}

11
ดูเหมือนว่าวิธีการที่ถูกต้องสำหรับฉัน - ไม่ควรสร้างรหัสผ่านแบบสุ่มเกลือเอนโทรปีและอื่น ๆ โดยใช้ Random () ซึ่งเหมาะสำหรับความเร็วและสร้างลำดับของตัวเลขที่ซ้ำกันได้ RNGCryptoServiceProvider.GetNonZeroBytes () ในทางกลับกันจะสร้างลำดับตัวเลขที่ไม่สามารถทำซ้ำได้
mindplay.dk

25
ตัวอักษรมีความเอนเอียงเล็กน้อย (255% 62! = 0) แม้จะมีข้อบกพร่องเล็ก ๆ น้อย ๆ นี้มันเป็นทางออกที่ดีที่สุดที่นี่
CodesInChaos

13
โปรดทราบว่านี่ไม่ใช่เสียงถ้าคุณต้องการความแข็งแกร่งของการเข้ารหัสลับแบบไม่มีอคติ (และหากคุณไม่ต้องการสิ่งนั้นทำไมต้องใช้RNGCSPตั้งแต่แรก) การใช้ mod to index ในcharsอาร์เรย์หมายความว่าคุณจะได้รับผลลัพธ์แบบเอนเอียงเว้นแต่chars.Lengthจะเป็นตัวหาร 256
Luke

15
หนึ่งเป็นไปได้ที่จะลดอคติมากจะขอไบต์สุ่มการใช้งานแล้ว4*maxSize (UInt32)(BitConverter.ToInt32(data,4*i)% chars.Lengthฉันยังต้องการใช้แทนGetBytes และในที่สุดคุณสามารถเอาสายแรกที่GetNonZeroBytes GetNonZeroBytesคุณไม่ได้ใช้ผลลัพธ์
CodesInChaos

14
เรื่องสนุก: AZ az 0-9 คือ 62 ตัวอักษร ผู้คนกำลังชี้อคติตัวอักษรเนื่องจาก 256% 62! = 0 รหัสวิดีโอของ YouTube คือ AZ az 0-9 และ "-" และ "_" ซึ่งสร้างอักขระที่เป็นไปได้ 64 ตัวซึ่งแบ่งออกเป็น 256 เท่า ๆ กัน เหตุบังเอิญ? ผมคิดว่าไม่! :)
qJake

200

โซลูชันที่ 1 - 'ช่วง' ที่ใหญ่ที่สุดด้วยความยาวที่ยืดหยุ่นที่สุด

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

โซลูชันนี้มีช่วงมากกว่าการใช้ GUID เนื่องจาก GUID มีบิตคงที่สองชุดที่เหมือนกันเสมอและดังนั้นจึงไม่สุ่มตัวอย่างเช่นอักขระ 13 ตัวในฐานสิบหกจะเป็น "4" เสมอ - อย่างน้อยในรุ่น 6 GUID

โซลูชันนี้ยังช่วยให้คุณสร้างสตริงที่มีความยาวเท่าใดก็ได้

โซลูชันที่ 2 - โค้ดหนึ่งบรรทัด - ดีมากถึง 22 ตัวอักษร

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

คุณไม่สามารถสร้างสตริงได้ตราบใดที่โซลูชัน 1และสตริงไม่มีช่วงเดียวกันเนื่องจากบิตคงที่ใน GUID's แต่ในหลาย ๆ กรณีสิ่งนี้จะทำงานได้

โซลูชันที่ 3 - รหัสน้อยกว่าเล็กน้อย

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

ส่วนใหญ่เก็บไว้ที่นี่เพื่อจุดประสงค์ทางประวัติศาสตร์ มันใช้รหัสน้อยกว่าเล็กน้อยซึ่งถึงแม้ว่าจะเป็นค่าใช้จ่ายในการมีช่วงที่น้อยลงเพราะใช้เลขฐานสิบหกแทน base64 จะใช้อักขระจำนวนมากเพื่อแสดงช่วงเดียวกันเมื่อเทียบกับโซลูชันอื่น

ซึ่งหมายถึงโอกาสที่จะเกิดการชนมากขึ้น - ทดสอบด้วย 100,000 ตัวอักษรจาก 8 ตัวอักขระที่สร้างขึ้นซ้ำหนึ่งรายการ


22
คุณสร้างสำเนาซ้ำจริงหรือ น่าแปลกที่ 5,316,911,983,139,663,491,615,228,241,121,400,000 การรวมกันของ GUID ที่เป็นไปได้
Alex

73
@Alex: เขาย่อ GUID ให้เหลือ 8 ตัวอักษรดังนั้นความน่าจะเป็นของการชนจะสูงกว่า GUID มาก
dtb

23
ไม่มีใครสามารถชื่นชมสิ่งนี้นอกจาก nerds :) ใช่คุณพูดถูกขีด จำกัด 8 ขีด จำกัด ถ่านสร้างความแตกต่าง
Alex

31
Guid.NewGuid (). ToString ("n") จะป้องกันการขีดกลางไม่จำเป็นต้องมีการแทนที่ Replace () แต่ควรจะกล่าวถึง GUID นั้นมีเพียง 0-9 และ AF จำนวนชุดค่าผสมคือ "ดีพอ" แต่ไม่มีที่ไหนใกล้เคียงกับสตริงสุ่มตัวอักษรและตัวเลขที่แท้จริง โอกาสของการชนคือ 1: 4,294,967,296 - เหมือนกับจำนวนเต็ม 32 บิตแบบสุ่ม
richardtallent

32
1) GUID ได้รับการออกแบบให้ไม่ซ้ำกันไม่ใช่แบบสุ่ม แม้ว่า windows เวอร์ชันปัจจุบันจะสร้าง V4 GUID ซึ่งเป็นแบบสุ่ม แต่ก็ไม่รับประกัน ตัวอย่างเช่นรุ่นที่เก่ากว่าของ windows ใช้ V1 GUID ที่คุณอาจจะล้มเหลว 2) เพียงแค่ใช้ตัวอักษรฐานสิบหกลดคุณภาพของสตริงสุ่มอย่างมีนัยสำคัญ จาก 47 ถึง 32 บิต 3) ผู้คนประเมินความน่าจะเป็นที่จะเกิดการชนน้อยเกินไปเนื่องจากพวกเขาให้แต่ละคู่ หากคุณสร้างค่า 100k 32 บิตคุณอาจมีการชนหนึ่งครั้ง ดูปัญหาวันเกิด
CodesInChaos

72

นี่คือตัวอย่างที่ฉันขโมยมาจากตัวอย่างของ Sam Allen ที่Dot Net Perls

หากคุณต้องการเพียง 8 ตัวอักษรให้ใช้ Path.GetRandomFileName () ในเนมสเปซ System.IO แซมบอกว่าใช้วิธี "Path.GetRandomFileName ที่นี่บางครั้งเหนือกว่าเพราะใช้ RNGCryptoServiceProvider สำหรับการสุ่มที่ดีกว่าอย่างไรก็ตามมัน จำกัด เพียง 11 ตัวอักษรแบบสุ่ม"

GetRandomFileName ส่งคืนสตริงอักขระ 12 ตัวเสมอโดยมีจุดที่อักขระ 9 ดังนั้นคุณจะต้องตัดช่วงเวลา (เนื่องจากไม่ใช่การสุ่ม) จากนั้นจึงนำอักขระ 8 ตัวจากสตริง ที่จริงแล้วคุณสามารถใช้อักขระ 8 ตัวแรกและไม่ต้องกังวลกับช่วงเวลานั้น

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

PS: ขอบคุณแซม


27
มันใช้งานได้ดี ฉันวิ่งผ่าน 100,000 ซ้ำและไม่เคยมีชื่อซ้ำกัน แต่ผมไม่หาคำหยาบคายหลาย (ภาษาอังกฤษ) จะไม่ได้คิดเรื่องนี้ยกเว้นหนึ่งในคนแรกในรายการมี F *** ในนั้น หัวขึ้นถ้าคุณใช้สิ่งนี้เพื่อสิ่งที่ผู้ใช้จะเห็น
techturtle

3
@ techturtle ขอบคุณสำหรับคำเตือน ฉันคิดว่ามีความเสี่ยงสำหรับคำหยาบคายกับการสร้างสตริงแบบสุ่มใด ๆ ที่ใช้ตัวอักษรทั้งหมดในตัวอักษร
Adam Porad

ดีและเรียบง่าย แต่ไม่ดีสำหรับสายยาว ... โหวตให้เคล็ดลับดีนี้
Maher Abuthraa

วิธีนี้ดูเหมือนว่าจะส่งกลับเฉพาะสตริงตัวอักษรและตัวเลขตัวเล็ก
jaybro

2
มีคำหยาบคายเป็นครั้งคราว แต่ถ้าคุณใช้มันนานพอแล้วในที่สุดมันก็จะเขียนเช็คสเปียร์ (อายุการใช้งานเพียงไม่กี่แห่งของจักรวาล :)
Slothario

38

เป้าหมายหลักของรหัสของฉันคือ:

  1. การแจกแจงของสตริงนั้นเกือบจะเหมือนกัน (ไม่ต้องสนใจค่าเบี่ยงเบนเล็ก ๆ น้อย ๆ ตราบใดที่มันเล็ก)
  2. มันส่งออกมากกว่าสองสามพันล้านสายสำหรับแต่ละชุดอาร์กิวเมนต์ การสร้างสตริงอักขระ 8 ตัว (~ 47 บิตของเอนโทรปี) นั้นไม่มีความหมายหาก PRNG ของคุณสร้างค่าต่างกันเพียง 2 พันล้าน (31 บิตของเอนโทรปี)
  3. มีความปลอดภัยเนื่องจากฉันคาดว่าผู้คนจะใช้รหัสผ่านนี้หรือโทเค็นความปลอดภัยอื่น ๆ

คุณสมบัติแรกสามารถทำได้โดยการใช้โมดูโลขนาด 64 บิต สำหรับตัวอักษรขนาดเล็ก (เช่นตัวอักษร 62 ตัวจากคำถาม) สิ่งนี้นำไปสู่การมีอคติเล็กน้อย ที่สองและสามคุณสมบัติที่จะประสบความสำเร็จโดยใช้แทนRNGCryptoServiceProviderSystem.Random

using System;
using System.Security.Cryptography;

public static string GetRandomAlphanumericString(int length)
{
    const string alphanumericCharacters =
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
        "abcdefghijklmnopqrstuvwxyz" +
        "0123456789";
    return GetRandomString(length, alphanumericCharacters);
}

public static string GetRandomString(int length, IEnumerable<char> characterSet)
{
    if (length < 0)
        throw new ArgumentException("length must not be negative", "length");
    if (length > int.MaxValue / 8) // 250 million chars ought to be enough for anybody
        throw new ArgumentException("length is too big", "length");
    if (characterSet == null)
        throw new ArgumentNullException("characterSet");
    var characterArray = characterSet.Distinct().ToArray();
    if (characterArray.Length == 0)
        throw new ArgumentException("characterSet must not be empty", "characterSet");

    var bytes = new byte[length * 8];
    var result = new char[length];
    using (var cryptoProvider = new RNGCryptoServiceProvider())
    {
        cryptoProvider.GetBytes(bytes);
    }
    for (int i = 0; i < length; i++)
    {
        ulong value = BitConverter.ToUInt64(bytes, i * 8);
        result[i] = characterArray[value % (uint)characterArray.Length];
    }
    return new string(result);
}

1
ไม่มีทางแยกด้วย 64 x Z และ Math.Pow (2, Y) ดังนั้นในขณะที่การสร้างตัวเลขที่มากขึ้นจะช่วยลดอคติ แต่ก็ไม่ได้กำจัดมัน ฉันอัปเดตคำตอบของฉันด้านล่างแนวทางของฉันคือการทิ้งอินพุตแบบสุ่มและแทนที่ด้วยค่าอื่น
ทอดด์

@Todd ฉันรู้ว่ามันไม่ได้กำจัดอคติ แต่ฉันเลือกความเรียบง่ายของการแก้ปัญหานี้มากกว่าการกำจัดอคติที่ไม่เกี่ยวข้องในทางปฏิบัติ
CodesInChaos

ฉันเห็นด้วยกับกรณีส่วนใหญ่อาจไม่เกี่ยวข้องกับการปฏิบัติจริง แต่ตอนนี้ฉันได้อัปเดตของฉันให้เป็นทั้งแบบสุ่มและปลอดภัยกว่าของคุณเล็กน้อย โอเพนซอร์สทั้งหมดสำหรับทุกคนที่จะแบ่งปัน ใช่ฉันเสียเวลามากเกินไปในเรื่องนี้ ...
ทอดด์

หากเราใช้ผู้ให้บริการ RNG เรามีวิธีหลีกเลี่ยงอคติในทางทฤษฎีหรือไม่? ฉันไม่แน่ใจ ... ถ้าทอดด์หมายถึงวิธีการที่เขาสร้างหมายเลขสุ่มเพิ่มเติม (เมื่อเราอยู่ในเขตอคติ) ก็อาจเป็นไปได้ว่าสมมติฐานผิด RNG มีการแจกแจงเชิงเส้นเกือบทุกค่าที่สร้างขึ้นโดยเฉลี่ย แต่ไม่ได้หมายความว่าเราจะไม่มีสหสัมพันธ์ในท้องถิ่นระหว่างไบต์ที่สร้างขึ้น ดังนั้นไบต์เพิ่มเติมสำหรับเขตอคติจึงยังสามารถให้อคติกับเราได้ แต่เนื่องจากเหตุผลที่แตกต่างกัน เป็นไปได้ว่าอคตินี้จะเล็กมาก แต่ในกรณีนี้การเพิ่มจำนวนไบต์ที่สร้างขึ้นทั้งหมดเป็นวิธีที่ตรงไปตรงมามากขึ้น
Maxim

1
@ Maxim คุณสามารถใช้การปฏิเสธเพื่อกำจัดอคติได้อย่างสมบูรณ์ (สมมติว่าเครื่องกำเนิดไฟฟ้าต้นแบบสุ่มอย่างสมบูรณ์) ในการแลกเปลี่ยนรหัสอาจมีความยาวตามอำเภอใจ (โดยมีความเป็นไปได้น้อยมากในการอธิบาย)
CodesInChaos

32

ง่ายที่สุด:

public static string GetRandomAlphaNumeric()
{
    return Path.GetRandomFileName().Replace(".", "").Substring(0, 8);
}

คุณจะได้ประสิทธิภาพที่ดีขึ้นถ้าคุณเขียนโค้ด char char และพึ่งพาSystem.Random:

public static string GetRandomAlphaNumeric()
{
    var chars = "abcdefghijklmnopqrstuvwxyz0123456789";
    return new string(chars.Select(c => chars[random.Next(chars.Length)]).Take(8).ToArray());
}

หากคุณกังวลว่าตัวอักษรภาษาอังกฤษสามารถเปลี่ยนแปลงได้บางครั้งและคุณอาจสูญเสียธุรกิจคุณสามารถหลีกเลี่ยงการเข้ารหัสยาก แต่ควรทำงานได้แย่ลงเล็กน้อย (เทียบเท่ากับPath.GetRandomFileNameวิธีการ)

public static string GetRandomAlphaNumeric()
{
    var chars = 'a'.To('z').Concat('0'.To('9')).ToList();
    return new string(chars.Select(c => chars[random.Next(chars.Length)]).Take(8).ToArray());
}

public static IEnumerable<char> To(this char start, char end)
{
    if (end < start)
        throw new ArgumentOutOfRangeException("the end char should not be less than start char", innerException: null);
    return Enumerable.Range(start, end - start + 1).Select(i => (char)i);
}

สองวิธีสุดท้ายนั้นดูดีกว่าถ้าคุณสามารถทำให้มันเป็นวิธีการขยายในSystem.Randomอินสแตนซ์


1
การใช้งานchars.Selectมีขนาดใหญ่น่าเกลียดเนื่องจากต้องอาศัยขนาดเอาต์พุตเป็นขนาดตัวอักษรมากที่สุด
CodesInChaos

@CodesInChaos ฉันไม่แน่ใจว่าฉันเข้าใจคุณ คุณหมายถึง'a'.To('z')วิธีการหรือไม่
nawfal

1
1) chars.Select().Take (n) chars.Count >= n`ทำงานเฉพาะถ้า การเลือกลำดับที่คุณไม่ได้ใช้จริง ๆ นั้นค่อนข้างใช้งานง่ายโดยเฉพาะกับข้อจำกัดความยาวโดยนัย ฉันอยากจะใช้หรือEnumerable.Range Enumerable.Repeat2) ข้อผิดพลาด "ถ่านท้ายที่สุดควรจะน้อยกว่าถ่านเริ่มต้น" เป็นรอบผิดวิธี / notหายไป
CodesInChaos

@CodesInChaos แต่ในกรณีของฉันเป็นวิธีchars.Count > nนอกจากนี้ฉันไม่ได้รับส่วนที่ไม่ได้ใช้งานง่าย นั่นทำให้ทุกการใช้งานTakeไม่ได้ใช้ไม่ได้หรือไม่ ฉันไม่เชื่อหรอก ขอบคุณสำหรับการชี้ที่พิมพ์ผิด
nawfal

4
สิ่งนี้ให้ความสำคัญกับtheDailyWTF.comเป็นบทความ CodeSOD

22

การเปรียบเทียบประสิทธิภาพของคำตอบที่หลากหลายในหัวข้อนี้:

วิธีการและการติดตั้ง

// what's available
public static string possibleChars = "abcdefghijklmnopqrstuvwxyz";
// optimized (?) what's available
public static char[] possibleCharsArray = possibleChars.ToCharArray();
// optimized (precalculated) count
public static int possibleCharsAvailable = possibleChars.Length;
// shared randomization thingy
public static Random random = new Random();


// http://stackoverflow.com/a/1344242/1037948
public string LinqIsTheNewBlack(int num) {
    return new string(
    Enumerable.Repeat(possibleCharsArray, num)
              .Select(s => s[random.Next(s.Length)])
              .ToArray());
}

// http://stackoverflow.com/a/1344258/1037948
public string ForLoop(int num) {
    var result = new char[num];
    while(num-- > 0) {
        result[num] = possibleCharsArray[random.Next(possibleCharsAvailable)];
    }
    return new string(result);
}

public string ForLoopNonOptimized(int num) {
    var result = new char[num];
    while(num-- > 0) {
        result[num] = possibleChars[random.Next(possibleChars.Length)];
    }
    return new string(result);
}

public string Repeat(int num) {
    return new string(new char[num].Select(o => possibleCharsArray[random.Next(possibleCharsAvailable)]).ToArray());
}

// http://stackoverflow.com/a/1518495/1037948
public string GenerateRandomString(int num) {
  var rBytes = new byte[num];
  random.NextBytes(rBytes);
  var rName = new char[num];
  while(num-- > 0)
    rName[num] = possibleCharsArray[rBytes[num] % possibleCharsAvailable];
  return new string(rName);
}

//SecureFastRandom - or SolidSwiftRandom
static string GenerateRandomString(int Length) //Configurable output string length
{
    byte[] rBytes = new byte[Length]; 
    char[] rName = new char[Length];
    SolidSwiftRandom.GetNextBytesWithMax(rBytes, biasZone);
    for (var i = 0; i < Length; i++)
    {
        rName[i] = charSet[rBytes[i] % charSet.Length];
    }
    return new string(rName);
}

ผล

ทดสอบใน LinqPad สำหรับขนาดสตริง 10 ให้สร้าง:

  • จาก Linq = chdgmevhcy [10]
  • จาก Loop = gtnoaryhxr [10]
  • จากเลือก = rsndbztyby [10]
  • จาก GenerateRandomString = owyefjjakj [10]
  • จาก SecureFastRandom = VzougLYHYP [10]
  • จาก SecureFastRandom-NoCache = oVQXNGmO1S [10]

และตัวเลขผลการดำเนินงานมีแนวโน้มที่จะแตกต่างกันเล็กน้อยเป็นครั้งคราวNonOptimizedเป็นจริงได้เร็วขึ้นและบางครั้งForLoopและGenerateRandomStringสลับคนที่อยู่ในนำ

  • LinqIsTheNewBlack (10000x) = 96762 เห็บที่ผ่านไป (9.6762 ms)
  • ForLoop (10000x) = 28970 เห็บที่ผ่านไป (2.897 ms)
  • ForLoopNonOptimized (10000x) = 33336 เห็บที่ผ่านไป (3.3336 ms)
  • ทำซ้ำ (10000x) = 78547 เห็บที่ผ่านไป (7.8547 ms)
  • GenerateRandomString (10000x) = 27416 เห็บที่ผ่านไป (2.7416 ms)
  • SecureFastRandom (10000x) = 13176 เห็บที่ผ่านไป (5ms) ต่ำสุด [เครื่องที่แตกต่าง]
  • SecureFastRandom-NoCache (10000x) = 39541 เห็บที่ผ่านไป (17ms) ต่ำสุด [เครื่องที่แตกต่าง]

3
จะน่าสนใจที่จะรู้ว่าคนที่สร้างคู่กัน
รีเบคก้า

@Junto - เพื่อค้นหาว่าผลลัพธ์ใดที่ซ้ำกันอย่างvar many = 10000; Assert.AreEqual(many, new bool[many].Select(o => EachRandomizingMethod(10)).Distinct().Count());ที่คุณแทนที่EachRandomizingMethodด้วย ... แต่ละวิธี
drzaus

19

โค้ดหนึ่งบรรทัดMembership.GeneratePassword()ใช้กลอุบายได้ :)

นี่คือตัวอย่างสำหรับสิ่งเดียวกัน


1
ดูเหมือนว่า Microsoft จะย้ายลิงก์ไปแล้ว .. ตัวอย่างรหัสอื่นอยู่ที่ msdn.microsoft.com/en-us/library/ms152017หรือaspnet.4guysfromrolla.com/demos/GeneratePassword.aspxหรือdeveloper.xamarin.com/api/member/
Pooran

ฉันคิดว่า แต่ไม่สามารถกำจัดตัวอักษรและตัวเลขที่ไม่สามารถกำจัดได้เนื่องจากอาร์กิวเมนต์ที่ 2 คืออักขระที่ไม่ใช่ตัวอักษรขั้นต่ำ
ozzy432836

13

รหัสที่เขียนโดย Eric J. ค่อนข้างเลอะเทอะ (ค่อนข้างชัดเจนว่าเมื่อ 6 ปีที่แล้ว ... เขาอาจจะไม่เขียนโค้ดนั้นในวันนี้) และมีปัญหาบางอย่าง

แตกต่างจากทางเลือกที่นำเสนอบางอย่างอันนี้เป็นเสียง cryptographically

ไม่จริง ... มีอคติในรหัสผ่าน (ตามที่เขียนไว้ในความคิดเห็น) bcdefghมีความเป็นไปได้มากกว่าคนอื่นเล็กน้อย ( aไม่ใช่เพราะGetNonZeroBytesมันไม่ได้สร้างไบต์ด้วยค่าศูนย์ดังนั้นความเอนเอียง สำหรับความaสมดุลนั้น) ดังนั้นจึงไม่มีเสียงที่เข้ารหัสลับ

สิ่งนี้ควรแก้ไขปัญหาทั้งหมด

public static string GetUniqueKey(int size = 6, string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
{
    using (var crypto = new RNGCryptoServiceProvider())
    {
        var data = new byte[size];

        // If chars.Length isn't a power of 2 then there is a bias if
        // we simply use the modulus operator. The first characters of
        // chars will be more probable than the last ones.

        // buffer used if we encounter an unusable random byte. We will
        // regenerate it in this buffer
        byte[] smallBuffer = null;

        // Maximum random number that can be used without introducing a
        // bias
        int maxRandom = byte.MaxValue - ((byte.MaxValue + 1) % chars.Length);

        crypto.GetBytes(data);

        var result = new char[size];

        for (int i = 0; i < size; i++)
        {
            byte v = data[i];

            while (v > maxRandom)
            {
                if (smallBuffer == null)
                {
                    smallBuffer = new byte[1];
                }

                crypto.GetBytes(smallBuffer);
                v = smallBuffer[0];
            }

            result[i] = chars[v % chars.Length];
        }

        return new string(result);
    }
}

7

นอกจากนี้เรายังใช้สตริงที่กำหนดเองแบบสุ่ม แต่เรานำมาใช้เป็นตัวช่วยของสตริงเพื่อให้มีความยืดหยุ่น ...

public static string Random(this string chars, int length = 8)
{
    var randomString = new StringBuilder();
    var random = new Random();

    for (int i = 0; i < length; i++)
        randomString.Append(chars[random.Next(chars.Length)]);

    return randomString.ToString();
}

การใช้

var random = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".Random();

หรือ

var random = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".Random(16);

7

รหัสบรรทัดเดียวที่เรียบง่ายใช้งานได้สำหรับฉัน :)

string  random = string.Join("", Guid.NewGuid().ToString("n").Take(8).Select(o => o));

Response.Write(random.ToUpper());
Response.Write(random.ToLower());

เพื่อขยายในเรื่องนี้สำหรับสตริงความยาวใด ๆ

    public static string RandomString(int length)
    {
        //length = length < 0 ? length * -1 : length;
        var str = "";

        do 
        {
            str += Guid.NewGuid().ToString().Replace("-", "");
        }

        while (length > str.Length);

        return str.Substring(0, length);
    }

ฉันชอบวิธี guid ด้วย - รู้สึกเบามาก
ozzy432836

6

อีกทางเลือกหนึ่งคือการใช้ Linq และการสุ่มตัวอักษรรวมเข้าไปใน stringbuilder

var chars = "abcdefghijklmnopqrstuvwxyz123456789".ToArray();
string pw = Enumerable.Range(0, passwordLength)
                      .Aggregate(
                          new StringBuilder(),
                          (sb, n) => sb.Append((chars[random.Next(chars.Length)])),
                          sb => sb.ToString());

6

คำถาม:ทำไมฉันต้องเสียเวลาโดยใช้Enumerable.Rangeแทนที่จะพิมพ์"ABCDEFGHJKLMNOPQRSTUVWXYZ0123456789"?

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

public class Test
{
    public static void Main()
    {
        var randomCharacters = GetRandomCharacters(8, true);
        Console.WriteLine(new string(randomCharacters.ToArray()));
    }

    private static List<char> getAvailableRandomCharacters(bool includeLowerCase)
    {
        var integers = Enumerable.Empty<int>();
        integers = integers.Concat(Enumerable.Range('A', 26));
        integers = integers.Concat(Enumerable.Range('0', 10));

        if ( includeLowerCase )
            integers = integers.Concat(Enumerable.Range('a', 26));

        return integers.Select(i => (char)i).ToList();
    }

    public static IEnumerable<char> GetRandomCharacters(int count, bool includeLowerCase)
    {
        var characters = getAvailableRandomCharacters(includeLowerCase);
        var random = new Random();
        var result = Enumerable.Range(0, count)
            .Select(_ => characters[random.Next(characters.Count)]);

        return result;
    }
}

คำตอบ:สตริงมายากลคือ BAD ทุกคนสังเกตเห็นว่าไม่มี "I " ในสายอักขระของฉันที่ด้านบนหรือไม่ แม่ของฉันสอนให้ฉันไม่ใช้สายเวทด้วยเหตุผลนี้ ...

nb 1: อื่น ๆ อีกมากมายเช่น @dtb พูดว่าอย่าใช้System.Randomถ้าคุณต้องการความปลอดภัยของการเข้ารหัส ...

nb 2: คำตอบนี้ไม่ได้มีประสิทธิภาพมากที่สุดหรือสั้นที่สุด แต่ฉันต้องการพื้นที่เพื่อแยกคำตอบออกจากคำถาม จุดประสงค์ของคำตอบของฉันคือการเตือนต่อสายเวทมากกว่าที่จะให้คำตอบที่แปลกใหม่


ทำไมฉันถึงสนใจว่าไม่มี " I?"
Christine

1
ตัวเลข (กรณีละเว้น) [A-Z0-9]เป็น ถ้าโดยบังเอิญสตริงสุ่มของคุณครอบคลุม[A-HJ-Z0-9]เฉพาะผลลัพธ์ไม่ครอบคลุมช่วงที่อนุญาตเต็มซึ่งอาจเป็นปัญหาได้
Wai Ha Lee

นั่นจะเป็นปัญหาได้อย่างไร Iดังนั้นมันไม่ได้มี เป็นเพราะมีตัวละครน้อยลงและทำให้ง่ายขึ้นในการถอดรหัส? สถิติของรหัสผ่านที่ถอดรหัสได้ที่มี 35 ตัวอักษรในช่วงที่มากกว่า 36 คืออะไรฉันคิดว่าฉันค่อนข้างเสี่ยงที่จะ ... หรือเพียงแค่พิสูจน์ช่วงตัวละคร ... แต่นั่นคือฉัน ฉันหมายความว่าไม่ต้องเป็นรูก้นฉันแค่พูด บางครั้งฉันคิดว่าโปรแกรมเมอร์มีแนวโน้มไปเส้นทางซับซ้อนพิเศษเพื่อประโยชน์ของซับซ้อน
Christine

1
มันลึกลงไปในกรณีการใช้งาน มันเป็นเรื่องธรรมดามากที่จะไม่รวมตัวอักษรเช่นIและOจากประเภทนี้ของสตริงสุ่มเพื่อหลีกเลี่ยงความสับสนมนุษย์พวกเขาด้วยและ1 0หากคุณไม่สนใจว่าจะมีสตริงที่มนุษย์อ่านได้ดี แต่ถ้ามันเป็นสิ่งที่บางคนอาจต้องพิมพ์มันก็ฉลาดที่จะลบตัวอักษรเหล่านั้นออก
Chris Pratt

5

โซลูชันของ DTB รุ่นที่สะอาดขึ้นเล็กน้อย

    var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    var random = new Random();
    var list = Enumerable.Repeat(0, 8).Select(x=>chars[random.Next(chars.Length)]);
    return string.Join("", list);

การตั้งค่าสไตล์ของคุณอาจแตกต่างกันไป


นี่เป็นสิ่งที่ดีกว่าและมีประสิทธิภาพมากกว่าคำตอบที่ยอมรับ
ลิ่ม

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

5

หลังจากทบทวนคำตอบอื่น ๆ และพิจารณาความคิดเห็นของ CodeInChaos พร้อมกับคำตอบของ CodeInChaos ที่ยังลำเอียง (แม้ว่าจะน้อยกว่า) คำตอบฉันคิดว่าจำเป็นต้องใช้วิธีการตัดและแปะสุดท้าย ดังนั้นในขณะที่อัปเดตคำตอบของฉันฉันตัดสินใจที่จะออกไปทั้งหมด

สำหรับรุ่นล่าสุดของรหัสนี้, กรุณาเยี่ยมชมที่เก็บปรอทใหม่บน Bitbucket: https://bitbucket.org/merarischroeder/secureswiftrandom ผมขอแนะนำให้คุณคัดลอกและวางโค้ดจาก: https://bitbucket.org/merarischroeder/secureswiftrandom/src/6c14b874f34a3f6576b0213379ecdf0ffc7496ea/Code/Alivate.SolidSwiftRandom/SolidSwiftRandom.cs?at=default&fileviewer=file-view-default (ให้แน่ใจว่าคุณคลิก ปุ่ม Raw เพื่อให้ง่ายต่อการคัดลอกและทำให้แน่ใจว่าคุณมีเวอร์ชั่นล่าสุดฉันคิดว่าลิงก์นี้ไปยังรหัสรุ่นที่ระบุไม่ใช่รุ่นล่าสุด)

บันทึกล่าสุด:

  1. เกี่ยวข้องกับคำตอบอื่น ๆ - ถ้าคุณทราบความยาวของผลลัพธ์คุณไม่จำเป็นต้องใช้ StringBuilder และเมื่อใช้ ToCharArray สิ่งนี้จะสร้างและเติมอาร์เรย์ (คุณไม่จำเป็นต้องสร้างอาร์เรย์ว่างก่อน)
  2. เกี่ยวข้องกับคำตอบอื่น ๆ - คุณควรใช้ NextBytes แทนการใช้ทีละตัวเพื่อประสิทธิภาพ
  3. ในทางเทคนิคคุณสามารถตรึงอาร์เรย์ไบต์เพื่อให้เข้าถึงได้เร็วขึ้น .. โดยปกติแล้วจะคุ้มค่าเมื่อคุณวนซ้ำมากกว่า 6-8 ครั้งในอาร์เรย์ไบต์ (ไม่ทำที่นี่)
  4. การใช้RNGCryptoServiceProvider เพื่อการสุ่มที่ดีที่สุด
  5. การใช้แคชของบัฟเฟอร์ข้อมูลแบบสุ่มขนาด 1MB การเปรียบเทียบแสดงให้เห็นว่าความเร็วในการเข้าถึงข้อมูลไบต์เดียวแคชนั้นเร็วกว่า ~ 1000x โดยใช้เวลา 9ms มากกว่า 1MB เทียบกับ 989ms สำหรับการทำสำเนา
  6. เพิ่มประสิทธิภาพการปฏิเสธโซนอคติภายในคลาสใหม่ของฉัน

จบการแก้ไขคำถาม:

static char[] charSet =  "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".ToCharArray();
static int byteSize = 256; //Labelling convenience
static int biasZone = byteSize - (byteSize % charSet.Length);
public string GenerateRandomString(int Length) //Configurable output string length
{
    byte[] rBytes = new byte[Length]; //Do as much before and after lock as possible
    char[] rName = new char[Length];
    SecureFastRandom.GetNextBytesMax(rBytes, biasZone);
    for (var i = 0; i < Length; i++)
    {
        rName[i] = charSet[rBytes[i] % charSet.Length];
    }
    return new string(rName);
}

แต่คุณต้องการคลาสใหม่ (ยังไม่ทดลอง) ของฉัน:

/// <summary>
/// My benchmarking showed that for RNGCryptoServiceProvider:
/// 1. There is negligable benefit of sharing RNGCryptoServiceProvider object reference 
/// 2. Initial GetBytes takes 2ms, and an initial read of 1MB takes 3ms (starting to rise, but still negligable)
/// 2. Cached is ~1000x faster for single byte at a time - taking 9ms over 1MB vs 989ms for uncached
/// </summary>
class SecureFastRandom
{
    static byte[] byteCache = new byte[1000000]; //My benchmark showed that an initial read takes 2ms, and an initial read of this size takes 3ms (starting to raise)
    static int lastPosition = 0;
    static int remaining = 0;

    /// <summary>
    /// Static direct uncached access to the RNGCryptoServiceProvider GetBytes function
    /// </summary>
    /// <param name="buffer"></param>
    public static void DirectGetBytes(byte[] buffer)
    {
        using (var r = new RNGCryptoServiceProvider())
        {
            r.GetBytes(buffer);
        }
    }

    /// <summary>
    /// Main expected method to be called by user. Underlying random data is cached from RNGCryptoServiceProvider for best performance
    /// </summary>
    /// <param name="buffer"></param>
    public static void GetBytes(byte[] buffer)
    {
        if (buffer.Length > byteCache.Length)
        {
            DirectGetBytes(buffer);
            return;
        }

        lock (byteCache)
        {
            if (buffer.Length > remaining)
            {
                DirectGetBytes(byteCache);
                lastPosition = 0;
                remaining = byteCache.Length;
            }

            Buffer.BlockCopy(byteCache, lastPosition, buffer, 0, buffer.Length);
            lastPosition += buffer.Length;
            remaining -= buffer.Length;
        }
    }

    /// <summary>
    /// Return a single byte from the cache of random data.
    /// </summary>
    /// <returns></returns>
    public static byte GetByte()
    {
        lock (byteCache)
        {
            return UnsafeGetByte();
        }
    }

    /// <summary>
    /// Shared with public GetByte and GetBytesWithMax, and not locked to reduce lock/unlocking in loops. Must be called within lock of byteCache.
    /// </summary>
    /// <returns></returns>
    static byte UnsafeGetByte()
    {
        if (1 > remaining)
        {
            DirectGetBytes(byteCache);
            lastPosition = 0;
            remaining = byteCache.Length;
        }

        lastPosition++;
        remaining--;
        return byteCache[lastPosition - 1];
    }

    /// <summary>
    /// Rejects bytes which are equal to or greater than max. This is useful for ensuring there is no bias when you are modulating with a non power of 2 number.
    /// </summary>
    /// <param name="buffer"></param>
    /// <param name="max"></param>
    public static void GetBytesWithMax(byte[] buffer, byte max)
    {
        if (buffer.Length > byteCache.Length / 2) //No point caching for larger sizes
        {
            DirectGetBytes(buffer);

            lock (byteCache)
            {
                UnsafeCheckBytesMax(buffer, max);
            }
        }
        else
        {
            lock (byteCache)
            {
                if (buffer.Length > remaining) //Recache if not enough remaining, discarding remaining - too much work to join two blocks
                    DirectGetBytes(byteCache);

                Buffer.BlockCopy(byteCache, lastPosition, buffer, 0, buffer.Length);
                lastPosition += buffer.Length;
                remaining -= buffer.Length;

                UnsafeCheckBytesMax(buffer, max);
            }
        }
    }

    /// <summary>
    /// Checks buffer for bytes equal and above max. Must be called within lock of byteCache.
    /// </summary>
    /// <param name="buffer"></param>
    /// <param name="max"></param>
    static void UnsafeCheckBytesMax(byte[] buffer, byte max)
    {
        for (int i = 0; i < buffer.Length; i++)
        {
            while (buffer[i] >= max)
                buffer[i] = UnsafeGetByte(); //Replace all bytes which are equal or above max
        }
    }
}

สำหรับประวัติ - โซลูชันที่เก่ากว่าของฉันสำหรับคำตอบนี้ใช้วัตถุสุ่ม:

    private static char[] charSet =
      "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".ToCharArray();

    static rGen = new Random(); //Must share, because the clock seed only has Ticks (~10ms) resolution, yet lock has only 20-50ns delay.
    static int byteSize = 256; //Labelling convenience
    static int biasZone = byteSize - (byteSize % charSet.Length);
    static bool SlightlyMoreSecurityNeeded = true; //Configuration - needs to be true, if more security is desired and if charSet.Length is not divisible by 2^X.
    public string GenerateRandomString(int Length) //Configurable output string length
    {
      byte[] rBytes = new byte[Length]; //Do as much before and after lock as possible
      char[] rName = new char[Length];
      lock (rGen) //~20-50ns
      {
          rGen.NextBytes(rBytes);

          for (int i = 0; i < Length; i++)
          {
              while (SlightlyMoreSecurityNeeded && rBytes[i] >= biasZone) //Secure against 1/5 increased bias of index[0-7] values against others. Note: Must exclude where it == biasZone (that is >=), otherwise there's still a bias on index 0.
                  rBytes[i] = rGen.NextByte();
              rName[i] = charSet[rBytes[i] % charSet.Length];
          }
      }
      return new string(rName);
    }

ประสิทธิภาพ:

  1. SecureFastRandom - ครั้งแรกที่ทำงานครั้งเดียว = ~ 9-33ms มองไม่เห็น อย่างต่อเนื่อง : 5ms (บางครั้งมันจะสูงถึง 13ms) มากกว่า 10,000 รอบโดยมีการทำซ้ำโดยเฉลี่ยเพียงครั้งเดียว = 1.5 microseconds . หมายเหตุ: ต้องการโดยทั่วไป 2 แต่บางครั้งอาจมีการรีเฟรชแคชสูงสุด 8 ครั้งขึ้นอยู่กับจำนวนไบต์เดียวเกินกว่าโซนอคติ
  2. สุ่ม - ครั้งแรกที่ทำงานครั้งเดียว = ~ 0-1ms มองไม่เห็น อย่างต่อเนื่อง : 5msมากกว่า 10,000 ซ้ำ ด้วยการทำซ้ำเฉลี่ยเดียว = .5 microseconds . เกี่ยวกับความเร็วเดียวกัน

ตรวจสอบด้วย:

ลิงค์เหล่านี้เป็นวิธีการอื่น การบัฟเฟอร์สามารถเพิ่มลงในฐานรหัสใหม่นี้ได้ แต่ที่สำคัญที่สุดคือการสำรวจแนวทางที่แตกต่างกันในการลบอคติและทำการเปรียบเทียบความเร็วและข้อดี / ข้อเสีย


ฉันพบการปรับปรุงประสิทธิภาพเล็กน้อยสำหรับวิธีการของคุณซึ่งดูเหมือนว่าจะได้รับการมัด - stackoverflow.com/a/17092645/1037948
drzaus

5
1) ทำไมทุกคนยังคงเวทมนต์เหล่านั้นหรือไม่ คุณระบุความยาวผลลัพธ์สามครั้ง เพียงกำหนดเป็นค่าคงที่หรือพารามิเตอร์ คุณสามารถใช้แทนcharSet.Length 622) สแตติกที่Randomไม่มีการล็อกหมายความว่ารหัสนี้ไม่ใช่ threadsafe 3) การลด 0-255 mod 62 แนะนำอคติที่ตรวจพบได้ 4) คุณไม่สามารถใช้บนแถวถ่านผลตอบแทนที่มักToString "System.Char[]"คุณต้องใช้new String(rName)แทน
CodesInChaos

ขอบคุณ @CodeInChaos ฉันไม่เคยคิดถึงสิ่งเหล่านั้นมาก่อน ยังคงใช้คลาสสุ่มเท่านั้น แต่ควรดีกว่านี้ ฉันไม่สามารถคิดถึงวิธีที่ดีกว่าในการตรวจจับและแก้ไขอินพุตไบอัส
ทอดด์

มันค่อนข้างโง่ที่เริ่มต้นด้วย RNG ที่อ่อนแอ ( System.Random) และหลีกเลี่ยงอคติใด ๆ ในรหัสของคุณอย่างระมัดระวัง การแสดงออกของ "การขัดสีน้ำมัน" อยู่ในใจ
CodesInChaos

@CodesInChaos และตอนนี้ลูกศิษย์ของเขาเก่งกว่า
ทอดด์

4

น่ากลัวฉันรู้ แต่ฉันก็ไม่สามารถช่วยตัวเองได้:


namespace ConsoleApplication2
{
    using System;
    using System.Text.RegularExpressions;

    class Program
    {
        static void Main(string[] args)
        {
            Random adomRng = new Random();
            string rndString = string.Empty;
            char c;

            for (int i = 0; i < 8; i++)
            {
                while (!Regex.IsMatch((c=Convert.ToChar(adomRng.Next(48,128))).ToString(), "[A-Za-z0-9]"));
                rndString += c;
            }

            Console.WriteLine(rndString + Environment.NewLine);
        }
    }
}


4

ฉันกำลังมองหาคำตอบเฉพาะเจาะจงมากขึ้นซึ่งฉันต้องการควบคุมรูปแบบของสตริงแบบสุ่มและพบกับโพสต์นี้ ตัวอย่างเช่น: แผ่นป้ายทะเบียน (รถยนต์) มีรูปแบบเฉพาะ (ต่อประเทศ) และฉันต้องการสร้างป้ายทะเบียนแบบสุ่ม
ฉันตัดสินใจเขียนวิธีการขยายแบบสุ่มของฉันเองสำหรับสิ่งนี้ (นี่คือเพื่อนำวัตถุสุ่มเดียวกันมาใช้ใหม่เนื่องจากคุณอาจมีคู่ในสถานการณ์จำลองหลายเธรด) ฉันสร้างส่วนสำคัญ ( https://gist.github.com/SamVanhoutte/808845ca78b9c041e928 ) แต่จะคัดลอกคลาสส่วนขยายที่นี่ด้วย:

void Main()
{
    Random rnd = new Random();
    rnd.GetString("1-###-000").Dump();
}

public static class RandomExtensions
{
    public static string GetString(this Random random, string format)
    {
        // Based on http://stackoverflow.com/questions/1344221/how-can-i-generate-random-alphanumeric-strings-in-c
        // Added logic to specify the format of the random string (# will be random string, 0 will be random numeric, other characters remain)
        StringBuilder result = new StringBuilder();
        for(int formatIndex = 0; formatIndex < format.Length ; formatIndex++)
        {
            switch(format.ToUpper()[formatIndex])
            {
                case '0': result.Append(getRandomNumeric(random)); break;
                case '#': result.Append(getRandomCharacter(random)); break;
                default : result.Append(format[formatIndex]); break;
            }
        }
        return result.ToString();
    }

    private static char getRandomCharacter(Random random)
    {
        string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        return chars[random.Next(chars.Length)];
    }

    private static char getRandomNumeric(Random random)
    {
        string nums = "0123456789";
        return nums[random.Next(nums.Length)];
    }
}

4

ขณะนี้อยู่ในรสชาติหนึ่งซับ

private string RandomName()
{
        return new string(
            Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 13)
                .Select(s =>
                {
                    var cryptoResult = new byte[4];
                    using (var cryptoProvider = new RNGCryptoServiceProvider())
                        cryptoProvider.GetBytes(cryptoResult);

                    return s[new Random(BitConverter.ToInt32(cryptoResult, 0)).Next(s.Length)];
                })
                .ToArray());
}

2
การใช้พร็อพเพอร์ตี้สำหรับสิ่งที่เปลี่ยนแปลงในทุกการเข้าถึงนั้นค่อนข้างน่าสงสัย ฉันขอแนะนำให้ใช้วิธีการแทน
CodesInChaos

2
RNGCryptoServiceProviderควรทิ้งหลังการใช้งาน
Tsahi Asher

ฉันแก้ไขปัญหา IDisposable แต่ยังคงเป็นที่น่าสงสัยอย่างมากในการสร้าง RNGCryptoServiceProvider ใหม่สำหรับจดหมายแต่ละฉบับ
piojo

@CodesInChaos ทำวิธีนี้แล้ว
Matas Vaitkevicius

3

พยายามที่จะรวมสองส่วน: ที่ไม่ซ้ำกัน (ลำดับเคาน์เตอร์หรือวันที่) และสุ่ม

public class RandomStringGenerator
{
    public static string Gen()
    {
        return ConvertToBase(DateTime.UtcNow.ToFileTimeUtc()) + GenRandomStrings(5); //keep length fixed at least of one part
    }

    private static string GenRandomStrings(int strLen)
    {
        var result = string.Empty;

        var Gen = new RNGCryptoServiceProvider();
        var data = new byte[1];

        while (result.Length < strLen)
        {
            Gen.GetNonZeroBytes(data);
            int code = data[0];
            if (code > 48 && code < 57 || // 0-9
                code > 65 && code < 90 || // A-Z
                code > 97 && code < 122   // a-z
                )
            {
                result += Convert.ToChar(code);
            }
        }

        return result;
    }

    private static string ConvertToBase(long num, int nbase = 36)
    {
        var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; //if you wish make algorithm more secure - change order of letter here

        // check if we can convert to another base
        if (nbase < 2 || nbase > chars.Length)
            return null;

        int r;
        var newNumber = string.Empty;

        // in r we have the offset of the char that was converted to the new base
        while (num >= nbase)
        {
            r = (int) (num % nbase);
            newNumber = chars[r] + newNumber;
            num = num / nbase;
        }
        // the last number to convert
        newNumber = chars[(int)num] + newNumber;

        return newNumber;
    }
}

แบบทดสอบ:

[Test]
    public void Generator_Should_BeUnigue1()
    {
        //Given
        var loop = Enumerable.Range(0, 1000);
        //When
        var str = loop.Select(x=> RandomStringGenerator.Gen());
        //Then
        var distinct = str.Distinct();
        Assert.AreEqual(loop.Count(),distinct.Count()); // Or Assert.IsTrue(distinct.Count() < 0.95 * loop.Count())
    }

1) คุณสามารถใช้ตัวอักษรตัวอักษรแทนค่า ASCII ที่เกี่ยวข้องกับตัวละครเหล่านั้น 2) คุณมีข้อผิดพลาดในการจับคู่ช่วงเวลาของคุณ คุณจำเป็นต้องใช้<=และ>=แทนและ< >3) ฉันจะเพิ่มวงเล็บที่ไม่จำเป็นรอบ ๆ&&นิพจน์เพื่อให้ชัดเจนว่าพวกเขามีความสำคัญกว่า แต่แน่นอนว่านั่นเป็นเพียงตัวเลือกโวหาร
CodesInChaos

+ 1 เหมาะสำหรับการลบอคติและเพิ่มการทดสอบ ฉันไม่แน่ใจว่าทำไมคุณเติมสตริงแบบสุ่มของคุณด้วยสตริงที่มาจากเวลาประทับ นอกจากนี้คุณยังต้องกำจัด RNGCryptoServiceProvider ของคุณ
monty

2

ทางออกโดยไม่ใช้Random:

var chars = Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 8);

var randomStr = new string(chars.SelectMany(str => str)
                                .OrderBy(c => Guid.NewGuid())
                                .Take(8).ToArray());

2
NewGuid ใช้การสุ่มภายใน ดังนั้นนี่คือการใช้แบบสุ่มมันแค่ซ่อนมันไว้
ลิ่ม

2

นี่คือตัวแปรของโซลูชันของ Eric J เช่นเสียงเข้ารหัสสำหรับ WinRT (Windows Store App):

public static string GenerateRandomString(int length)
{
    var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
    var result = new StringBuilder(length);
    for (int i = 0; i < length; ++i)
    {
        result.Append(CryptographicBuffer.GenerateRandomNumber() % chars.Length);
    }
    return result.ToString();
}

หากประสิทธิภาพมีความสำคัญ (โดยเฉพาะเมื่อความยาวสูง):

public static string GenerateRandomString(int length)
{
    var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
    var result = new System.Text.StringBuilder(length);
    var bytes = CryptographicBuffer.GenerateRandom((uint)length * 4).ToArray();
    for (int i = 0; i < bytes.Length; i += 4)
    {
        result.Append(BitConverter.ToUInt32(bytes, i) % chars.Length);
    }
    return result.ToString();
}

1
นี่ไม่ใช่เสียงที่เข้ารหัส มีอคติเล็ก ๆ เนื่องจากการดำเนินการโมดูลัสไม่กระจายความกว้างทั้งหมดของ ulong เท่า ๆ กันเป็น 62 ตัวอักษร
Lie Ryan

1

ฉันรู้ว่านี่ไม่ใช่วิธีที่ดีที่สุด แต่คุณสามารถลองสิ่งนี้

string str = Path.GetRandomFileName(); //This method returns a random file name of 11 characters
str = str.Replace(".","");
Console.WriteLine("Random string: " + str);

2
หนึ่งบรรทัดเป็นอย่างไร Console.WriteLine ($ "สตริงสุ่ม: {Path.GetRandomFileName (). แทนที่ (". "," ")}"); เป็นหนึ่งบรรทัด
PmanAce

1

ฉันไม่รู้ว่า cryptographically sound นี้เป็นอย่างไร แต่มันสามารถอ่านได้และรัดกุมกว่าโซลูชั่นที่ซับซ้อนมากขึ้นโดยไกล (imo) และมันควรจะเป็น "สุ่ม" มากกว่าSystem.Randomโซลูชั่นที่ใช้

return alphabet
    .OrderBy(c => Guid.NewGuid())
    .Take(strLength)
    .Aggregate(
        new StringBuilder(),
        (builder, c) => builder.Append(c))
    .ToString();

ฉันไม่สามารถตัดสินใจได้ว่าฉันคิดว่ารุ่นนี้หรืออันถัดไปคือ "สวยกว่า" แต่พวกเขาให้ผลลัพธ์ที่เหมือนกัน:

return new string(alphabet
    .OrderBy(o => Guid.NewGuid())
    .Take(strLength)
    .ToArray());

ได้รับมันไม่ได้ปรับให้เหมาะกับความเร็วดังนั้นหากภารกิจสำคัญยิ่งในการสร้างสตริงสุ่มนับล้านทุก ๆ วินาทีลองอีกสายหนึ่ง!

หมายเหตุ: วิธีนี้ไม่อนุญาตให้มีการทำซ้ำสัญลักษณ์ในตัวอักษรและตัวอักษรต้องมีขนาดเท่ากันหรือใหญ่กว่าเอาท์พุทสตริงทำให้วิธีนี้เป็นที่ต้องการน้อยกว่าในบางสถานการณ์ทุกอย่างขึ้นอยู่กับการใช้งานของคุณ


0

หากค่าของคุณไม่ได้สุ่มอย่างสมบูรณ์ แต่ในความเป็นจริงอาจขึ้นอยู่กับบางสิ่งบางอย่าง - คุณอาจคำนวณแฮช md5 หรือ sha1 ของ 'Somwthing' และตัดให้ยาวเท่าที่คุณต้องการ

นอกจากนี้คุณอาจสร้างและตัดทอน guid


0
public static class StringHelper
{
    private static readonly Random random = new Random();

    private const int randomSymbolsDefaultCount = 8;
    private const string availableChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

    private static int randomSymbolsIndex = 0;

    public static string GetRandomSymbols()
    {
        return GetRandomSymbols(randomSymbolsDefaultCount);
    }

    public static string GetRandomSymbols(int count)
    {
        var index = randomSymbolsIndex;
        var result = new string(
            Enumerable.Repeat(availableChars, count)
                      .Select(s => {
                          index += random.Next(s.Length);
                          if (index >= s.Length)
                              index -= s.Length;
                          return s[index];
                      })
                      .ToArray());
        randomSymbolsIndex = index;
        return result;
    }
}

2
1) วิธีการคงที่ควรมีความปลอดภัยด้าย 2) อะไรคือจุดเพิ่มดัชนีแทนที่จะใช้ผลลัพธ์random.Nextโดยตรง ทำให้รหัสมีความซับซ้อนและไม่เกิดประโยชน์อะไรเลย
CodesInChaos

0

นี่เป็นกลไกในการสร้างสตริงตัวอักษรและตัวเลขแบบสุ่ม (ฉันใช้สิ่งนี้เพื่อสร้างรหัสผ่านและทดสอบข้อมูล) โดยไม่ต้องกำหนดตัวอักษรและตัวเลข

CleanupBase64 จะลบส่วนที่จำเป็นในสตริงและทำการเพิ่มตัวอักษรและตัวเลขแบบสุ่มซ้ำ ๆ

        public static string GenerateRandomString(int length)
        {
            var numArray = new byte[length];
            new RNGCryptoServiceProvider().GetBytes(numArray);
            return CleanUpBase64String(Convert.ToBase64String(numArray), length);
        }

        private static string CleanUpBase64String(string input, int maxLength)
        {
            input = input.Replace("-", "");
            input = input.Replace("=", "");
            input = input.Replace("/", "");
            input = input.Replace("+", "");
            input = input.Replace(" ", "");
            while (input.Length < maxLength)
                input = input + GenerateRandomString(maxLength);
            return input.Length <= maxLength ?
                input.ToUpper() : //In my case I want capital letters
                input.ToUpper().Substring(0, maxLength);
        }

คุณได้ประกาศGenerateRandomStringและทำให้การเรียกร้องให้ออกมาจากภายในGetRandomString SanitiseBase64Stringนอกจากนี้คุณมีการประกาศSanitiseBase64String และการโทรในCleanUpBase64String GenerateRandomString
Wai Ha Lee


0

ไม่แน่ใจ 100% เพราะฉันไม่ได้ทดสอบตัวเลือกทุกอย่างที่นี่ แต่สิ่งที่ฉันทำทดสอบตัวนี้เร็วที่สุด หมดเวลากับนาฬิกาจับเวลาและพบว่ามีเห็บ 9-10 ดังนั้นหากความเร็วมีความสำคัญมากกว่าความปลอดภัยลองดู:

 private static Random random = new Random(); 
 public static string Random(int length)
     {   
          var stringChars = new char[length];

          for (int i = 0; i < length; i++)
              {
                  stringChars[i] = (char)random.Next(0x30, 0x7a);                  
                  return new string(stringChars);
              }
     }

ทำไมต้องตอบ คำถามนี้ได้รับการตอบคำถามบ่อยครั้ง แต่คุณยังคงโพสต์คำตอบที่ได้ผลครึ่งหนึ่งไว้ ..
Laurent
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.