我正在实施一个加密安全的shuffle例程,并有几个问题:
我使用的方法是加权排序,其中每个权重都是加密强随机数。
我通过使用列表中的项目数(X)并将其插入此公式= log10(X!) / log10(2)
来计算每个权重所需的位数。例如,52卡片组每重量需要log10(52!) / log10(2) = 225.58100312370276194634244437667
位。我总是把它四舍五入,因为有些小部分无法表示。我总是在纠正中是正确的还是给了我太多的东西?
从硬件rng中检索位可能不太实用,因此必须检索字节。与前面的示例226 / 8 = 28.25
一样,我们有28个完整字节,还有一个额外的字节来获取剩余的2个比特。我正在做的是丢弃最后一个字节的未使用的高6位,这样只有2个位被附加到数字上。我是在纠正丢弃这些位还是通过这样做来破坏熵?
我按照分配给每个数字的(左边填充,全部大写,ASCII)十六进制的权重字符串进行排序。这似乎产生了正确的排序顺序。我应该注意以这种方式排序字符串吗?
我应该使用硬件rng来测试它产生的数字的熵,但我使用的是MS RNGCryptoServiceProvider。是否有更好的加密RNG与.NET一起使用?
要从加密和排序列表中“选择”一个数字,我只是选择索引0.是否有更好的加密随机方法来选择列表中的项目?
如果我能帮助澄清或者如果这是错误的网站,请告诉我,请告诉我更好的网站。
这是我的代码,如果它有助于说明我所说的VB.NET控制台应用程序:
Imports System.Security.Cryptography
Module Module1
Public Class Ball
Public Weight As String
Public Value As Integer
Public Sub New(ByVal _Weight As String, ByVal _Value As Integer)
Weight = _Weight
Value = _Value
End Sub
End Class
Public Class BallComparer_Weight
Implements IComparer(Of Ball)
Public Function Compare(x As Ball, y As Ball) As Integer Implements System.Collections.Generic.IComparer(Of Ball).Compare
If x.Weight > y.Weight Then
Return 1
ElseIf x.Weight < y.Weight Then
Return -1
Else
Return 0
End If
End Function
End Class
Public Class BallComparer_Value
Implements IComparer(Of Ball)
Public Function Compare(x As Ball, y As Ball) As Integer Implements System.Collections.Generic.IComparer(Of Ball).Compare
If x.Value > y.Value Then
Return 1
ElseIf x.Value < y.Value Then
Return -1
Else
Return 0
End If
End Function
End Class
Public Function Weight(ByVal rng As RNGCryptoServiceProvider, ByVal bits As Integer) As String
' generate a "cryptographically" random string of length 'bits' (should be using hardware rng)
Dim remainder As Integer = bits Mod 8
Dim quotient As Integer = bits \ 8
Dim byteCount As Integer = quotient + If(remainder <> 0, 1, 0)
Dim bytes() As Byte = New Byte(byteCount - 1) {}
Dim result As String = String.Empty
rng.GetBytes(bytes)
For index As Integer = bytes.Length - 1 To 0 Step -1
If index = bytes.Length - 1 Then
' remove upper `remainder` bits from upper byte
Dim value As Byte = (bytes(0) << remainder) >> remainder
result &= value.ToString("X2")
Else
result &= bytes(index).ToString("X2")
End If
Next
Return result
End Function
Public Function ContainsValue(ByVal lst As List(Of Ball), ByVal value As Integer) As Boolean
For i As Integer = 0 To lst.Count - 1
If lst(i).Value = value Then
Return True
End If
Next
Return False
End Function
Sub Main()
Dim valueComparer As New BallComparer_Value()
Dim weightComparer As New BallComparer_Weight()
Dim picks As New List(Of Ball)
Dim balls As New List(Of Ball)
' number of bits after each "ball" is drawn
Dim bits() As Integer = New Integer() {364, 358, 351, 345, 339}
Using rng As New RNGCryptoServiceProvider
While True
picks.Clear()
' simulate random balls
'log10(75!) / log10(2) = number of bits required for weighted random shuffle (reduces each time ball is pulled) = 363.40103411549404253061653790169 = 364
For i As Integer = 0 To 4
balls.Clear()
For value As Integer = 1 To 75
' do not add previous picks
If Not ContainsValue(picks, value) Then
balls.Add(New Ball(Weight(rng, bits(i)), value))
End If
Next
balls.Sort(weightComparer)
'For Each x As Ball In balls
' Console.WriteLine(x.Weight)
'Next
'Console.ReadLine()
' choose first ball in sorted list
picks.Add(balls(0))
Next
picks.Sort(valueComparer)
' simulate random balls
'log10(15!) / log10(2) = number of bits required for weighted random shuffle = 40.250140469882621763813506287601 = 41 bits required for megaball
balls.Clear()
For value As Integer = 1 To 15
balls.Add(New Ball(Weight(rng, 41), value))
Next
balls.Sort(weightComparer)
' print to stdout
For i As Integer = 0 To 4
Console.Write(picks(i).Value.ToString("D2") & " "c)
Next
Console.WriteLine(balls(0).Value.ToString("D2"))
End While
End Using
End Sub
End Module
答案 0 :(得分:1)
你的基本想法似乎很合理。但是:
你的体重中不需要那么多位。你需要的只是make collisions unlikely,即每个项目的⌈log 2 n 2 ⌉比特,加上一些好的衡量标准。对于52张卡,最低限度为每卡约12位,16位将使碰撞概率降至约4%。这应该是充足的,至少只要你明确检查碰撞。
你应检查是否存在冲突(即两个具有相同随机排序键的项目),如果找到,则重新启动shuffle。或者,你可以增加排序键的长度,使得碰撞的概率可以忽略不计。
是的,以十六进制编码排序键应该没问题。实际上,只要它是确定性的(即总是为相同的随机数提供相同的编码),对它们进行编码的如何并不重要。也就是说,既然您知道随机位串的长度,为什么不将它们存储在原始二进制中呢? (特别是,如果每个键需要少于64位,则可以将每个键存储在适当大小的整数变量中。)
如果你想避免使用side channel attacks,你应该选择一种可以在恒定时间内运行并且功耗不变的排序方法,而不管最终的顺序是什么。这说起来容易做起来难,因为大多数常见的排序算法都没有接近恒定时间。也就是说,根据您的应用程序,此类攻击可能会或可能不会起作用(但在您考虑问题之前不要排除这些攻击!)。
安全地改组阵列的另一种方法是使用带有加密安全RNG的Fisher–Yates shuffle。这种方法可以减少比特浪费,并且更容易在恒定时间内实现(或者至少在时间上独立于输出;见下文),但它确实需要您的生成器能够从任何整数范围返回无偏样本,而不仅仅是来自具有两倍幂的范围。 (Rejection sampling是实现此目的的一种方式 - 它不是恒定时间,但可以显示所需时间不会显示有关最终输出的任何内容,因此它仍然可以。)
最后,如果你只需要来自混洗数组的一个元素,所有这些都是不必要的:只需为数组选择一个随机索引(统一,例如使用上面提到的拒绝抽样方法)和返回相应的元素。