{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE ViewPatterns #-}
module Crypto.Cipher.Types.Block
(
BlockCipher(..)
, IV
, makeIV
, nullIV
, ivAdd
, XTS
, AEAD(..)
, AEADState(..)
, AEADModeImpl(..)
, cfb8Encrypt
, cfb8Decrypt
) where
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import qualified Data.ByteString.Internal as B (unsafeCreate)
import Data.Byteable
import Data.Word
import Data.Bits (shiftR, Bits)
import Crypto.Cipher.Types.Base
import Crypto.Cipher.Types.GF
import Crypto.Cipher.Types.Utils
import Foreign.Ptr
import Foreign.Storable
type XTS cipher = (cipher, cipher)
-> IV cipher
-> DataUnitOffset
-> ByteString
-> ByteString
class Cipher cipher => BlockCipher cipher where
blockSize :: cipher -> Int
ecbEncrypt :: cipher -> ByteString -> ByteString
ecbDecrypt :: cipher -> ByteString -> ByteString
cbcEncrypt :: cipher -> IV cipher -> ByteString -> ByteString
cbcEncrypt = cbcEncryptGeneric
cbcDecrypt :: cipher -> IV cipher -> ByteString -> ByteString
cbcDecrypt = cbcDecryptGeneric
cfbEncrypt :: cipher -> IV cipher -> ByteString -> ByteString
cfbEncrypt = cfbEncryptGeneric
cfbDecrypt :: cipher -> IV cipher -> ByteString -> ByteString
cfbDecrypt = cfbDecryptGeneric
ctrCombine :: cipher -> IV cipher -> ByteString -> ByteString
ctrCombine = ctrCombineGeneric
xtsEncrypt :: (cipher, cipher)
-> IV cipher
-> DataUnitOffset
-> ByteString
-> ByteString
xtsEncrypt = xtsEncryptGeneric
xtsDecrypt :: (cipher, cipher)
-> IV cipher
-> DataUnitOffset
-> ByteString
-> ByteString
xtsDecrypt = xtsDecryptGeneric
aeadInit :: Byteable iv => AEADMode -> cipher -> iv -> Maybe (AEAD cipher)
aeadInit _ _ _ = Nothing
data AEAD cipher = AEAD cipher (AEADState cipher)
data AEADState cipher = forall st . AEADModeImpl cipher st => AEADState st
class BlockCipher cipher => AEADModeImpl cipher state where
aeadStateAppendHeader :: cipher -> state -> ByteString -> state
aeadStateEncrypt :: cipher -> state -> ByteString -> (ByteString, state)
aeadStateDecrypt :: cipher -> state -> ByteString -> (ByteString, state)
aeadStateFinalize :: cipher -> state -> Int -> AuthTag
makeIV :: (Byteable b, BlockCipher c) => b -> Maybe (IV c)
makeIV b = toIV undefined
where toIV :: BlockCipher c => c -> Maybe (IV c)
toIV cipher
| byteableLength b == sz = Just (IV $ toBytes b)
| otherwise = Nothing
where sz = blockSize cipher
nullIV :: BlockCipher c => IV c
nullIV = toIV undefined
where toIV :: BlockCipher c => c -> IV c
toIV cipher = IV $ B.replicate (blockSize cipher) 0
ivAdd :: BlockCipher c => IV c -> Int -> IV c
ivAdd (IV b) i = IV $ snd $ B.mapAccumR addCarry i b
where addCarry :: Int -> Word8 -> (Int, Word8)
addCarry acc w
| acc == 0 = (0, w)
| otherwise = let (hi,lo) = acc `divMod` 256
nw = lo + (fromIntegral w)
in (hi + (nw `shiftR` 8), fromIntegral nw)
cbcEncryptGeneric :: BlockCipher cipher => cipher -> IV cipher -> ByteString -> ByteString
cbcEncryptGeneric cipher (IV ivini) input = B.concat $ doEnc ivini $ chunk (blockSize cipher) input
where doEnc _ [] = []
doEnc iv (i:is) =
let o = ecbEncrypt cipher $ bxor iv i
in o : doEnc o is
cbcDecryptGeneric :: BlockCipher cipher => cipher -> IV cipher -> ByteString -> ByteString
cbcDecryptGeneric cipher (IV ivini) input = B.concat $ doDec ivini $ chunk (blockSize cipher) input
where doDec _ [] = []
doDec iv (i:is) =
let o = bxor iv $ ecbDecrypt cipher i
in o : doDec i is
cfbEncryptGeneric :: BlockCipher cipher => cipher -> IV cipher -> ByteString -> ByteString
cfbEncryptGeneric cipher (IV ivini) input = B.concat $ doEnc ivini $ chunk (blockSize cipher) input
where doEnc _ [] = []
doEnc iv (i:is) =
let o = bxor i $ ecbEncrypt cipher iv
in o : doEnc o is
cfbDecryptGeneric :: BlockCipher cipher => cipher -> IV cipher -> ByteString -> ByteString
cfbDecryptGeneric cipher (IV ivini) input = B.concat $ doDec ivini $ chunk (blockSize cipher) input
where doDec _ [] = []
doDec iv (i:is) =
let o = bxor i $ ecbEncrypt cipher iv
in o : doDec i is
ctrCombineGeneric :: BlockCipher cipher => cipher -> IV cipher -> ByteString -> ByteString
ctrCombineGeneric cipher ivini input = B.concat $ doCnt ivini $ chunk (blockSize cipher) input
where doCnt _ [] = []
doCnt iv (i:is) =
let ivEnc = ecbEncrypt cipher (toBytes iv)
in bxor i ivEnc : doCnt (ivAdd iv 1) is
xtsEncryptGeneric :: BlockCipher cipher => XTS cipher
xtsEncryptGeneric = xtsGeneric ecbEncrypt
xtsDecryptGeneric :: BlockCipher cipher => XTS cipher
xtsDecryptGeneric = xtsGeneric ecbDecrypt
xtsGeneric :: BlockCipher cipher
=> (cipher -> B.ByteString -> B.ByteString)
-> (cipher, cipher)
-> IV cipher
-> DataUnitOffset
-> ByteString
-> ByteString
xtsGeneric f (cipher, tweakCipher) iv sPoint input
| blockSize cipher /= 16 = error "XTS mode is only available with cipher that have a block size of 128 bits"
| otherwise = B.concat $ doXts iniTweak $ chunk (blockSize cipher) input
where encTweak = ecbEncrypt tweakCipher (toBytes iv)
iniTweak = iterate xtsGFMul encTweak !! fromIntegral sPoint
doXts _ [] = []
doXts tweak (i:is) =
let o = bxor (f cipher $ bxor i tweak) tweak
in o : doXts (xtsGFMul tweak) is
cfb8Encrypt :: BlockCipher a => a -> IV a -> B.ByteString -> B.ByteString
cfb8Encrypt ctx origIv msg = B.unsafeCreate (B.length msg) $ \dst -> loop dst origIv msg
where loop d iv@(IV i) m
| B.null m = return ()
| otherwise = poke d out >> loop (d `plusPtr` 1) ni (B.drop 1 m)
where m' = if B.length m < blockSize ctx
then m `B.append` B.replicate (blockSize ctx - B.length m) 0
else B.take (blockSize ctx) m
r = cfbEncrypt ctx iv m'
out = B.head r
ni = IV (B.drop 1 i `B.snoc` out)
cfb8Decrypt :: BlockCipher a => a -> IV a -> B.ByteString -> B.ByteString
cfb8Decrypt ctx origIv msg = B.unsafeCreate (B.length msg) $ \dst -> loop dst origIv msg
where loop d iv@(IV i) m
| B.null m = return ()
| otherwise = poke d out >> loop (d `plusPtr` 1) ni (B.drop 1 m)
where m' = if B.length m < blockSize ctx
then m `B.append` B.replicate (blockSize ctx - B.length m) 0
else B.take (blockSize ctx) m
r = cfbDecrypt ctx iv m'
out = B.head r
ni = IV (B.drop 1 i `B.snoc` B.head m')