{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ForeignFunctionInterface #-}
module Crypto.KDF.Scrypt
( Parameters(..)
, generate
) where
import Data.Word
import Foreign.Marshal.Alloc
import Foreign.Ptr (Ptr, plusPtr)
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
data Parameters = Parameters
{ n :: Word64
, r :: Int
, p :: Int
, outputLength :: Int
}
foreign import ccall "cryptonite_scrypt_smix"
ccryptonite_scrypt_smix :: Ptr Word8 -> Word32 -> Word64 -> Ptr Word8 -> Ptr Word8 -> IO ()
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 1 intLen) password salt :: B.Bytes
newSalt <- B.copy b $ \bPtr ->
allocaBytesAligned (128*(fromIntegral $ n params)*(r params)) 8 $ \v ->
allocaBytesAligned (256*r params + 64) 8 $ \xy -> do
forM_ [0..(p params-1)] $ \i ->
ccryptonite_scrypt_smix (bPtr `plusPtr` (i * 128 * (r params)))
(fromIntegral $ r params) (n params) v xy
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 #-}