ฉันจะสร้างสตริงตัวอักษรและตัวเลขแบบสุ่ม 8 ตัวใน C # ได้อย่างไร
Random
ชั้นเรียนเพื่อสร้างรหัสผ่าน การเพาะของRandom
เอนโทรปีมีค่าต่ำมากจึงไม่ปลอดภัย ใช้ PRNG เข้ารหัสลับสำหรับรหัสผ่าน
ฉันจะสร้างสตริงตัวอักษรและตัวเลขแบบสุ่ม 8 ตัวใน C # ได้อย่างไร
Random
ชั้นเรียนเพื่อสร้างรหัสผ่าน การเพาะของRandom
เอนโทรปีมีค่าต่ำมากจึงไม่ปลอดภัย ใช้ PRNG เข้ารหัสลับสำหรับรหัสผ่าน
คำตอบ:
ฉันได้ยินว่า 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
คลาสหากคุณต้องการตัวสร้างตัวเลขสุ่มที่รัดกุม)
return new string(Enumerable.Range(1, length).Select(_ => chars[random.Next(chars.Length)]).ToArray());
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
คลาสหากคุณต้องการตัวสร้างตัวเลขสุ่มที่รัดกุม)
GetRandomFileName
โซลูชันของ Adam Porad นั้นเร็วกว่า แต่ไม่อนุญาตให้มีการควบคุมอักขระที่ใช้และความยาวสูงสุดที่เป็นไปได้คือ 11 chars Guid
วิธีแก้ปัญหาของดักลาสเร็วมาก แต่ตัวละครถูก จำกัด ไว้ที่ A-F0-9 และความยาวสูงสุดที่ใช้ได้คือ 32 ตัวอักษร
GetRandomFileName
แต่ (a) คุณสูญเสียความได้เปรียบในการทำงานและ (b) รหัสของคุณจะซับซ้อนกว่านี้
System.Random
ไม่เหมาะสำหรับความปลอดภัย
อัปเดตตามความคิดเห็น การใช้งานดั้งเดิมสร้างขึ้น 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")}%");
}
}
}
}
RNGCSP
ตั้งแต่แรก) การใช้ mod to index ในchars
อาร์เรย์หมายความว่าคุณจะได้รับผลลัพธ์แบบเอนเอียงเว้นแต่chars.Length
จะเป็นตัวหาร 256
4*maxSize
(UInt32)(BitConverter.ToInt32(data,4*i)% chars.Length
ฉันยังต้องการใช้แทนGetBytes
และในที่สุดคุณสามารถเอาสายแรกที่GetNonZeroBytes
GetNonZeroBytes
คุณไม่ได้ใช้ผลลัพธ์
โซลูชันที่ 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 ตัวอักขระที่สร้างขึ้นซ้ำหนึ่งรายการ
นี่คือตัวอย่างที่ฉันขโมยมาจากตัวอย่างของ 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: ขอบคุณแซม
เป้าหมายหลักของรหัสของฉันคือ:
คุณสมบัติแรกสามารถทำได้โดยการใช้โมดูโลขนาด 64 บิต สำหรับตัวอักษรขนาดเล็ก (เช่นตัวอักษร 62 ตัวจากคำถาม) สิ่งนี้นำไปสู่การมีอคติเล็กน้อย ที่สองและสามคุณสมบัติที่จะประสบความสำเร็จโดยใช้แทนRNGCryptoServiceProvider
System.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);
}
ง่ายที่สุด:
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
อินสแตนซ์
chars.Select
มีขนาดใหญ่น่าเกลียดเนื่องจากต้องอาศัยขนาดเอาต์พุตเป็นขนาดตัวอักษรมากที่สุด
'a'.To('z')
วิธีการหรือไม่
chars.Select()
.Take (n) chars.Count >= n
`ทำงานเฉพาะถ้า การเลือกลำดับที่คุณไม่ได้ใช้จริง ๆ นั้นค่อนข้างใช้งานง่ายโดยเฉพาะกับข้อจำกัดความยาวโดยนัย ฉันอยากจะใช้หรือEnumerable.Range
Enumerable.Repeat
2) ข้อผิดพลาด "ถ่านท้ายที่สุดควรจะน้อยกว่าถ่านเริ่มต้น" เป็นรอบผิดวิธี / not
หายไป
chars.Count
> n
นอกจากนี้ฉันไม่ได้รับส่วนที่ไม่ได้ใช้งานง่าย นั่นทำให้ทุกการใช้งานTake
ไม่ได้ใช้ไม่ได้หรือไม่ ฉันไม่เชื่อหรอก ขอบคุณสำหรับการชี้ที่พิมพ์ผิด
การเปรียบเทียบประสิทธิภาพของคำตอบที่หลากหลายในหัวข้อนี้:
// 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) ต่ำสุด [เครื่องที่แตกต่าง]
var many = 10000; Assert.AreEqual(many, new bool[many].Select(o => EachRandomizingMethod(10)).Distinct().Count());
ที่คุณแทนที่EachRandomizingMethod
ด้วย ... แต่ละวิธี
โค้ดหนึ่งบรรทัดMembership.GeneratePassword()
ใช้กลอุบายได้ :)
นี่คือตัวอย่างสำหรับสิ่งเดียวกัน
รหัสที่เขียนโดย 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);
}
}
นอกจากนี้เรายังใช้สตริงที่กำหนดเองแบบสุ่ม แต่เรานำมาใช้เป็นตัวช่วยของสตริงเพื่อให้มีความยืดหยุ่น ...
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);
รหัสบรรทัดเดียวที่เรียบง่ายใช้งานได้สำหรับฉัน :)
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);
}
อีกทางเลือกหนึ่งคือการใช้ 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());
คำถาม:ทำไมฉันต้องเสียเวลาโดยใช้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
?"
[A-Z0-9]
เป็น ถ้าโดยบังเอิญสตริงสุ่มของคุณครอบคลุม[A-HJ-Z0-9]
เฉพาะผลลัพธ์ไม่ครอบคลุมช่วงที่อนุญาตเต็มซึ่งอาจเป็นปัญหาได้
I
ดังนั้นมันไม่ได้มี เป็นเพราะมีตัวละครน้อยลงและทำให้ง่ายขึ้นในการถอดรหัส? สถิติของรหัสผ่านที่ถอดรหัสได้ที่มี 35 ตัวอักษรในช่วงที่มากกว่า 36 คืออะไรฉันคิดว่าฉันค่อนข้างเสี่ยงที่จะ ... หรือเพียงแค่พิสูจน์ช่วงตัวละคร ... แต่นั่นคือฉัน ฉันหมายความว่าไม่ต้องเป็นรูก้นฉันแค่พูด บางครั้งฉันคิดว่าโปรแกรมเมอร์มีแนวโน้มไปเส้นทางซับซ้อนพิเศษเพื่อประโยชน์ของซับซ้อน
I
และO
จากประเภทนี้ของสตริงสุ่มเพื่อหลีกเลี่ยงความสับสนมนุษย์พวกเขาด้วยและ1
0
หากคุณไม่สนใจว่าจะมีสตริงที่มนุษย์อ่านได้ดี แต่ถ้ามันเป็นสิ่งที่บางคนอาจต้องพิมพ์มันก็ฉลาดที่จะลบตัวอักษรเหล่านั้นออก
โซลูชันของ 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);
การตั้งค่าสไตล์ของคุณอาจแตกต่างกันไป
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());
}
หลังจากทบทวนคำตอบอื่น ๆ และพิจารณาความคิดเห็นของ 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 เพื่อให้ง่ายต่อการคัดลอกและทำให้แน่ใจว่าคุณมีเวอร์ชั่นล่าสุดฉันคิดว่าลิงก์นี้ไปยังรหัสรุ่นที่ระบุไม่ใช่รุ่นล่าสุด)
บันทึกล่าสุด:
จบการแก้ไขคำถาม:
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);
}
ประสิทธิภาพ:
ตรวจสอบด้วย:
ลิงค์เหล่านี้เป็นวิธีการอื่น การบัฟเฟอร์สามารถเพิ่มลงในฐานรหัสใหม่นี้ได้ แต่ที่สำคัญที่สุดคือการสำรวจแนวทางที่แตกต่างกันในการลบอคติและทำการเปรียบเทียบความเร็วและข้อดี / ข้อเสีย
charSet.Length
62
2) สแตติกที่Random
ไม่มีการล็อกหมายความว่ารหัสนี้ไม่ใช่ threadsafe 3) การลด 0-255 mod 62 แนะนำอคติที่ตรวจพบได้ 4) คุณไม่สามารถใช้บนแถวถ่านผลตอบแทนที่มักToString
"System.Char[]"
คุณต้องใช้new String(rName)
แทน
System.Random
) และหลีกเลี่ยงอคติใด ๆ ในรหัสของคุณอย่างระมัดระวัง การแสดงออกของ "การขัดสีน้ำมัน" อยู่ในใจ
น่ากลัวฉันรู้ แต่ฉันก็ไม่สามารถช่วยตัวเองได้:
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);
}
}
}
ฉันกำลังมองหาคำตอบเฉพาะเจาะจงมากขึ้นซึ่งฉันต้องการควบคุมรูปแบบของสตริงแบบสุ่มและพบกับโพสต์นี้ ตัวอย่างเช่น: แผ่นป้ายทะเบียน (รถยนต์) มีรูปแบบเฉพาะ (ต่อประเทศ) และฉันต้องการสร้างป้ายทะเบียนแบบสุ่ม
ฉันตัดสินใจเขียนวิธีการขยายแบบสุ่มของฉันเองสำหรับสิ่งนี้ (นี่คือเพื่อนำวัตถุสุ่มเดียวกันมาใช้ใหม่เนื่องจากคุณอาจมีคู่ในสถานการณ์จำลองหลายเธรด) ฉันสร้างส่วนสำคัญ ( 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)];
}
}
ขณะนี้อยู่ในรสชาติหนึ่งซับ
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());
}
RNGCryptoServiceProvider
ควรทิ้งหลังการใช้งาน
พยายามที่จะรวมสองส่วน: ที่ไม่ซ้ำกัน (ลำดับเคาน์เตอร์หรือวันที่) และสุ่ม
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())
}
<=
และ>=
แทนและ<
>
3) ฉันจะเพิ่มวงเล็บที่ไม่จำเป็นรอบ ๆ&&
นิพจน์เพื่อให้ชัดเจนว่าพวกเขามีความสำคัญกว่า แต่แน่นอนว่านั่นเป็นเพียงตัวเลือกโวหาร
ทางออกโดยไม่ใช้Random
:
var chars = Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 8);
var randomStr = new string(chars.SelectMany(str => str)
.OrderBy(c => Guid.NewGuid())
.Take(8).ToArray());
นี่คือตัวแปรของโซลูชันของ 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();
}
ฉันรู้ว่านี่ไม่ใช่วิธีที่ดีที่สุด แต่คุณสามารถลองสิ่งนี้
string str = Path.GetRandomFileName(); //This method returns a random file name of 11 characters
str = str.Replace(".","");
Console.WriteLine("Random string: " + str);
ฉันไม่รู้ว่า 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());
ได้รับมันไม่ได้ปรับให้เหมาะกับความเร็วดังนั้นหากภารกิจสำคัญยิ่งในการสร้างสตริงสุ่มนับล้านทุก ๆ วินาทีลองอีกสายหนึ่ง!
หมายเหตุ: วิธีนี้ไม่อนุญาตให้มีการทำซ้ำสัญลักษณ์ในตัวอักษรและตัวอักษรต้องมีขนาดเท่ากันหรือใหญ่กว่าเอาท์พุทสตริงทำให้วิธีนี้เป็นที่ต้องการน้อยกว่าในบางสถานการณ์ทุกอย่างขึ้นอยู่กับการใช้งานของคุณ
หากค่าของคุณไม่ได้สุ่มอย่างสมบูรณ์ แต่ในความเป็นจริงอาจขึ้นอยู่กับบางสิ่งบางอย่าง - คุณอาจคำนวณแฮช md5 หรือ sha1 ของ 'Somwthing' และตัดให้ยาวเท่าที่คุณต้องการ
นอกจากนี้คุณอาจสร้างและตัดทอน guid
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;
}
}
random.Next
โดยตรง ทำให้รหัสมีความซับซ้อนและไม่เกิดประโยชน์อะไรเลย
นี่เป็นกลไกในการสร้างสตริงตัวอักษรและตัวเลขแบบสุ่ม (ฉันใช้สิ่งนี้เพื่อสร้างรหัสผ่านและทดสอบข้อมูล) โดยไม่ต้องกำหนดตัวอักษรและตัวเลข
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
มีหนึ่งในแพ็คเกจนักเก็ตสุดเหวี่ยงที่ทำให้มันง่ายมาก
var myObject = new Faker<MyObject>()
.RuleFor(p => p.MyAlphaNumericProperty, f => f.Random.AlphaNumeric(/*lenght*/ 7))
.Generate();
ไม่แน่ใจ 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);
}
}