生成唯一随机字符串

109

我希望生成类似于MSDN库(Error Object)创建的随机唯一字符串,例如't9zk6eay'。


1
尝试使用以下代码:string randoms = Guid.NewGuid().ToString().Replace("-", string.Empty).Replace("+", string.Empty).Substring(0, 4); 更多相关信息请参见此处 - Shaiju T
1
要使某物完全独特,它必须基于一些非随机的东西,比如时间、位置等,因此实际上永远不可能是完全随机的。Guid看起来可能是随机的,但实际上并不是。在我看来,你唯一的希望就是使它如此随机和复杂,以至于在所有实际情况下,这些值都将是独特的(即具有极低的碰撞概率)。 - bytedev
13个回答

187

更新于2016/1/23

如果您觉得这个答案有用,您可能会对我发布的一个简单(约500行代码)的密码生成库MlkPwgen感兴趣:

Install-Package MlkPwgen

接下来,您可以像下面的答案一样生成随机字符串:

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

该库的一个优点是代码更好地分解,因此您可以使用安全的随机性来生成字符串以外的内容。有关详细信息,请查看项目网站

原始回答

由于目前还没有提供安全代码,因此我发布以下代码,以防有人发现它有用。

string RandomString(int length, string allowedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") {
    if (length < 0) throw new ArgumentOutOfRangeException("length", "length cannot be less than zero.");
    if (string.IsNullOrEmpty(allowedChars)) throw new ArgumentException("allowedChars may not be empty.");

    const int byteSize = 0x100;
    var allowedCharSet = new HashSet<char>(allowedChars).ToArray();
    if (byteSize < allowedCharSet.Length) throw new ArgumentException(String.Format("allowedChars may contain no more than {0} characters.", byteSize));

    // Guid.NewGuid and System.Random are not particularly random. By using a
    // cryptographically-secure random number generator, the caller is always
    // protected, regardless of use.
    using (var rng = System.Security.Cryptography.RandomNumberGenerator.Create()) {
        var result = new StringBuilder();
        var buf = new byte[128];
        while (result.Length < length) {
            rng.GetBytes(buf);
            for (var i = 0; i < buf.Length && result.Length < length; ++i) {
                // Divide the byte into allowedCharSet-sized groups. If the
                // random value falls into the last group and the last group is
                // too small to choose from the entire allowedCharSet, ignore
                // the value in order to avoid biasing the result.
                var outOfRangeStart = byteSize - (byteSize % allowedCharSet.Length);
                if (outOfRangeStart <= buf[i]) continue;
                result.Append(allowedCharSet[buf[i] % allowedCharSet.Length]);
            }
        }
        return result.ToString();
    }
}

感谢Ahmad指出如何在.NET Core上使代码正常工作。


3
@LeeGrissom,偏置是一个重要的方面。比如说,如果你的字母表包含255个字符,并且你得到一个在0-255之间的随机值,在环形缓冲区中,值0和255都将对应于相同的字符,这将使第一个字母在字母表中更加倾向于出现,从而降低了随机性。当然,这是否重要取决于具体应用。 - Oskar Sjöberg
4
谁在攻击 ".netcore":将 var rng = new RNGCryptoServiceProvider() 替换为 var rng = RandomNumberGenerator.Create()。 (注:这是一条要求将使用不安全的随机数生成器替换为更安全的建议,以防止潜在的攻击。) - amd
2
为什么你要在每次迭代中计算“var outOfRangeStart = byteSize - (byteSize % allowedCharSet.Length);”?你可以在“using”之前计算它。 - mtkachenko
allowedCharSet 数组不是必需的。allowedChars(一个字符串)已经是字符数组。 - kspearrin
1
@BartCalixto 已修复。谢谢! - Michael Kropat
显示剩余14条评论

97

使用 GUID 是一种很好的方式,但为了得到类似于您示例的东西,您可能想将其转换为 Base64 字符串:

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

为了更接近你的例子,我去掉了"="和"+"。否则你会在字符串末尾得到"=="和一个"+"在中间。这是一个例子输出字符串:

