[KDF] move PBKDF2 and Scrypt to not be pinned to ByteString

This commit is contained in:
Vincent Hanquez 2015-05-22 15:19:42 +01:00
parent 1dacb7fa94
commit 02956f9ef0
4 changed files with 40 additions and 35 deletions

View File

@ -17,47 +17,49 @@ module Crypto.KDF.PBKDF2
import Data.Word import Data.Word
import Data.Bits import Data.Bits
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import Foreign.Marshal.Alloc import Foreign.Marshal.Alloc
import Foreign.Ptr (plusPtr) import Foreign.Ptr (plusPtr)
import Crypto.Hash (HashAlgorithm) import Crypto.Hash (HashAlgorithm)
import qualified Crypto.MAC.HMAC as HMAC import qualified Crypto.MAC.HMAC as HMAC
import Crypto.Internal.ByteArray (ByteArray) import Crypto.Internal.ByteArray (ByteArray, ByteArrayAccess, Bytes)
import qualified Crypto.Internal.ByteArray as B (allocAndFreeze, convert, withByteArray) import qualified Crypto.Internal.ByteArray as B
import Data.Memory.PtrMethods import Data.Memory.PtrMethods
-- | The PRF used for PBKDF2 -- | The PRF used for PBKDF2
type PRF = B.ByteString -- ^ the password parameters type PRF password =
-> B.ByteString -- ^ the content password -- ^ the password parameters
-> B.ByteString -- ^ prf(password,content) -> Bytes -- ^ the content
-> Bytes -- ^ prf(password,content)
-- | PRF for PBKDF2 using HMAC with the hash algorithm as parameter -- | PRF for PBKDF2 using HMAC with the hash algorithm as parameter
prfHMAC :: HashAlgorithm a prfHMAC :: (HashAlgorithm a, ByteArrayAccess password)
=> a -- ^ the Hash Algorithm to use with HMAC => a
-> PRF -- ^ the PRF functiont o use -> PRF password
prfHMAC alg k = hmacIncr alg (HMAC.initialize k) prfHMAC alg k = hmacIncr alg (HMAC.initialize k)
where hmacIncr :: HashAlgorithm a => a -> HMAC.Context a -> (ByteString -> ByteString) where hmacIncr :: HashAlgorithm a => a -> HMAC.Context a -> (Bytes -> Bytes)
hmacIncr _ !ctx = \b -> B.convert $ HMAC.finalize $ HMAC.update ctx b hmacIncr _ !ctx = \b -> B.convert $ HMAC.finalize $ HMAC.update ctx b
-- | Parameters for PBKDF2 -- | Parameters for PBKDF2
data Parameters = Parameters data Parameters = Parameters
{ password :: ByteString -- ^ Password (bytes encoded) { iterCounts :: Int -- ^ the number of user-defined iterations for the algorithms. e.g. WPA2 uses 4000.
, salt :: ByteString -- ^ Salt (bytes encoded) , outputLength :: Int -- ^ the number of bytes to generate out of PBKDF2
, iterCounts :: Int -- ^ the number of user-defined iterations for the algorithms. e.g. WPA2 uses 4000.
, outputLength :: Int -- ^ the number of bytes to generate out of PBKDF2
} }
-- | generate the pbkdf2 key derivation function from the output -- | generate the pbkdf2 key derivation function from the output
generate :: ByteArray ba => PRF -> Parameters -> ba generate :: (ByteArrayAccess password, ByteArrayAccess salt, ByteArray ba)
generate prf params = => PRF password
-> Parameters
-> password
-> salt
-> ba
generate prf params password salt =
B.allocAndFreeze (outputLength params) $ \p -> do B.allocAndFreeze (outputLength params) $ \p -> do
memSet p 0 (outputLength params) memSet p 0 (outputLength params)
loop 1 (outputLength params) p loop 1 (outputLength params) p
where where
!runPRF = prf (password params) !runPRF = prf password
!hLen = B.length $ runPRF B.empty !hLen = B.length $ runPRF B.empty
-- run the following f function on each complete chunk. -- run the following f function on each complete chunk.
@ -76,21 +78,22 @@ generate prf params =
let uData = runPRF uprev let uData = runPRF uprev
B.withByteArray uData $ \u -> memXor p p u hLen B.withByteArray uData $ \u -> memXor p p u hLen
applyMany (i-1) uData applyMany (i-1) uData
applyMany (iterCounts params) (salt params `B.append` toBS iterNb) applyMany (iterCounts params) (B.convert salt `B.append` toBS iterNb)
loop (iterNb+1) (len - hLen) (p `plusPtr` hLen) loop (iterNb+1) (len - hLen) (p `plusPtr` hLen)
partial iterNb len p = allocaBytesAligned hLen 8 $ \tmp -> do partial iterNb len p = allocaBytesAligned hLen 8 $ \tmp -> do
let applyMany 0 _ = return () let applyMany :: Int -> Bytes -> IO ()
applyMany 0 _ = return ()
applyMany i uprev = do applyMany i uprev = do
let uData = runPRF uprev let uData = runPRF uprev
B.withByteArray uData $ \u -> memXor tmp tmp u hLen B.withByteArray uData $ \u -> memXor tmp tmp u hLen
applyMany (i-1) uData applyMany (i-1) uData
memSet tmp 0 hLen memSet tmp 0 hLen
applyMany (iterCounts params) (salt params `B.append` toBS iterNb) applyMany (iterCounts params) (B.convert salt `B.append` toBS iterNb)
memCopy p tmp len memCopy p tmp len
-- big endian encoding of Word32 -- big endian encoding of Word32
toBS :: Word32 -> ByteString toBS :: ByteArray ba => Word32 -> ba
toBS w = B.pack [a,b,c,d] toBS w = B.pack [a,b,c,d]
where a = fromIntegral (w `shiftR` 24) where a = fromIntegral (w `shiftR` 24)
b = fromIntegral ((w `shiftR` 16) .&. 0xff) b = fromIntegral ((w `shiftR` 16) .&. 0xff)

