Nachdem ich die anderen Antworten überprüft und die Kommentare von CodeInChaos sowie die immer noch voreingenommene (wenn auch weniger) Antwort von CodeInChaos berücksichtigt hatte, dachte ich, dass eine endgültige Lösung zum Ausschneiden und Einfügen erforderlich ist. Während ich meine Antwort aktualisierte, beschloss ich, alles zu tun.
Eine aktuelle Version dieses Codes finden Sie im neuen Hg-Repository auf Bitbucket: https://bitbucket.org/merarischroeder/secureswiftrandom . Ich empfehle Ihnen, den Code zu kopieren und einzufügen von: https://bitbucket.org/merarischroeder/secureswiftrandom/src/6c14b874f34a3f6576b0213379ecdf0ffc7496ea/Code/Alivate.SolidSwiftRandom/SolidSwiftRandom.cs-vdef (stellen Sie sicher , klicken Klicken Sie auf die Schaltfläche "Roh", um das Kopieren zu vereinfachen und sicherzustellen, dass Sie über die neueste Version verfügen. Ich denke, dieser Link führt zu einer bestimmten Version des Codes, nicht zu der neuesten.
Aktualisierte Notizen:
- In Bezug auf einige andere Antworten - Wenn Sie die Länge der Ausgabe kennen, benötigen Sie keinen StringBuilder. Wenn Sie ToCharArray verwenden, wird das Array erstellt und gefüllt (Sie müssen nicht zuerst ein leeres Array erstellen).
- In Bezug auf einige andere Antworten - Sie sollten NextBytes verwenden, anstatt jeweils eine für die Leistung zu erhalten
- Technisch gesehen können Sie das Byte-Array für einen schnelleren Zugriff anheften. Es lohnt sich normalerweise, wenn Sie mehr als 6-8 Mal über ein Byte-Array iterieren. (Hier nicht gemacht)
- Verwendung von RNGCryptoServiceProvider für beste Zufälligkeit
- Gebrauch von Zwischenspeicherung eines 1-MB-Puffers mit zufälligen Daten - Benchmarking zeigt, dass die Zugriffsgeschwindigkeit zwischengespeicherter Einzelbytes ~ 1000x schneller ist - 9 ms über 1 MB gegenüber 989 ms für nicht zwischengespeicherte.
- Optimierte Unterdrückung der Vorspannungszone in meiner neuen Klasse.
Endlösung zur Frage:
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);
}
Aber du brauchst meine neue (ungetestete) Klasse:
/// <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
}
}
}
Für die Geschichte - meine ältere Lösung für diese Antwort, verwendetes Zufallsobjekt:
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);
}
Performance:
- SecureFastRandom - Erster einzelner Lauf = ~ 9-33 ms . Unmerklich. Laufend : 5 ms (manchmal bis zu 13 ms) über 10.000 Iterationen, mit einer einzelnen durchschnittlichen Iteration = 1,5 Mikrosekunden. . Hinweis: Erfordert im Allgemeinen 2, gelegentlich jedoch bis zu 8 Cache-Aktualisierungen - hängt davon ab, wie viele einzelne Bytes die Bias-Zone überschreiten
- Zufällig - Erster einzelner Lauf = ~ 0-1 ms . Unmerklich. Laufend : 5 ms über 10.000 Iterationen. Mit einer einzelnen durchschnittlichen Iteration = 0,5 Mikrosekunden. . Etwa die gleiche Geschwindigkeit.
Schauen Sie sich auch an:
Diese Links sind ein weiterer Ansatz. Diese neue Codebasis könnte um Pufferung erweitert werden. Am wichtigsten war jedoch die Untersuchung verschiedener Ansätze zur Beseitigung von Verzerrungen und das Benchmarking der Geschwindigkeiten und Vor- und Nachteile.