"OZVV5TpP4U6wJthaCORZEQ"


16
你应该考虑替换 / 也。 - Jason Kealey
22
Guid不应被视为安全的随机字符串,因为序列可以被猜测。Guid旨在避免键冲突,而不是随机生成。关于Guid随机性的讨论,Stack Overflow上有一些很好的文章。 - Daniel Bradley
想要了解 Convert.ToBase64String 的清晰简短的解释,请点击这里 - jwaliszko
2
将GUID转换为Base64并替换+和=符号会增加碰撞概率吗? - Milan Aggarwal
8
如果你能写出一个使用"new Guid()"生成碰撞而不需"黑客攻击"(篡改时钟或内部Windows数据结构)的应用程序,我会请你喝一杯啤酒。你可以随意使用多核、线程、同步原语等等。 - Lucero
显示剩余3条评论

40

我要提醒大家,GUID不是随机数。它们不应该被用作生成您希望完全随机的任何东西的基础(参见http://en.wikipedia.org/wiki/Globally_Unique_Identifier):

对WinAPI GUID生成器进行密码分析显示,由于V4 GUID序列是伪随机的,在给定初始状态的情况下,可以预测函数UuidCreate返回的接下来的250,000个GUID。这就是为什么GUID不应该用于加密,例如作为随机密钥。

相反,只需使用C# Random方法。可以像这样使用(在此处找到代码):

private string RandomString(int size)
{
  StringBuilder builder = new StringBuilder();
  Random random = new Random();
  char ch ;
  for(int i=0; i<size; i++)
  {
    ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65))) ;
    builder.Append(ch);
  }
  return builder.ToString();
}

如果你想要一个唯一的东西(比如数据库中的唯一文件名或键),那么 GUID 是可以的,但如果你需要一个随机的东西(比如密码或加密密钥)就不太合适了。所以这取决于你的应用程序。

Edit。微软表示,随机数也不是很好:

要创建适用于生成随机密码等用途的加密安全的随机数,请使用派生自 System.Security.Cryptography.RandomNumberGenerator 的类,例如 System.Security.Cryptography.RNGCryptoServiceProvider。


5
C# 的随机类也不是真正的“随机”,不能用于任何加密代码,因为它是从特定种子数开始的经典随机生成器。相同的种子将返回相同的数字序列;在这里,GUID 方法已经更好(不是“随机”而是“唯一”的)。 - Lucero
3
@Lucero,你是正确的。微软建议使用派生自System.Security.Cryptography.RandomNumberGenerator类的类,如System.Security.Cryptography.RNGCryptoServiceProvider,生成适用于创建随机密码的加密安全随机数。 - Keltex
问题已经说明他需要(伪)随机唯一字符串,因此没有加密要求,甚至不需要遵循特定的随机分布。因此,GUID可能是最简单的方法。 - Joey
1
“给定初始状态,可以预测接下来的250,000个GUID”这个说法似乎对于任何 PRNG都是本质上正确的...我相信它也不安全,但如果OP的目标是生成真正随机的URL,那么可能没有太多价值。;) - ojrac
1
(无论如何 +1 -- PRNG教育非常重要。) - ojrac

14

我认为它们并不是真正的随机数,但我的猜想是它们可能是一些哈希值。

每当我需要一些随机标识符时,我通常使用 GUID 并将其转换为其“裸”表示形式:

Guid.NewGuid().ToString("n");

正如@Keltex所指出的那样:对WinAPI GUID生成器的密码分析显示,由于V4 GUID序列是伪随机的,因此在给定初始状态的情况下,可以预测函数UuidCreate返回的接下来的250,000个GUID。 - JoanComasFdz

13

我简化了 @Michael Kropats 的解决方案,并制作了一个类似于LINQ的版本。

