我使用以下代码生成随机密钥,如下所示 TX8L1I
public string GetNewId()
{
string result = string.Empty;
Random rnd = new Random();
short codeLength = 5;
string chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
StringBuilder builder = new StringBuilder(codeLength);
for (int i = 0; i < codeLength; ++i)
builder.Append(chars[rnd.Next(chars.Length)]);
result = string.Format("{0}{1}", "T", builder.ToString());
return result;
}
每次生成密钥时,都会使用生成的结果作为主键在数据库上创建对应记录。当然,这可能不安全,因为可能会发生主键违规。
对此有什么正确的解决方法?我是否应该加载所有现有密钥并验证密钥是否已存在,如果是,则生成另一个密钥?
或者更有效的方法是将这个逻辑移到数据库端?但即使在数据库方面,我仍然需要检查当前表中不存在生成的随机密钥。
有什么想法吗?
由于
答案 0 :(得分:3)
将决定移至数据库。添加类型为uniqueidentifier
的主键。然后是一个简单的“即发即忘”算法......数据库决定了密钥是什么。
答案 1 :(得分:2)
我认为您应该在方法之外使用Random
实例。
由于Random
对象是seeded from the system clock,这意味着如果你很快地多次调用你的方法,它每次都会使用相同的种子,这意味着你将获得相同的字符串结束。
答案 2 :(得分:2)
我决定使用随机的是我不希望用户能够知道密钥的生成方式(格式),因为网站是公开的,我不希望用户尝试访问那把钥匙。
您已将两个问题合并为一个非常独立的问题:
这是一个更为一般的两个问题的具体案例:
在这种情况下,当我们不关心猜测时,处理它的最简单方法是使用相同的标识符。例如。对于由整数42
标识的数据库行,我们使用字符串"42"
,并且映射在一个方向上是一个微不足道的int.Parse()
或int.TryParse()
,并且是一个普通的.ToString()
或者在另一方隐含.ToString()
。
因此,我们使用的模式甚至没有考虑过它;公共ID和数据库键是相同的(可能有一些转换)。
但是,您希望阻止猜测的特定情况的最佳解决方案是不来更改密钥,而是更改密钥和公共标识符之间的映射。
首先,使用自动递增的整数(SQL Server中的“IDENTITY”,以及其他数据库中的各种类似概念)。
然后,当您将密钥发送到客户端时(即在表单值中使用它或将其附加到URI),然后将其映射为:
private const string Seed = "this is my secret seed ¾Ÿˇʣכ ↼⊜┲◗ blah balh";
private static string GetProtectedID(int id)
{
using(var sha = System.Security.Cryptography.SHA1.Create())
{
return string.Join("", sha.ComputeHash(Encoding.UTF8.GetBytes(id.ToString() + Seed)).Select(b => b.ToString("X2"))) + id.ToString();
}
}
例如,如果ID为123
,则会生成"989178D90470D8777F77C972AF46C4DED41EF0D9123"
。
现在使用以下地址映射回密钥:
private static bool GetIDFromProtectedID(string str, out int id)
{
int chkID;
if(int.TryParse(str.Substring(40), out chkID))
{
using(var sha = System.Security.Cryptography.SHA1.Create())
{
if(string.Join("", sha.ComputeHash(Encoding.UTF8.GetBytes(chkID.ToString() + Seed)).Select(b => b.ToString("X2"))) == str.Substring(0, 40))
{
id = chkID;
return true;
}
}
}
id = 0;
return false;
}
对于"989178D90470D8777F77C972AF46C4DED41EF0D9123"
,这会返回true
并将id
参数设置为123
。对于"989178D90470D8777F77C972AF46C4DED41EF0D9122"
(因为我试图猜测攻击您网站的ID),它会返回false
并将id
设置为0
。 (密钥122
的正确ID为"F8AD0F55CA1B9426D18F684C4857E0C4D43984BA122"
,从123
}看到这一点并不容易猜到。
如果需要,可以删除输出的40个字符中的某些字符以生成较小的ID。这使得安全性降低(攻击者可能使用较少的ID来进行暴力攻击),但对于许多用途来说仍然是合理的。
显然,你应该使用Seed
的不同值而不是这里,以便阅读此答案的人不能用它来预测你的身份证;更改种子,更改ID。一旦设置而不更改系统中的每个ID,就无法更改Seed
。有时候这是一件好事(如果标识符从来没有意味着具有长期价值),但通常它很糟糕(你只是在网站中使用它的每一页都是404d。)
答案 3 :(得分:1)
我会将逻辑移到数据库中。即使数据库仍然需要检查密钥的存在,它在那时已经在数据库操作中,因此没有来回发生。
此外,如果这个随机生成的密钥有一个索引,一个简单的if存在将足够快,以确定密钥是否已被使用过。
答案 4 :(得分:1)
您可以执行以下操作:
生成您的字符串以及将以下内容连接到其上
string newUniqueString = string.Format("{0}{1}", result, DateTime.Now.ToString("yyyyMMddHHmmssfffffff"));
这样,你再也不会拥有同样的钥匙了!
或使用
var StringGuid = Guid.NewGuid();
答案 5 :(得分:1)
您已使用Random
提交典型罪:不应创建Random
个实例
每次他们想要生成随机值(否则会有严重倾斜的分布多次重复)。将Random
放在表单函数中:
// Let Generator be thread safe
private static ThreadLocal<Random> s_Generator = new ThreadLocal<Random>(
() => new Random());
public static Random Generator {
get {
return s_Generator.Value;
}
}
// For repetition test. One can remove repetion test if
// number of generated ids << 15000
private HashSet<String> m_UsedIds = new HashSet<String>();
public string GetNewId() {
int codeLength = 5;
string chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
while (true) {
StringBuilder builder = new StringBuilder(codeLength);
for (int i = 0; i < codeLength; ++i)
builder.Append(chars[Generator.Next(chars.Length)]); // <- same generator for all calls
result = string.Format("{0}{1}", "T", builder.ToString());
// Test if random string in fact a repetition
if (m_UsedIds.Contains(result))
continue;
m_UsedIds.Add(result);
return result;
}
}
对于codeLength = 5
,可能Math.Pow(chars.Length, codeLength)
字符串 (Math.Pow(36, 5) == 60466176 = 6e7)
。根据生日悖论
你可以期待大约2 * sqrt(possible strings) == 2 * sqrt(6e7) == 15000
的第一次重复。如果没问题,您可以跳过重复测试,否则HashSet<String>
可以
是一个解决方案。