packages feed

tahoe-chk-0.2.0.0: src/Tahoe/CHK/Encrypt.hs

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

-- | Support the encryption requirements of CHK.
module Tahoe.CHK.Encrypt (encrypt, encryptLazy, decrypt, decryptLazy) where

import Crypto.Cipher.Types (BlockCipher (blockSize, ctrCombine), ivAdd, nullIV)
import Data.ByteArray (ByteArray)
import qualified Data.ByteString.Lazy as LBS
import Data.List (unfoldr)

{- | CTR-mode encrypt a byte string using some block cipher.

 When used for CHKv1 or CHKv2 the block cipher should be AES128.

 This replaces allmydata.immutable.upload.EncryptAnUploadable

 The only noteworthy piece here is that encryption starts with the zero IV.
-}
encrypt :: (BlockCipher cipher, ByteArray ba) => cipher -> ba -> ba
encrypt key = ctrCombine key nullIV

-- | Like encrypt but operate on lazy bytestrings.
encryptLazy :: forall cipher. BlockCipher cipher => cipher -> LBS.ByteString -> LBS.ByteString
encryptLazy cipher lbs = LBS.concat . (LBS.fromStrict <$>) $ zipWith (ctrCombine cipher) ivs blocks
  where
    -- The underlying encryption function works on strict bytes.  Here's the
    -- number of *blocks* to feed to it (that is, to make strict) at a time.
    -- This value here is a magic number that is meant to represent a good
    -- compromise between performance and number of bytes forced at one time.
    workingBlocks = 1024 * 16

    -- The size of a block is determined by the cipher.
    workingBytes = workingBlocks * blockSize @cipher undefined

    ivs = iterate (`ivAdd` workingBlocks) nullIV
    blocks = LBS.toStrict <$> unfoldr takeChunk lbs

    takeChunk "" = Nothing
    takeChunk xs = Just . LBS.splitAt (fromIntegral workingBytes) $ xs

-- | AES128-CTR decrypt a byte string in the manner used by CHK.
decrypt :: (BlockCipher cipher, ByteArray ba) => cipher -> ba -> ba
decrypt = encrypt

-- | Like decrypt but operate on lazy bytestrings.
decryptLazy :: BlockCipher cipher => cipher -> LBS.ByteString -> LBS.ByteString
decryptLazy = encryptLazy