From 02956f9ef05a5acda50667eeded425a3fc574b82 Mon Sep 17 00:00:00 2001 From: Vincent Hanquez Date: Fri, 22 May 2015 15:19:42 +0100 Subject: [PATCH] [KDF] move PBKDF2 and Scrypt to not be pinned to ByteString --- Crypto/KDF/PBKDF2.hs | 47 +++++++++++++++++++++++--------------------- Crypto/KDF/Scrypt.hs | 24 +++++++++++----------- tests/KAT_PBKDF2.hs | 2 +- tests/KAT_Scrypt.hs | 2 +- 4 files changed, 40 insertions(+), 35 deletions(-) diff --git a/Crypto/KDF/PBKDF2.hs b/Crypto/KDF/PBKDF2.hs index 4b0c028..2f5c183 100644 --- a/Crypto/KDF/PBKDF2.hs +++ b/Crypto/KDF/PBKDF2.hs @@ -17,47 +17,49 @@ module Crypto.KDF.PBKDF2 import Data.Word import Data.Bits -import Data.ByteString (ByteString) -import qualified Data.ByteString as B import Foreign.Marshal.Alloc import Foreign.Ptr (plusPtr) import Crypto.Hash (HashAlgorithm) import qualified Crypto.MAC.HMAC as HMAC -import Crypto.Internal.ByteArray (ByteArray) -import qualified Crypto.Internal.ByteArray as B (allocAndFreeze, convert, withByteArray) +import Crypto.Internal.ByteArray (ByteArray, ByteArrayAccess, Bytes) +import qualified Crypto.Internal.ByteArray as B import Data.Memory.PtrMethods -- | The PRF used for PBKDF2 -type PRF = B.ByteString -- ^ the password parameters - -> B.ByteString -- ^ the content - -> B.ByteString -- ^ prf(password,content) +type PRF password = + password -- ^ the password parameters + -> Bytes -- ^ the content + -> Bytes -- ^ 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 :: (HashAlgorithm a, ByteArrayAccess password) + => a + -> PRF password 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 -- | Parameters for PBKDF2 data Parameters = Parameters - { password :: ByteString -- ^ Password (bytes encoded) - , salt :: ByteString -- ^ Salt (bytes encoded) - , 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 + { 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 :: ByteArray ba => PRF -> Parameters -> ba -generate prf params = +generate :: (ByteArrayAccess password, ByteArrayAccess salt, ByteArray ba) + => PRF password + -> Parameters + -> password + -> salt + -> ba +generate prf params password salt = B.allocAndFreeze (outputLength params) $ \p -> do memSet p 0 (outputLength params) loop 1 (outputLength params) p where - !runPRF = prf (password params) + !runPRF = prf password !hLen = B.length $ runPRF B.empty -- run the following f function on each complete chunk. @@ -76,21 +78,22 @@ generate prf params = let uData = runPRF uprev B.withByteArray uData $ \u -> memXor p p u hLen 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) 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 let uData = runPRF uprev B.withByteArray uData $ \u -> memXor tmp tmp u hLen applyMany (i-1) uData 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 -- big endian encoding of Word32 - toBS :: Word32 -> ByteString + toBS :: ByteArray ba => Word32 -> ba toBS w = B.pack [a,b,c,d] where a = fromIntegral (w `shiftR` 24) b = fromIntegral ((w `shiftR` 16) .&. 0xff) diff --git a/Crypto/KDF/Scrypt.hs b/Crypto/KDF/Scrypt.hs index 6f47604..8586359 100644 --- a/Crypto/KDF/Scrypt.hs +++ b/Crypto/KDF/Scrypt.hs @@ -15,7 +15,6 @@ module Crypto.KDF.Scrypt ) where import Data.Word -import Data.ByteString (ByteString) import Foreign.Marshal.Alloc import Foreign.Ptr (Ptr, plusPtr) import Control.Monad (forM_) @@ -23,30 +22,33 @@ import Control.Monad (forM_) import Crypto.Hash (SHA256(..)) import qualified Crypto.KDF.PBKDF2 as PBKDF2 import Crypto.Internal.Compat (popCount, unsafeDoIO) +import Crypto.Internal.ByteArray (ByteArray, ByteArrayAccess) import qualified Crypto.Internal.ByteArray as B -- | Parameters for Scrypt data Parameters = Parameters - { password :: ByteString -- ^ Password (bytes encoded) - , salt :: ByteString -- ^ Salt (bytes encoded) - , n :: Word64 -- ^ Cpu/Memory cost ratio. must be a power of 2 greater than 1. also known as N. - , r :: Int -- ^ Must satisfy r * p < 2^30 - , p :: Int -- ^ Must satisfy r * p < 2^30 - , outputLength :: Int -- ^ the number of bytes to generate out of Scrypt + { n :: Word64 -- ^ Cpu/Memory cost ratio. must be a power of 2 greater than 1. also known as N. + , r :: Int -- ^ Must satisfy r * p < 2^30 + , 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" ccryptonite_scrypt_smix :: Ptr Word8 -> Word32 -> Word64 -> Ptr Word8 -> Ptr Word8 -> IO () -- | Generate the scrypt key derivation data -generate :: Parameters -> ByteString -generate params +generate :: (ByteArrayAccess password, ByteArrayAccess salt, ByteArray output) + => Parameters + -> password + -> salt + -> output +generate params password salt | r params * p params >= 0x40000000 = error "Scrypt: invalid parameters: r and p constraint" | popCount (n params) /= 1 = error "Scrypt: invalid parameters: n not a power of 2" | 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 -> allocaBytesAligned (128*(fromIntegral $ n params)*(r params)) 8 $ \v -> allocaBytesAligned (256*r params) 8 $ \xy -> do @@ -54,7 +56,7 @@ generate params ccryptonite_scrypt_smix (bPtr `plusPtr` (i * 128 * (r params))) (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 intLen = p params * 128 * r params {-# NOINLINE generate #-} diff --git a/tests/KAT_PBKDF2.hs b/tests/KAT_PBKDF2.hs index 6f2cd79..63e5085 100644 --- a/tests/KAT_PBKDF2.hs +++ b/tests/KAT_PBKDF2.hs @@ -49,7 +49,7 @@ tests = testGroup "PBKDF2" 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)) + testCase (show i) (output @=? PBKDF2.generate prf (PBKDF2.Parameters iter dkLen) pass salt) is :: [Int] is = [1..] diff --git a/tests/KAT_Scrypt.hs b/tests/KAT_Scrypt.hs index a65572a..eee5a76 100644 --- a/tests/KAT_Scrypt.hs +++ b/tests/KAT_Scrypt.hs @@ -30,4 +30,4 @@ vectors = tests = testGroup "Scrypt" $ map toCase $ zip [(1::Int)..] vectors 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)