module Crypto.KDF.BCryptPBKDF
( Parameters (..)
, generate
, hashInternal
)
where
import Basement.Block (MutableBlock)
import qualified Basement.Block as Block
import qualified Basement.Block.Mutable as Block
import Basement.Monad (PrimState)
import Basement.Types.OffsetSize (CountOf (..), Offset (..))
import Control.Exception (finally)
import Control.Monad (when)
import qualified Crypto.Cipher.Blowfish.Box as Blowfish
import qualified Crypto.Cipher.Blowfish.Primitive as Blowfish
import Crypto.Hash.Algorithms (SHA512 (..))
import Crypto.Hash.Types (Context,
hashDigestSize,
hashInternalContextSize,
hashInternalFinalize,
hashInternalInit,
hashInternalUpdate)
import Crypto.Internal.Compat (unsafeDoIO)
import Data.Bits
import qualified Data.ByteArray as B
import Data.Foldable (forM_)
import Data.Memory.PtrMethods (memCopy, memSet, memXor)
import Data.Word
import Foreign.Ptr (Ptr, castPtr)
import Foreign.Storable (peekByteOff, pokeByteOff)
data Parameters = Parameters
{ iterCounts :: Int
, outputLength :: Int
} deriving (Eq, Ord, Show)
generate :: (B.ByteArray pass, B.ByteArray salt, B.ByteArray output)
=> Parameters
-> pass
-> salt
-> output
generate params pass salt
| iterCounts params < 1 = error "BCryptPBKDF: iterCounts must be > 0"
| keyLen < 1 || keyLen > 1024 = error "BCryptPBKDF: outputLength must be in 1..1024"
| otherwise = B.unsafeCreate keyLen deriveKey
where
outLen, tmpLen, blkLen, keyLen, passLen, saltLen, ctxLen, hashLen, blocks :: Int
outLen = 32
tmpLen = 32
blkLen = 4
passLen = B.length pass
saltLen = B.length salt
keyLen = outputLength params
ctxLen = hashInternalContextSize SHA512
hashLen = hashDigestSize SHA512
blocks = (keyLen + outLen - 1) `div` outLen
deriveKey :: Ptr Word8 -> IO ()
deriveKey keyPtr = do
ksClean <- Blowfish.createKeySchedule
ksDirty <- Blowfish.createKeySchedule
ctxMBlock <- Block.newPinned (CountOf ctxLen :: CountOf Word8)
outMBlock <- Block.newPinned (CountOf outLen :: CountOf Word8)
tmpMBlock <- Block.newPinned (CountOf tmpLen :: CountOf Word8)
blkMBlock <- Block.newPinned (CountOf blkLen :: CountOf Word8)
passHashMBlock <- Block.newPinned (CountOf hashLen :: CountOf Word8)
saltHashMBlock <- Block.newPinned (CountOf hashLen :: CountOf Word8)
finallyErase outMBlock $ finallyErase passHashMBlock $
B.withByteArray pass $ \passPtr->
B.withByteArray salt $ \saltPtr->
Block.withMutablePtr ctxMBlock $ \ctxPtr->
Block.withMutablePtr outMBlock $ \outPtr->
Block.withMutablePtr tmpMBlock $ \tmpPtr->
Block.withMutablePtr blkMBlock $ \blkPtr->
Block.withMutablePtr passHashMBlock $ \passHashPtr->
Block.withMutablePtr saltHashMBlock $ \saltHashPtr-> do
let shaPtr = castPtr ctxPtr :: Ptr (Context SHA512)
hashInternalInit shaPtr
hashInternalUpdate shaPtr passPtr (fromIntegral passLen)
hashInternalFinalize shaPtr (castPtr passHashPtr)
passHashBlock <- Block.unsafeFreeze passHashMBlock
forM_ [1..blocks] $ \block-> do
Block.unsafeWrite blkMBlock 0 (fromIntegral $ block `shiftR` 24)
Block.unsafeWrite blkMBlock 1 (fromIntegral $ block `shiftR` 16)
Block.unsafeWrite blkMBlock 2 (fromIntegral $ block `shiftR` 8)
Block.unsafeWrite blkMBlock 3 (fromIntegral $ block `shiftR` 0)
hashInternalInit shaPtr
hashInternalUpdate shaPtr saltPtr (fromIntegral saltLen)
hashInternalUpdate shaPtr blkPtr (fromIntegral blkLen)
hashInternalFinalize shaPtr (castPtr saltHashPtr)
Block.unsafeFreeze saltHashMBlock >>= \x-> do
Blowfish.copyKeySchedule ksDirty ksClean
hashInternalMutable ksDirty passHashBlock x tmpMBlock
memCopy outPtr tmpPtr outLen
forM_ [2..iterCounts params] $ const $ do
hashInternalInit shaPtr
hashInternalUpdate shaPtr tmpPtr (fromIntegral tmpLen)
hashInternalFinalize shaPtr (castPtr saltHashPtr)
Block.unsafeFreeze saltHashMBlock >>= \x-> do
Blowfish.copyKeySchedule ksDirty ksClean
hashInternalMutable ksDirty passHashBlock x tmpMBlock
memXor outPtr outPtr tmpPtr outLen
forM_ [0..outLen - 1] $ \outIdx-> do
let keyIdx = outIdx * blocks + block - 1
when (keyIdx < keyLen) $ do
w8 <- peekByteOff outPtr outIdx :: IO Word8
pokeByteOff keyPtr keyIdx w8
hashInternal :: (B.ByteArrayAccess pass, B.ByteArrayAccess salt, B.ByteArray output)
=> pass
-> salt
-> output
hashInternal passHash saltHash
| B.length passHash /= 64 = error "passHash must be 512 bits"
| B.length saltHash /= 64 = error "saltHash must be 512 bits"
| otherwise = unsafeDoIO $ do
ks0 <- Blowfish.createKeySchedule
outMBlock <- Block.newPinned 32
hashInternalMutable ks0 passHash saltHash outMBlock
B.convert `fmap` Block.freeze outMBlock
hashInternalMutable :: (B.ByteArrayAccess pass, B.ByteArrayAccess salt)
=> Blowfish.KeySchedule
-> pass
-> salt
-> MutableBlock Word8 (PrimState IO)
-> IO ()
hashInternalMutable bfks passHash saltHash outMBlock = do
Blowfish.expandKeyWithSalt bfks passHash saltHash
forM_ [0..63 :: Int] $ const $ do
Blowfish.expandKey bfks saltHash
Blowfish.expandKey bfks passHash
store 0 =<< cipher 64 0x4f78796368726f6d
store 8 =<< cipher 64 0x61746963426c6f77
store 16 =<< cipher 64 0x6669736853776174
store 24 =<< cipher 64 0x44796e616d697465
where
store :: Offset Word8 -> Word64 -> IO ()
store o w64 = do
Block.unsafeWrite outMBlock (o + 0) (fromIntegral $ w64 `shiftR` 32)
Block.unsafeWrite outMBlock (o + 1) (fromIntegral $ w64 `shiftR` 40)
Block.unsafeWrite outMBlock (o + 2) (fromIntegral $ w64 `shiftR` 48)
Block.unsafeWrite outMBlock (o + 3) (fromIntegral $ w64 `shiftR` 56)
Block.unsafeWrite outMBlock (o + 4) (fromIntegral $ w64 `shiftR` 0)
Block.unsafeWrite outMBlock (o + 5) (fromIntegral $ w64 `shiftR` 8)
Block.unsafeWrite outMBlock (o + 6) (fromIntegral $ w64 `shiftR` 16)
Block.unsafeWrite outMBlock (o + 7) (fromIntegral $ w64 `shiftR` 24)
cipher :: Int -> Word64 -> IO Word64
cipher 0 block = return block
cipher i block = Blowfish.cipherBlockMutable bfks block >>= cipher (i - 1)
finallyErase :: MutableBlock Word8 (PrimState IO) -> IO () -> IO ()
finallyErase mblock action =
action `finally` Block.withMutablePtr mblock (\ptr-> memSet ptr 0 len)
where
CountOf len = Block.mutableLengthBytes mblock