add PBKDF2

This commit is contained in:
Vincent Hanquez 2014-07-18 15:26:01 +01:00
parent 8779ba8927
commit a0ce598e37
4 changed files with 120 additions and 5 deletions

90
Crypto/KDF/PBKDF2.hs Normal file
View File

@ -0,0 +1,90 @@
{-# LANGUAGE BangPatterns #-}
-- |
-- Module : Crypto.KDF.PBKDF2
-- License : BSD-style
-- Maintainer : Vincent Hanquez <vincent@snarc.org>
-- Stability : experimental
-- Portability : unknown
--
-- Password Based Key Derivation Function 2
--
module Crypto.KDF.PBKDF2
( PRF
, prfHMAC
, Parameters(..)
, generate
) where
import Data.Word
import Data.Bits
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import qualified Data.ByteString.Internal as B (unsafeCreate, memset)
import Data.Byteable
import Foreign.Storable
import Foreign.Ptr (Ptr, plusPtr)
import Control.Applicative
import Control.Monad (forM_, void)
import Crypto.Hash (HashAlgorithm)
import qualified Crypto.MAC.HMAC as HMAC
-- | The PRF used for PBKDF2
type PRF = B.ByteString -- ^ the password parameters
-> B.ByteString -- ^ the content
-> B.ByteString -- ^ prf(password,content)
-- | PRF for PBKDF2 using HMAC with the hash algorithm as parameter
prfHMAC :: HashAlgorithm a
=> a -- ^ the Hash Algorithm to use with HMAC
-> PRF -- ^ the PRF functiont o use
prfHMAC alg k = hmacIncr alg (HMAC.initialize k)
where hmacIncr :: HashAlgorithm a => a -> HMAC.Context a -> (ByteString -> ByteString)
hmacIncr _ !ctx = \b -> toBytes $ HMAC.finalize $ HMAC.update ctx b
-- | Parameters for PBKDF2
data Parameters = Parameters
{ password :: ByteString
, salt :: ByteString
, iterCounts :: Int
, outputLength :: Int
}
-- | generate the pbkdf2 key derivation function from the output
generate :: PRF -> Parameters -> B.ByteString
generate prf params =
B.take (outputLength params) $ B.concat $ map f [1..l]
where
!runPRF = prf (password params)
!hLen = B.length $ runPRF B.empty
-- f(pass,salt,c,i) = U1 xor U2 xor .. xor Uc
-- U1 = PRF(pass,salt || BE32(i))
-- Uc = PRF(pass,Uc-1)
f iterNb = B.unsafeCreate hLen $ \dst -> do
let applyMany 0 _ = return ()
applyMany i uprev =
let u = runPRF uprev
in bsXor dst u >> applyMany (i-1) u
void $ B.memset dst 0 (fromIntegral hLen)
applyMany (iterCounts params) (salt params `B.append` toBS iterNb)
-- a mutable version of xor, that allow to not reallocate
-- the accumulate buffer.
bsXor :: Ptr Word8 -> ByteString -> IO ()
bsXor d sBs = withBytePtr sBs $ \s ->
forM_ [0..hLen-1] $ \i -> do
v <- xor <$> peek (s `plusPtr` i) <*> peek (d `plusPtr` i)
poke (d `plusPtr` i) (v :: Word8)
-- count the number of blocks necessary
l = let (q,rema) = (outputLength params) `divMod` hLen
in fromIntegral (q + if rema > 0 then 1 else 0)
-- big endian encoding of Word32
toBS :: Word32 -> ByteString
toBS w = B.pack [a,b,c,d]
where a = fromIntegral (w `shiftR` 24)
b = fromIntegral ((w `shiftR` 16) .&. 0xff)
c = fromIntegral ((w `shiftR` 8) .&. 0xff)
d = fromIntegral (w .&. 0xff)

View File

@ -24,6 +24,7 @@ Library
Crypto.Cipher.Salsa
Crypto.MAC.Poly1305
Crypto.MAC.HMAC
Crypto.KDF.PBKDF2
Crypto.Hash
Crypto.Hash.SHA1
Crypto.Hash.SHA224

View File

@ -1,17 +1,21 @@
{-# LANGUAGE OverloadedStrings #-}
-- from <http://www.ietf.org/rfc/rfc6070.txt>
module KAT_PBKDF2 (vectors) where
module KAT_PBKDF2 (tests) where
import Crypto.Hash (SHA1(..), SHA256(..))
import qualified Crypto.KDF.PBKDF2 as PBKDF2
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import Data.ByteString.Char8 ()
import Test.Tasty
import Test.Tasty.HUnit
type VectParams = (ByteString, ByteString, Int, Int)
-- PBKDF-HMAC-SHA1
vectors :: [ (VectParams, ByteString) ]
vectors =
vectors_hmac_sha1 :: [ (VectParams, ByteString) ]
vectors_hmac_sha1 =
[
( ("password","salt",2,20)
, "\xea\x6c\x01\x4d\xc7\x2d\x6f\x8c\xcd\x1e\xd9\x2a\xce\x1d\x41\xf0\xd8\xde\x89\x57"
@ -28,4 +32,23 @@ vectors =
)
]
vectors_hmac_sha256 =
[ ( ("password", "salt", 2, 32)
, "\xae\x4d\x0c\x95\xaf\x6b\x46\xd3\x2d\x0a\xdf\xf9\x28\xf0\x6d\xd0\x2a\x30\x3f\x8e\xf3\xc2\x51\xdf\xd6\xe2\xd8\x5a\x95\x47\x4c\x43"
)
, ( ("passwordPASSWORDpassword", "saltSALTsaltSALTsaltSALTsaltSALTsalt", 4096, 40)
, "\x34\x8c\x89\xdb\xcb\xd3\x2b\x2f\x32\xd8\x14\xb8\x11\x6e\x84\xcf\x2b\x17\x34\x7e\xbc\x18\x00\x18\x1c\x4e\x2a\x1f\xb8\xdd\x53\xe1\xc6\x35\x51\x8c\x7d\xac\x47\xe9"
)
]
tests = testGroup "PBKDF2"
[ testGroup "KATs-HMAC-SHA1" (katTests (PBKDF2.prfHMAC SHA1) vectors_hmac_sha1)
, testGroup "KATs-HMAC-SHA256" (katTests (PBKDF2.prfHMAC SHA256) vectors_hmac_sha256)
]
where katTests prf vects = map (toKatTest prf) $ zip is vects
toKatTest prf (i, ((pass, salt, iter, dkLen), output)) =
testCase (show i) (output @=? PBKDF2.generate prf (PBKDF2.Parameters pass salt iter dkLen))
is :: [Int]
is = [1..]

View File

@ -70,6 +70,7 @@ tests = testGroup "cryptonite"
]
, KATHash.tests
, KAT_HMAC.tests
, KAT_PBKDF2.tests
]
where chachaRunSimple expected rounds klen nonceLen =
let chacha = ChaCha.initialize rounds (B.replicate klen 0) (B.replicate nonceLen 0)