cryptonite/Crypto/Data/AFIS.hs
Vincent Hanquez db7c3bbb4f [hash] massive overhaul of the hash interface
use the typeclass for the lowest IO impure C bindings definitions,
and define the pure interface as generic on top of this.

At the same time define an Hash.IO interface to allow mutable manipulations
of hash contextes when necessary.

Use HashAlgorithm instead of HashFunction in the [PubKey] sections

Tweak the HMAC, PBKDF2 functions to be more efficient and use the new interface
2015-04-30 06:18:07 +01:00

148 lines
5.8 KiB
Haskell

-- |
-- Module : Crypto.Data.AFIS
-- License : BSD-style
-- Maintainer : Vincent Hanquez <vincent@snarc.org>
-- Stability : experimental
-- Portability : unknown
--
-- haskell implementation of the Anti-forensic information splitter
-- available in LUKS. <http://clemens.endorphin.org/AFsplitter>
--
-- The algorithm bloats an arbitrary secret with many bits that are necessary for
-- the recovery of the key (merge), and allow greater way to permanently
-- destroy a key stored on disk.
--
{-# LANGUAGE ScopedTypeVariables #-}
module Crypto.Data.AFIS
( split
, merge
) where
import Crypto.Hash
import Crypto.Random.Types
import Crypto.Internal.Bytes (bufSet, bufCopy)
import Crypto.Internal.Compat
import Control.Monad (forM_, foldM)
import Data.Word
import Data.Bits
import Foreign.Storable
import Foreign.Ptr
import Crypto.Internal.ByteArray (ByteArray, Bytes, MemView(..))
import qualified Crypto.Internal.ByteArray as B
-- | Split data to diffused data, using a random generator and
-- an hash algorithm.
--
-- the diffused data will consist of random data for (expandTimes-1)
-- then the last block will be xor of the accumulated random data diffused by
-- the hash algorithm.
--
-- ----------
-- - orig -
-- ----------
--
-- ---------- ---------- --------------
-- - rand1 - - rand2 - - orig ^ acc -
-- ---------- ---------- --------------
--
-- where acc is :
-- acc(n+1) = hash (n ++ rand(n)) ^ acc(n)
--
split :: (ByteArray ba, HashAlgorithm hash, DRG rng)
=> hash -- ^ Hash algorithm to use as diffuser
-> rng -- ^ Random generator to use
-> Int -- ^ Number of times to diffuse the data.
-> ba -- ^ original data to diffuse.
-> (ba, rng) -- ^ The diffused data
{-# NOINLINE split #-}
split hashAlg rng expandTimes src
| expandTimes <= 1 = error "invalid expandTimes value"
| otherwise = unsafeDoIO $ do
(rng', bs) <- B.allocRet diffusedLen runOp
return (bs, rng')
where diffusedLen = blockSize * expandTimes
blockSize = B.length src
runOp dstPtr = do
let lastBlock = dstPtr `plusPtr` (blockSize * (expandTimes-1))
bufSet lastBlock 0 blockSize
let randomBlockPtrs = map (plusPtr dstPtr . (*) blockSize) [0..(expandTimes-2)]
rng' <- foldM fillRandomBlock rng randomBlockPtrs
mapM_ (addRandomBlock lastBlock) randomBlockPtrs
B.withByteArray src $ \srcPtr -> xorMem srcPtr lastBlock blockSize
return rng'
addRandomBlock lastBlock blockPtr = do
xorMem blockPtr lastBlock blockSize
diffuse hashAlg lastBlock blockSize
fillRandomBlock g blockPtr = do
let (rand :: Bytes, g') = randomBytesGenerate blockSize g
B.withByteArray rand $ \randPtr -> bufCopy blockPtr randPtr (fromIntegral blockSize)
return g'
-- | Merge previously diffused data back to the original data.
merge :: (ByteArray ba, HashAlgorithm hash)
=> hash -- ^ Hash algorithm used as diffuser
-> Int -- ^ Number of times to un-diffuse the data
-> ba -- ^ Diffused data
-> ba -- ^ Original data
{-# NOINLINE merge #-}
merge hashAlg expandTimes bs
| r /= 0 = error "diffused data not a multiple of expandTimes"
| originalSize <= 0 = error "diffused data null"
| otherwise = B.allocAndFreeze originalSize $ \dstPtr ->
B.withByteArray bs $ \srcPtr -> do
bufSet dstPtr 0 originalSize
forM_ [0..(expandTimes-2)] $ \i -> do
xorMem (srcPtr `plusPtr` (i * originalSize)) dstPtr originalSize
diffuse hashAlg dstPtr originalSize
xorMem (srcPtr `plusPtr` ((expandTimes-1) * originalSize)) dstPtr originalSize
return ()
where (originalSize,r) = len `quotRem` expandTimes
len = B.length bs
-- | inplace Xor with an input
-- dst = src `xor` dst
xorMem :: Ptr Word8 -> Ptr Word8 -> Int -> IO ()
xorMem src dst sz
| sz `mod` 64 == 0 = loop 8 (castPtr src :: Ptr Word64) (castPtr dst) sz
| sz `mod` 32 == 0 = loop 4 (castPtr src :: Ptr Word32) (castPtr dst) sz
| otherwise = loop 1 (src :: Ptr Word8) dst sz
where loop _ _ _ 0 = return ()
loop incr s d n = do a <- peek s
b <- peek d
poke d (a `xor` b)
loop incr (s `plusPtr` incr) (d `plusPtr` incr) (n-incr)
diffuse :: HashAlgorithm hash
=> hash -- ^ Hash function to use as diffuser
-> Ptr Word8 -- ^ buffer to diffuse, modify in place
-> Int -- ^ length of buffer to diffuse
-> IO ()
diffuse hashAlg src sz = loop src 0
where (full,pad) = sz `quotRem` digestSize
loop s i
| i < full = do h <- hashBlock i s digestSize
B.withByteArray h $ \hPtr -> bufCopy s hPtr digestSize
loop (s `plusPtr` digestSize) (i+1)
| pad /= 0 = do h <- hashBlock i s pad
B.withByteArray h $ \hPtr -> bufCopy s hPtr pad
return ()
| otherwise = return ()
digestSize = hashDigestSize hashAlg
-- Hash [ BE32(n), (p .. p+hashSz) ]
hashBlock n p hashSz = do
let ctx = hashInitWith hashAlg
return $! hashFinalize $ hashUpdate (hashUpdate ctx (be32 n)) (MemView p hashSz)
be32 :: Int -> Bytes
be32 n = B.allocAndFreeze 4 $ \ptr -> do
poke ptr (f8 (n `shiftR` 24))
poke (ptr `plusPtr` 1) (f8 (n `shiftR` 16))
poke (ptr `plusPtr` 2) (f8 (n `shiftR` 8))
poke (ptr `plusPtr` 3) (f8 n)
where
f8 :: Int -> Word8
f8 = fromIntegral