Performance 加速Haskell PBKDF2算法
我在Haskell中编写了PBKDF2算法的新版本。它几乎通过了中列出的所有HMAC-SHA-1测试向量,但效率不高。如何改进代码 当我在测试向量上运行它时,第三种情况(见下文)永远不会结束(我让它在2010 Macbook Pro上运行了1/2个多小时) 我相信Performance 加速Haskell PBKDF2算法,performance,haskell,pbkdf2,Performance,Haskell,Pbkdf2,我在Haskell中编写了PBKDF2算法的新版本。它几乎通过了中列出的所有HMAC-SHA-1测试向量,但效率不高。如何改进代码 当我在测试向量上运行它时,第三种情况(见下文)永远不会结束(我让它在2010 Macbook Pro上运行了1/2个多小时) 我相信foldl'是我的问题。foldr性能会更好,还是需要使用可变数组 {-#语言模式} {-版权所有2013,马里兰州G.拉尔夫·昆茨。保留所有权利。LGPL许可证。-} 模块加密在哪里 导入Codec.Utils(八位字节) 导入符合条
foldl'
是我的问题。foldr
性能会更好,还是需要使用可变数组
{-#语言模式}
{-版权所有2013,马里兰州G.拉尔夫·昆茨。保留所有权利。LGPL许可证。-}
模块加密在哪里
导入Codec.Utils(八位字节)
导入符合条件的数据。二进制为B(编码)
导入数据位(异或)
将限定数据.ByteString.Lazy.Char8作为C(包)导入
将限定的Data.ByteString.Lazy导入为L(解包)
导入数据列表(foldl')
导入Data.HMAC(HMAC_sha1)
导入Text.Bytedump(dumpRaw)
--将PBKDF2计算为十六进制字符串
pbkdf2
::([Octet]->[Octet]->[Octet])--伪随机函数(HMAC)
->Int—以字节为单位的哈希长度
->字符串--密码
->绳子--盐
->Int——迭代
->Int—以字节为单位的派生密钥长度
->串
pbkdf2 prf hashLength密码salt密钥长度=
让
passwordOctets=stringToOctets密码
saltOctets=stringToOctets盐
总积木=
上限$(fromIntegral keyLength::Double)/fromIntegral hashLength
块迭代器消息acc=
foldl'(\(a,m)\->
设!m'=prf密码八位组m
in(带异或a m',m')(acc,message)[1..iterations]
在里面
dumpRaw$take keydlength$foldl'(\acc block->
acc++fst(块迭代器(saltOctets++intToOctets块)
(复制hashLength 0))[[1..totalBlocks]
哪里
intToOctets::Int->[Octet]
intToOctets i=
让a=L.打开包装。B.encode$i
落下(长度a-4)a
stringToOctets::String->[Octet]
stringToOctets=L.打开包装。C.包装
--使用HMAC和SHA-1将PBKDF2计算为十六进制字符串
pbkdf2HmacSha1
::字符串--密码
->绳子--盐
->Int——迭代
->Int—以字节为单位的派生密钥长度
->串
pbkdf2HmacSha1=
pbkdf2 hmac_sha1 20
第三测试向量
我能够在MacBookPro上在约16分钟内完成:
% time Crypto-Main
eefe3d61cd4da4e4e9945b3d6ba2158c2634e984
./Crypto-Main 1027.30s user 15.34s system 100% cpu 17:22.61 total
通过改变折叠的严格程度:
let
-- ...
blockIterator message acc = foldl' (zipWith' xor) acc ms
where ms = take iterations . tail $ iterate (prf passwordOctets) message
zipWith' f as bs = let cs = zipWith f as bs in sum cs `seq` cs
in
dumpRaw $ take keyLength $ foldl' (\acc block ->
acc ++ blockIterator (saltOctets ++ intToOctets block)
(replicate hashLength 0)) [] [1..totalBlocks]
请注意,我是如何使用xor强制对每个zipw进行完整计算的。为了计算
将cs
求和到WHNF中,我们必须知道cs
中每个元素的确切值
这可以防止建立一个thunk链,我认为您现有的代码正试图这样做,但失败了,因为foldl'
只会强制累加器进入WHNF。因为你的累加器是一对,WHNF只是(\u thunk,\u另一个\u thunk)
,所以你的中间thunk没有被强迫。快速观察:你在foldl'
参数中没有真正强迫m
。因为m
是一个你需要使用的列表,例如deepSeq
来强制执行所有功能。可能我在这一点上完全错了,因为我不是一个真正强大的Haskeller,但是你在一个函数中塞进的数量让它有点难以理解,如果你把它分成更小更简单的部分,你可能会发现改进的空间变得非常明显。我强烈建议将算法改为通过testring来处理ByteString
s(这可以很容易地看作Word8
s的向量),而不是Octet
s的列表。我无法想象这是速度缓慢的唯一原因,尽管这也是一个需要测试的有点棘手的算法,因为你可以调整它以花费你喜欢的时间。我将看看如何使用ByteString
。你比我更有耐心。我用原始版本等了大约30分钟,但没有完成。我来看看你的建议。谢谢。关于这个问题,Petr Pudlák建议更好的解决方案可能是使用非固定ST数组。我必须看一看,我实际上是用非固定的ST数组重写了这个函数,但它仍然很慢。我需要运行性能测试来找出原因。
let
-- ...
blockIterator message acc = foldl' (zipWith' xor) acc ms
where ms = take iterations . tail $ iterate (prf passwordOctets) message
zipWith' f as bs = let cs = zipWith f as bs in sum cs `seq` cs
in
dumpRaw $ take keyLength $ foldl' (\acc block ->
acc ++ blockIterator (saltOctets ++ intToOctets block)
(replicate hashLength 0)) [] [1..totalBlocks]