-----------------------------------------------------------------------------
-- |
-- Module : Crypto.IDA
-- Copyright : Peter Robinson 2014
-- License : LGPL
--
-- Maintainer : Peter Robinson <peter.robinson@monoid.at>
-- Stability : experimental
-- Portability : portable
--
-- This module provides an (m,n)-information dispersal scheme that provides
-- data redundancy while preserving secrecy.
-- In other words, this module combines the best of 2 worlds: secret sharing
-- algorithms with low-overhead information dispersal.
--
-- Function 'encode' splits a given bytestring into @n@ fragments with the
-- following properties:
--
-- 1. Any @m@ of the @n@ fragments are sufficient for reconstructing the original
-- bytestring via 'decode', and
-- 2. the knowledge of up to @m-1@ fragments does /not/ leak any information
-- about the original bytestring.
--
-- In more detail, suppose that we have some bytestring @b@ that we want to
-- (securely) disperse and parameter @m@, @n@.
-- Running 'encode' @m n b@ does the following:
--
-- * Generate a randomly chosen key of 32 bytes, called @key@.
-- * Encrypt the bytestring @b@ using @key@ via AES.
-- * Generate @n@ shares using the perfect secret sharing algorithm implemented
-- in module "Crypto.SecretSharing"; see package <http://hackage.haskell.org/package/secret-sharing>secret-sharing
-- * Generate @n@ fragments of the encrypted data using the information
-- dispersal algorithm in "Data.IDA".
-- * Finally, we pair up these shares and fragments as
-- a list of 'EncryptedFragment's.
--
-- The size of each encrypted fragment is @O(|b|\/m + |key|)@.
-- For sufficiently large bytestrings, the @O(|b|\/m)@ factor dominates and thus
-- the scheme is space-optimal.
--
-- The secret sharing algorithm guarantess that the knowledge of up to @m-1@ of
-- the fragments does not leak any information about the encryption key (and
-- hence the encrypted data).
--
-----------------------------------------------------------------------------
{-# LANGUAGE DeriveDataTypeable, ScopedTypeVariables, DeriveGeneric #-}
module Crypto.IDA( EncryptedFragment(keyShare,aesIV,fragment)
, encode
, encodeWithIV
, decode
)
where
import Data.IDA.Internal( Fragment(theContent))
import qualified Data.IDA.Internal as IDA
import Crypto.SecretSharing( Share )
import qualified Crypto.SecretSharing as PSS
import Data.ByteString.Lazy( ByteString )
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString as B
import System.Entropy( getEntropy )
import Codec.Crypto.AES
import Control.Exception
import Data.Typeable
import Data.Binary( Binary )
import GHC.Generics
data EncryptedFragment = EncryptedFragment
{ keyShare :: Share -- ^ the list of (bytewise) shares of the AES key
, aesIV :: B.ByteString -- ^ the initialization vector of the AES encryption
, fragment :: Fragment -- ^ the encrypted fragment of the original data
}
deriving(Typeable,Eq,Generic)
instance Show EncryptedFragment where
show f = show (keyShare f,theContent $ fragment f)
instance Binary EncryptedFragment
aesKeyLength, aesIVLength :: Int
aesKeyLength = 32
aesIVLength = 16
-- | Space efficient and secrecy-preserving (m,n)-information dispersal:
-- Generates @n@ fragments out
-- of a given bytestring @b@. Each fragment has size @length b \/ m + O(1)@.
-- At least m fragments are required for reconstruction.
-- Preserves secrecy: The knowledge of less than m
-- fragments provides /no/ information about the original data whatsoever.
encode :: Int -- ^ m: number of fragments required for reconstruction
-> Int -- ^ n: total number of fragments (@n ≥ m@)
-> ByteString -- ^ the information that we want to disperse
-> IO [EncryptedFragment] -- ^ a list of n encrypted fragments.
encode m n msg = encode' m n Nothing msg
-- | Same as 'encode' but uses an initialization vector for the AES encryption.
encodeWithIV :: Int -- ^ m: number of fragments required for reconstruction
-> Int -- ^ n: total number of fragments (@n ≥ m@)
-> ByteString -- ^ the initialization vector for the AES encryption
-> ByteString -- ^ the information that we want to disperse
-> IO [EncryptedFragment] -- ^ a list of n encrypted fragments.
encodeWithIV m n iv msg = encode' m n (Just iv) msg
encode' :: Int -- ^ m: number of fragments required for reconstruction
-> Int -- ^ n: total number of fragments (@n ≥ m@)
-> Maybe ByteString -- ^ the initialization vector for the AES encryption.
-- If none is given, we create a random one.
-> ByteString -- ^ the information that we want to disperse
-> IO [EncryptedFragment] -- ^ a list of n encrypted fragments.
encode' m numFragments mIV msg = do
key <- getEntropy aesKeyLength
iv <- maybe (getEntropy aesIVLength) (return . BL.toStrict) mIV
keyShareList <- PSS.encode m numFragments (BL.fromStrict key)
let headers = zip keyShareList (replicate numFragments $ BL.fromStrict iv)
let fs = IDA.encode m numFragments $ BL.toStrict $ crypt CTR key iv Encrypt msg
return [ EncryptedFragment ks (BL.toStrict iv') f
| ((ks,iv'),f) <- zip headers fs
]
-- | Reconstruct the original data from (at least) @m@ fragments.
-- Throws an 'AssertionFailed' exception if an insufficient number fragments are
-- given or if a decoding error occurs.
decode :: [EncryptedFragment]
-> ByteString
decode [] = BL.pack []
decode pss@(p:_)
| length pss < IDA.reconstructionThreshold (fragment p) = throw $ AssertionFailed
"decode: not enough fragments for reconstruction."
| otherwise =
let m = IDA.reconstructionThreshold $ fragment p in
let efs = take m pss in
let iv = aesIV p in
let fs = map fragment efs in
let ss = map keyShare efs in
let emsg = IDA.decode fs in
let key = PSS.decode ss in
crypt CTR (BL.toStrict key) iv Decrypt $ BL.fromStrict emsg