string RandomString(int length, string alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
{       
    var outOfRange = byte.MaxValue + 1 - (byte.MaxValue + 1) % alphabet.Length;

    return string.Concat(
        Enumerable
            .Repeat(0, int.MaxValue)
            .Select(e => RandomByte())
            .Where(randomByte => randomByte < outOfRange)
            .Take(length)
            .Select(randomByte => alphabet[randomByte % alphabet.Length])
    );
}

byte RandomByte()
{
    using (var randomizationProvider = new RNGCryptoServiceProvider())
    {
        var randomBytes = new byte[1];
        randomizationProvider.GetBytes(randomBytes);
        return randomBytes.Single();
    }   
}

4

我很惊讶为什么没有一种加密解决方案。 GUID是唯一的,但不具备加密安全性请参阅此Dotnet Fiddle。

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

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

如果你想要在 Guid 前面添加:

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

更干净的字母数字字符串:

result = Regex.Replace(result,"[^A-Za-z0-9]","");
Console.WriteLine(result);

4
尝试将Guid和Time.Ticks结合使用
 var randomNumber = Convert.ToBase64String(Guid.NewGuid().ToByteArray()) + DateTime.Now.Ticks;
     randomNumber = System.Text.RegularExpressions.Regex.Replace(randomNumber, "[^0-9a-zA-Z]+", "");

2

这对我来说非常完美。

    private string GeneratePasswordResetToken()
    {
        string token = Guid.NewGuid().ToString();
        var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(token);
        return Convert.ToBase64String(plainTextBytes);
    }

1
迈克尔·克罗帕茨在VB.net中的解决方案。
Private Function RandomString(ByVal length As Integer, Optional ByVal allowedChars As String = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") As String
    If length < 0 Then Throw New ArgumentOutOfRangeException("length", "length cannot be less than zero.")
    If String.IsNullOrEmpty(allowedChars) Then Throw New ArgumentException("allowedChars may not be empty.")


    Dim byteSize As Integer = 256
    Dim hash As HashSet(Of Char) = New HashSet(Of Char)(allowedChars)
    'Dim hash As HashSet(Of String) = New HashSet(Of String)(allowedChars)
    Dim allowedCharSet() = hash.ToArray

    If byteSize < allowedCharSet.Length Then Throw New ArgumentException(String.Format("allowedChars may contain no more than {0} characters.", byteSize))


    ' Guid.NewGuid and System.Random are not particularly random. By using a
    ' cryptographically-secure random number generator, the caller is always
    ' protected, regardless of use.
    Dim rng = New System.Security.Cryptography.RNGCryptoServiceProvider()
    Dim result = New System.Text.StringBuilder()
    Dim buf = New Byte(128) {}
    While result.Length < length
        rng.GetBytes(buf)
        Dim i
        For i = 0 To buf.Length - 1 Step +1
            If result.Length >= length Then Exit For
            ' Divide the byte into allowedCharSet-sized groups. If the
            ' random value falls into the last group and the last group is
            ' too small to choose from the entire allowedCharSet, ignore
            ' the value in order to avoid biasing the result.
            Dim outOfRangeStart = byteSize - (byteSize Mod allowedCharSet.Length)
            If outOfRangeStart <= buf(i) Then
                Continue For
            End If
            result.Append(allowedCharSet(buf(i) Mod allowedCharSet.Length))
        Next
    End While
    Return result.ToString()
End Function

0

如果您想要一个包含小写字母和大写字母字符([a-zA-Z0-9])的字母数字字符串,您可以使用Convert.ToBase64String()来获得快速简单的解决方案。

至于唯一性,请查看生日悖论以计算在生成的字符串数量(B)和生成的字符串长度(A)给定的情况下发生碰撞的可能性有多大。

Random random = new Random();

int outputLength = 10;
int byteLength = (int)Math.Ceiling(3f / 4f * outputLength); // Base64 uses 4 characters for every 3 bytes of data; so in random bytes we need only 3/4 of the desired length
byte[] randomBytes = new byte[byteLength];
string output;
do
{
    random.NextBytes(randomBytes); // Fill bytes with random data
    output = Convert.ToBase64String(randomBytes); // Convert to base64
    output = output.Substring(0, outputLength); // Truncate any superfluous characters and/or padding
} while (output.Contains('/') || output.Contains('+')); // Repeat if we contain non-alphanumeric characters (~25% chance if length=10; ~50% chance if length=20; ~35% chance if length=32)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接