cryptoids (empty) → 0.0.0
raw patch · 5 files changed
+256/−0 lines, 5 filesdep +basedep +binarydep +bytestringsetup-changed
Dependencies added: base, binary, bytestring, cryptoids-types, cryptonite, memory, mtl
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- changes.md +3/−0
- cryptoids.cabal +34/−0
- src/Data/CryptoID/Poly.hs +187/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2017, Gregor Kleen++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Gregor Kleen nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ changes.md view
@@ -0,0 +1,3 @@+# 0.0.0++First published version
+ cryptoids.cabal view
@@ -0,0 +1,34 @@+name: cryptoids+version: 0.0.0+cabal-version: >=1.10+build-type: Simple+license: BSD3+license-file: LICENSE+maintainer: aethoago@141.li+synopsis: Reversable and secure encoding of object ids as a bytestring+category: cryptography+author: Gregor Kleen+extra-source-files:+ changes.md++source-repository head+ type: git+ location: https://git.rheperire.org/cryptoids+ subdir: cryptoids++library+ exposed-modules:+ Data.CryptoID.Poly+ build-depends:+ base >=4.9.1.0 && <4.11,+ cryptoids-types ==0.0.0,+ cryptonite >=0.23 && <0.25,+ bytestring >=0.10.8.1 && <0.11,+ binary >=0.8.3.0 && <0.9,+ memory >=0.14.6 && <0.15,+ mtl >=2.2.1 && <2.3+ default-language: Haskell2010+ default-extensions: RankNTypes DataKinds GeneralizedNewtypeDeriving+ ViewPatterns RecordWildCards FlexibleContexts+ hs-source-dirs: src+
+ src/Data/CryptoID/Poly.hs view
@@ -0,0 +1,187 @@+{-# LANGUAGE ScopedTypeVariables #-}++{-|+Description: Encryption of bytestrings using a type level nonce for determinism+License: BSD3++Given a value of a serializable type (like 'Int') we perform serialization and+compute a cryptographic hash of the associated namespace (carried as a phantom+type of kind 'Symbol').+The serialized payload is then encrypted using the symmetric cipher in CBC mode+using the hashed namespace as an initialization vector (IV).++The probability of detecting a namespace mismatch is thus \(1 - 2^{128-l}\)+where \(l\) is the length of the serialized payload.+-}+module Data.CryptoID.Poly+ ( CryptoID(..)+ , CryptoIDKey+ , genKey+ , encrypt+ , decrypt+ , CryptoIDError(..)+ , CryptoCipher, CryptoHash+ ) where++import Data.CryptoID++import Data.Binary+import Data.Binary.Put+import Data.Binary.Get++import Data.ByteString (ByteString)+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Char8 as ByteString.Char++import qualified Data.ByteString.Lazy as Lazy (ByteString)++import Data.List (sortOn)+import Data.Ord (Down(..))++import Data.ByteArray (ByteArrayAccess)+import qualified Data.ByteArray as ByteArray++import Data.Foldable (asum)+import Control.Monad.Except+import Control.Exception++import Data.Typeable+import GHC.TypeLits++import Crypto.Cipher.Types+import Crypto.Cipher.Blowfish (Blowfish)+import Crypto.Hash (hash, Digest)+import Crypto.Hash.Algorithms (SHAKE128)+import Crypto.Error++import Crypto.Random.Entropy+++-- | The symmetric cipher 'BlockCipher' this module uses +type CryptoCipher = Blowfish+-- | The cryptographic 'HashAlgorithm' this module uses+--+-- We expect the block size of 'CryptoCipher' to be exactly the size of the+-- 'Digest' generated by 'CryptoHash' (since a 'Digest' is used as an 'IV').+--+-- Violation of this expectation causes runtime errors.+type CryptoHash = SHAKE128 64+ ++-- | This newtype ensures only keys of the correct length can be created+--+-- Use 'genKey' to securely generate keys.+--+-- Use the 'Binary' instance to save and restore values of 'CryptoIDKey' across+-- executions.+newtype CryptoIDKey = CryptoIDKey { keyMaterial :: ByteString }+ deriving (Typeable, ByteArrayAccess)++-- | Does not actually show any key material+instance Show CryptoIDKey where+ show = show . typeOf++instance Binary CryptoIDKey where+ put = putByteString . keyMaterial+ get = CryptoIDKey <$> getKey (cipherKeySize cipher)+ where+ cipher :: CryptoCipher+ cipher = undefined++ -- Try key sizes from large to small ('Get' commits to the first branch+ -- that parses)+ getKey (KeySizeFixed n) = getByteString n+ getKey (KeySizeEnum ns) = asum [ getKey $ KeySizeFixed n | n <- sortOn Down ns ]+ getKey (KeySizeRange min max) = getKey $ KeySizeEnum [max .. min]+++-- | Error cases that can be encountered during 'encrypt' and 'decrypt'+data CryptoIDError+ = AlgorithmError CryptoError+ -- ^ One of the underlying cryptographic algorithms+ -- ('CryptoHash' or 'CryptoCipher') failed.+ | NamespaceHashIsWrongLength ByteString+ -- ^ The length of the digest produced by 'CryptoHash' does+ -- not match the block size of 'CryptoCipher'.+ --+ -- The offending digest is included.+ --+ -- This error should not occur and is included primarily+ -- for sake of totality.+ | CiphertextConversionFailed+ -- ^ The produced 'ByteString' is the wrong length for conversion into a+ -- ciphertext.+ | DeserializationError (Lazy.ByteString, ByteOffset, String)+ -- ^ The plaintext obtained by decrypting a ciphertext with the given+ -- 'CryptoIDKey' in the context of the @namespace@ could not be+ -- deserialized into a value of the expected @payload@-type.+ --+ -- This is expected behaviour if the @namespace@ or @payload@-type does not+ -- match the ones used during 'encrypt'ion or if the 'ciphertext' was+ -- tempered with.+ | InvalidNamespaceDetected+ -- ^ We have determined that, allthough deserializion succeded, the+ -- ciphertext was likely modified during transit or created using a+ -- different namespace.+ deriving (Show, Eq)++instance Exception CryptoIDError++-- | Securely generate a new key using system entropy+--+-- When 'CryptoCipher' accepts keys of varying lengths this function generates a+-- key of the largest accepted size.+genKey :: MonadIO m => m CryptoIDKey+genKey = CryptoIDKey <$> liftIO (getEntropy keySize)+ where+ keySize' = cipherKeySize (undefined :: CryptoCipher)++ keySize+ | KeySizeFixed n <- keySize' = n+ | KeySizeEnum ns <- keySize' = maximum ns+ | KeySizeRange _ max <- keySize' = max+ + +-- | @pad err size src@ appends null bytes to @src@ until it has length that is+-- a multiple of @size@.+pad :: ByteArrayAccess a => Int -> a -> ByteString+pad n (ByteArray.unpack -> src) = ByteString.pack $ src ++ replicate (l `mod` n) 0+ where+ l = length src++-- | Use 'CryptoHash' to generate a 'Digest' of the Symbol passed as proxy type+namespace' :: forall proxy namespace m.+ ( KnownSymbol namespace, MonadError CryptoIDError m+ ) => proxy namespace -> m (IV CryptoCipher)+namespace' p = case makeIV namespaceHash of+ Nothing -> throwError . NamespaceHashIsWrongLength $ ByteArray.convert namespaceHash+ Just iv -> return iv+ where+ namespaceHash :: Digest CryptoHash+ namespaceHash = hash . ByteString.Char.pack $ symbolVal p++-- | Wrap failure of one of the cryptographic algorithms as a 'CryptoIDError'+cryptoFailable :: MonadError CryptoIDError m => CryptoFailable a -> m a+cryptoFailable = either (throwError . AlgorithmError) return . eitherCryptoError++-- | Encrypt an arbitrary serializable value+encrypt :: forall m namespace.+ ( KnownSymbol namespace+ , MonadError CryptoIDError m+ ) => CryptoIDKey -> ByteString -> m (CryptoID namespace ByteString)+encrypt (keyMaterial -> key) plaintext = do+ cipher <- cryptoFailable (cipherInit key :: CryptoFailable CryptoCipher)+ namespace <- namespace' (Proxy :: Proxy namespace)+ return . CryptoID . cbcEncrypt cipher namespace $ pad (blockSize cipher) plaintext+++-- | Decrypt an arbitrary serializable value+decrypt :: forall m namespace.+ ( KnownSymbol namespace+ , MonadError CryptoIDError m+ ) => CryptoIDKey -> CryptoID namespace ByteString -> m ByteString+decrypt (keyMaterial -> key) CryptoID{..} = do+ cipher <- cryptoFailable (cipherInit key :: CryptoFailable CryptoCipher)+ namespace <- namespace' (Proxy :: Proxy namespace)+ return $ cbcDecrypt cipher namespace ciphertext+