关于C#:唯一随机字符串生成

 2019-05-06 

Unique random string generation

我想生成随机的唯一字符串,如由msdn库生成的字符串:

例如,http://msdn.microsoft.com/en-us/library/t9zk6eay.aspx。应生成类似"t9zk6eay"的字符串。


更新2016/1/23

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

1
Install-Package MlkPwgen

然后您可以生成随机字符串,如下所示:

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

该库的一个优点是,代码被更好地分解出来,这样您就可以对更多的字符串使用安全随机性。查看项目网站以了解更多详细信息。

原始答案

由于还没有人提供安全代码,我发布了以下内容,以防有人发现它有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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核心上工作。


使用guid是一种很好的方法,但要获得类似于示例的内容,您可能需要将其转换为base64字符串:

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

我把"="和"+"去掉,让你更接近你的例子,否则你在字符串末尾得到"==",中间加上"+"。下面是一个输出字符串示例:

"ozvv5tp4u6wjthacorzeq"


我警告说guid不是随机数。它们不应作为生成任何您希望完全随机的内容的基础(请参见http://en.wikipedia.org/wiki/globally_unique_identifier):

Cryptanalysis of the WinAPI GUID generator shows that, since the sequence of V4 GUIDs is pseudo-random, given the initial state one can predict up to next 250 000 GUIDs returned by the function UuidCreate. This is why GUIDs should not be used in cryptography, e. g., as random keys.

相反,只需使用c随机方法。类似于此(此处找到代码):

1
2
3
4
5
6
7
8
9
10
11
12
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是可以的,但是它们不适合您想要随机的东西(如密码或加密密钥)。所以这取决于你的申请。

编辑。微软说,随机也没有那么好(http://msdn.microsoft.com/en-us/library/system.random(vs.71.aspx):

To generate a cryptographically secure random number suitable for creating a random password, for example, use a class derived from System.Security.Cryptography.RandomNumberGenerator such as System.Security.Cryptography.RNGCryptoServiceProvider.


我简化了@michael kropats解决方案,并制作了一个Linq风格的版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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();
    }  
}

我不认为它们是随机的,但我猜它们是一些散列。

每当我需要一些随机标识符时,我通常使用一个guid并将其转换为其"裸"表示:

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


尝试在guid和time.ticks之间组合。

1
2
 var randomNumber = Convert.ToBase64String(Guid.NewGuid().ToByteArray()) + DateTime.Now.Ticks;
     randomNumber = System.Text.RegularExpressions.Regex.Replace(randomNumber,"[^0-9a-zA-Z]+","");


我很惊讶为什么没有合适的crytpographic解决方案。guid是唯一的,但不是加密安全的。看看这个网络小提琴。

1
2
3
4
5
6
var bytes = new byte[40]; // byte size
using (var crypto = new RNGCryptoServiceProvider())
  crypto.GetBytes(bytes);

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

如果要用guid预先发送:

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

更清晰的字母数字字符串:

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

vb.net中的michael kropats解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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

这个很适合我

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

如果您想要一个包含小写和大写字符([a-za-z0-9])的字母数字字符串,您可以使用convert.tobase64string()快速而简单的解决方案。

至于唯一性,请检查生日问题以计算碰撞的可能性(a)生成的字符串长度和(b)生成的字符串数。

1
2
3
4
5
6
7
8
9
10
11
12
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)

这是对各种语言的要求。这里有一个关于密码的问题,在这里也应该适用。

如果要使用字符串进行URL缩短,还需要字典<>或数据库检查以查看是否已使用生成的ID。


使用guid哈希代码获取唯一键

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static string GetUniqueKey(int length)
{
    string guidResult = string.Empty;

    while (guidResult.Length < length)
    {
        // Get the GUID.
        guidResult += Guid.NewGuid().ToString().GetHashCode().ToString("x");
    }

    // Make sure length is valid.
    if (length <= 0 || length > guidResult.Length)
        throw new ArgumentException("Length must be between 1 and" + guidResult.Length);

    // Return the first length bytes.
    return guidResult.Substring(0, length);
}


  • 不确定是否随机生成了Microsoft的链接
  • 查看新的guid().toString()。