View File

@ -15,7 +15,6 @@ module Crypto.KDF.Scrypt
) where ) where
import Data.Word import Data.Word
import Data.ByteString (ByteString)
import Foreign.Marshal.Alloc import Foreign.Marshal.Alloc
import Foreign.Ptr (Ptr, plusPtr) import Foreign.Ptr (Ptr, plusPtr)
import Control.Monad (forM_) import Control.Monad (forM_)
@ -23,30 +22,33 @@ import Control.Monad (forM_)
import Crypto.Hash (SHA256(..)) import Crypto.Hash (SHA256(..))
import qualified Crypto.KDF.PBKDF2 as PBKDF2 import qualified Crypto.KDF.PBKDF2 as PBKDF2
import Crypto.Internal.Compat (popCount, unsafeDoIO) import Crypto.Internal.Compat (popCount, unsafeDoIO)
import Crypto.Internal.ByteArray (ByteArray, ByteArrayAccess)
import qualified Crypto.Internal.ByteArray as B import qualified Crypto.Internal.ByteArray as B
-- | Parameters for Scrypt -- | Parameters for Scrypt
data Parameters = Parameters data Parameters = Parameters
{ password :: ByteString -- ^ Password (bytes encoded) { n :: Word64 -- ^ Cpu/Memory cost ratio. must be a power of 2 greater than 1. also known as N.
, salt :: ByteString -- ^ Salt (bytes encoded) , r :: Int -- ^ Must satisfy r * p < 2^30
, n :: Word64 -- ^ Cpu/Memory cost ratio. must be a power of 2 greater than 1. also known as N. , p :: Int -- ^ Must satisfy r * p < 2^30
, r :: Int -- ^ Must satisfy r * p < 2^30 , outputLength :: Int -- ^ the number of bytes to generate out of Scrypt
, p :: Int -- ^ Must satisfy r * p < 2^30
, outputLength :: Int -- ^ the number of bytes to generate out of Scrypt
} }
foreign import ccall "cryptonite_scrypt_smix" foreign import ccall "cryptonite_scrypt_smix"
ccryptonite_scrypt_smix :: Ptr Word8 -> Word32 -> Word64 -> Ptr Word8 -> Ptr Word8 -> IO () ccryptonite_scrypt_smix :: Ptr Word8 -> Word32 -> Word64 -> Ptr Word8 -> Ptr Word8 -> IO ()
-- | Generate the scrypt key derivation data -- | Generate the scrypt key derivation data
generate :: Parameters -> ByteString generate :: (ByteArrayAccess password, ByteArrayAccess salt, ByteArray output)
generate params => Parameters
-> password
-> salt
-> output
generate params password salt
| r params * p params >= 0x40000000 = | r params * p params >= 0x40000000 =
error "Scrypt: invalid parameters: r and p constraint" error "Scrypt: invalid parameters: r and p constraint"
| popCount (n params) /= 1 = | popCount (n params) /= 1 =
error "Scrypt: invalid parameters: n not a power of 2" error "Scrypt: invalid parameters: n not a power of 2"
| otherwise = unsafeDoIO $ do | otherwise = unsafeDoIO $ do
let b = PBKDF2.generate prf (PBKDF2.Parameters (password params) (salt params) 1 intLen) :: B.Bytes let b = PBKDF2.generate prf (PBKDF2.Parameters 1 intLen) password salt :: B.Bytes
newSalt <- B.copy b $ \bPtr -> newSalt <- B.copy b $ \bPtr ->
allocaBytesAligned (128*(fromIntegral $ n params)*(r params)) 8 $ \v -> allocaBytesAligned (128*(fromIntegral $ n params)*(r params)) 8 $ \v ->
allocaBytesAligned (256*r params) 8 $ \xy -> do allocaBytesAligned (256*r params) 8 $ \xy -> do
@ -54,7 +56,7 @@ generate params
ccryptonite_scrypt_smix (bPtr `plusPtr` (i * 128 * (r params))) ccryptonite_scrypt_smix (bPtr `plusPtr` (i * 128 * (r params)))
(fromIntegral $ r params) (n params) v xy (fromIntegral $ r params) (n params) v xy
return $ PBKDF2.generate prf (PBKDF2.Parameters (password params) newSalt 1 (outputLength params)) return $ PBKDF2.generate prf (PBKDF2.Parameters 1 (outputLength params)) password (newSalt :: B.Bytes)
where prf = PBKDF2.prfHMAC SHA256 where prf = PBKDF2.prfHMAC SHA256
intLen = p params * 128 * r params intLen = p params * 128 * r params
{-# NOINLINE generate #-} {-# NOINLINE generate #-}

View File

@ -49,7 +49,7 @@ tests = testGroup "PBKDF2"
where katTests prf vects = map (toKatTest prf) $ zip is vects where katTests prf vects = map (toKatTest prf) $ zip is vects
toKatTest prf (i, ((pass, salt, iter, dkLen), output)) = toKatTest prf (i, ((pass, salt, iter, dkLen), output)) =
testCase (show i) (output @=? PBKDF2.generate prf (PBKDF2.Parameters pass salt iter dkLen)) testCase (show i) (output @=? PBKDF2.generate prf (PBKDF2.Parameters iter dkLen) pass salt)
is :: [Int] is :: [Int]
is = [1..] is = [1..]

View File

@ -30,4 +30,4 @@ vectors =
tests = testGroup "Scrypt" tests = testGroup "Scrypt"
$ map toCase $ zip [(1::Int)..] vectors $ map toCase $ zip [(1::Int)..] vectors
where toCase (i, ((pass,salt,n,r,p,dklen), output)) = where toCase (i, ((pass,salt,n,r,p,dklen), output)) =
testCase (show i) (output @=? Scrypt.generate (Scrypt.Parameters pass salt n r p dklen)) testCase (show i) (output @=? Scrypt.generate (Scrypt.Parameters n r p dklen) pass salt)