packages feed

crypton-2.0.0: tests/BlockCipher/ModesSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module BlockCipher.ModesSpec (spec) where

import Crypto.Cipher.Camellia (Camellia128)
import Crypto.Cipher.DES (DES)
import Crypto.Cipher.Types
import Crypto.Error (throwCryptoError)
import Data.Bits (xor)
import qualified Data.ByteString as B
import Imports

-- | AES answers for its own modes in C; every other cipher reaches the generic
-- implementations in "Crypto.Cipher.Types.Block".  The suite checks that those
-- round trip, which a mode that chains the wrong way does too, and it checks
-- them only at the lengths QuickCheck happens to draw.
--
-- So write each mode out as its definition states it, and compare.
blocksOf :: Int -> ByteString -> [ByteString]
blocksOf n bs
    | B.null bs = []
    | otherwise = let (a, b) = B.splitAt n bs in a : blocksOf n b

bxor :: ByteString -> ByteString -> ByteString
bxor a b = B.pack (B.zipWith xor a b)

-- big-endian increment, which is what CTR counts with
incr :: ByteString -> ByteString
incr bs = B.pack (reverse (go (reverse (B.unpack bs))))
  where
    go [] = []
    go (w : ws)
        | w == 0xff = 0 : go ws
        | otherwise = (w + 1) : ws

refCBCEncrypt
    , refCBCDecrypt
    , refCFBEncrypt
    , refCFBDecrypt
    , refCTR
        :: BlockCipher c => c -> ByteString -> ByteString -> ByteString
refCBCEncrypt c iv msg = B.concat (go iv (blocksOf (blockSize c) msg))
  where
    go _ [] = []
    go v (m : ms) = let o = ecbEncrypt c (bxor v m) in o : go o ms
refCBCDecrypt c iv msg = B.concat (go iv (blocksOf (blockSize c) msg))
  where
    go _ [] = []
    go v (m : ms) = bxor v (ecbDecrypt c m) : go m ms
refCFBEncrypt c iv msg = B.concat (go iv (blocksOf (blockSize c) msg))
  where
    go _ [] = []
    go v (m : ms) = let o = bxor m (ecbEncrypt c v) in o : go o ms
refCFBDecrypt c iv msg = B.concat (go iv (blocksOf (blockSize c) msg))
  where
    go _ [] = []
    go v (m : ms) = bxor m (ecbEncrypt c v) : go m ms
refCTR c iv msg =
    B.concat
        (zipWith bxor (blocksOf (blockSize c) msg) (map (ecbEncrypt c) (iterate incr iv)))

modeTests :: BlockCipher c => String -> c -> ByteString -> Spec
modeTests name c iv0 =
    describe name $ do
        it "CBC encryption is what the definition says" $
            disagree (cbcEncrypt c iv) (refCBCEncrypt c iv0) wholeBlocks `shouldBe` []
        it "CBC decryption is what the definition says" $
            disagree (cbcDecrypt c iv) (refCBCDecrypt c iv0) wholeBlocks `shouldBe` []
        it "CFB encryption is what the definition says" $
            disagree (cfbEncrypt c iv) (refCFBEncrypt c iv0) wholeBlocks `shouldBe` []
        it "CFB decryption is what the definition says" $
            disagree (cfbDecrypt c iv) (refCFBDecrypt c iv0) wholeBlocks `shouldBe` []
        it "CTR is what the definition says, whole blocks or not" $
            disagree (ctrCombine c iv) (refCTR c iv0) everyLength `shouldBe` []
        it "and on a message of 64 KiB" $ do
            cbcEncrypt c iv big `shouldBe` refCBCEncrypt c iv0 big
            cbcDecrypt c iv big `shouldBe` refCBCDecrypt c iv0 big
            ctrCombine c iv big `shouldBe` refCTR c iv0 big
  where
    bsz = blockSize c
    iv = maybe (error "bad IV") id (makeIV iv0)
    -- the message, and the lengths to take of it
    message = B.concat (replicate 4 (B.pack (map fromIntegral [1 .. 255 :: Int])))
    wholeBlocks = [bsz * i | i <- [0 .. 20]]
    everyLength = [0 .. 40]
    big = B.concat (replicate 256 message)
    disagree lib ref lens =
        [n | n <- lens, let m = B.take n message, lib m /= ref m]

spec :: Spec
spec = do
    modeTests
        "DES"
        (throwCryptoError (cipherInit desKey) :: DES)
        (B.replicate 8 0x42)
    modeTests
        "Camellia128"
        (throwCryptoError (cipherInit camKey) :: Camellia128)
        (B.replicate 16 0x42)
  where
    desKey = "\x01\x23\x45\x67\x89\xab\xcd\xef" :: ByteString
    camKey =
        "\x01\x23\x45\x67\x89\xab\xcd\xef\xfe\xdc\xba\x98\x76\x54\x32\x10" :: ByteString