我们的一个应用程序使用 SQL Server 作为后端,并且该应用程序需要一个 4 位数的数字 PIN 才能创建使用密钥(或多或少)的员工。系统本身默认所有 PIN 为 0000,因为如果您使用的设备没有键盘,这就是“不需要 PIN”的神奇值。
我们遇到了员工没有更新他们的 PIN 的问题(并且厌倦了等待供应商纠正问题),因此我们使用 SQL Server 生成我们自己的 PIN,使用计划作业更新任何 0000 的 PIN。
PIN不需要是安全的(毕竟 4 位数的 PIN 有多安全),它们只需要不重要。但是,没有理由不考虑安全问题,我喜欢花时间了解我正在做的事情,至少是半胜任的:
当前的解决方案(不是我!)是:
replace(str(round(rand(checksum(newid()))*10000,0),4), ' ','0')
这有几个问题。当然,密码函数rand()也不newid()是,但实际上需要更改的问题是,****由于str()函数的怪癖,这偶尔会产生一个 pin of。
现在,SQL Server 确实具有crypt_gen_random()返回固定字节数的安全加密生成数字的功能。这让我觉得这样的事情应该有效:
format(cast(crypt_gen_random(2) as int) % 10000,'0000')
我看到的问题crypt_gen_random(2)基本上是“在 0 到 65535 之间选择一个数字”。然而,在该空间中,模 10000 存在偏差。0 到 5535 之间的每个结果有 7 个实例,但 5536 和 9999 之间的每个结果只有 6 个实例。相差 16%。
现在,我可以想到一个可能的简单修复方法,但我不确定是否有首选方法。我不是安全程序员。可以简单地增加生成的字节数吗?
format(cast(crypt_gen_random(7) as bigint) % 10000,'0000')
[注意:我使用 bigint 而不是 int,因为它支持长达 8 个字节的数字。我只生成 7 个字节而不是 8 个字节来防止负数。]
如果我生成 7 个字节而不是 2 个字节,我将生成一个介于 0 和 72057594037927935 (16^14) 之间的数字。问题仍然存在——在 0 和 7935 之间有 7205759403793 个值,但在 7936 和 9999 之间只有 7205759403792——但稀释程度如此之大,这并不重要。我们大约是 10^-12。
不过,就像我说的,我不是安全程序员。我是一名 DBA、分析师和管理员,我对安全编程的所有了解是,我没有资格知道我何时会犯安全错误。
有没有更好的方法来做到这一点?
我的 T-SQL 解决方案基本上看起来像这样:
declare @candidate_pin int = cast(crypt_gen_random(2) as int)
while @candidate_pin not between 1 and 9999
set @candidate_pin = cast(crypt_gen_random(2) as int)
select format(@candidate_pin, '0000') as [pin]
我希望避免使用过程逻辑而不是更简单的声明性语句,但我不会尝试将其强制为递归 CTE 或其他声明性解决方案。
我并不特别关心这里的性能。如果我们在一个季度内需要 100 个新 PIN,就会发生一些奇怪的事情。