packages feed

crypton-2.0.0: Crypto/Cipher/DES/Primitive.hs

{-# LANGUAGE ForeignFunctionInterface #-}

-- |
-- Module      : Crypto.Cipher.DES.Primitive
-- License     : BSD-style
-- Stability   : experimental
-- Portability : Good
--
-- The DES block operation, as FIPS 46-3 defines it, over the C in
-- @cbits/crypton_des.c@.
--
-- A t'Schedule' holds the round keys of one or more stages in the order they
-- are applied, which is what lets single DES and the three stage constructions
-- share one entry point.
module Crypto.Cipher.DES.Primitive (
    Schedule,
    Direction (..),
    schedule,
    ecb,
) where

import Crypto.Internal.ByteArray (ByteArray, ByteArrayAccess, Bytes)
import qualified Crypto.Internal.ByteArray as B
import Crypto.Internal.Compat (unsafeDoIO)
import Data.Word
import Foreign.C.Types (CInt (..))
import Foreign.Ptr (Ptr, plusPtr)

-- | Which way a stage runs.
data Direction = Encrypt | Decrypt
    deriving (Show, Eq)

-- | The round keys of one or more stages, in the order they are applied.
newtype Schedule = Schedule Bytes
    deriving (Eq)

-- | Bytes per stage: sixteen rounds of eight six-bit values.
stageSize :: Int
stageSize = 16 * 8

-- | The block size DES works in.
blockBytes :: Int
blockBytes = 8

-- | Build the schedule for a sequence of stages, each an eight byte key and
-- the direction that stage runs in.  Shorter keys are rejected by the callers,
-- which know their own size; the bytes past the eighth are not read.
schedule :: ByteArrayAccess key => [(Direction, key)] -> Schedule
schedule stages =
    Schedule $ B.allocAndFreeze (stageSize * length stages) $ \dst ->
        mapM_ (uncurry (one dst)) (zip [0 ..] stages)
  where
    one dst i (dir, key) =
        B.withByteArray key $ \k ->
            c_des_init (dst `plusPtr` (i * stageSize)) k (reverseFlag dir)
    reverseFlag Encrypt = 0
    reverseFlag Decrypt = 1

-- | Apply every stage of the schedule, in order, to each block of the input.
ecb :: ByteArray ba => Schedule -> ba -> ba
ecb (Schedule sched) input
    | len `mod` blockBytes /= 0 =
        error $
            "Crypto.Cipher.DES: input length must be a multiple of block size (8). Its length is: "
                ++ show len
    | otherwise = unsafeDoIO $
        B.alloc len $ \out ->
            B.withByteArray sched $ \ks ->
                B.withByteArray input $ \inp ->
                    c_des_ecb
                        out
                        ks
                        (fromIntegral (B.length sched `div` stageSize))
                        inp
                        (fromIntegral (len `div` blockBytes))
  where
    len = B.length input

foreign import ccall unsafe "crypton_des.h crypton_des_init"
    c_des_init :: Ptr Word8 -> Ptr Word8 -> CInt -> IO ()

foreign import ccall unsafe "crypton_des.h crypton_des_ecb"
    c_des_ecb :: Ptr Word8 -> Ptr Word8 -> Word32 -> Ptr Word8 -> Word32 -> IO ()