From 25188728720d5ee47941e56754ab5e89da4fe509 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Tue, 10 Oct 2017 23:44:36 +0200 Subject: [PATCH] Fix padding --- cryptoids/changes.md | 5 +++++ cryptoids/cryptoids.cabal | 2 +- cryptoids/src/Data/CryptoID/ByteString.hs | 24 ++++++++++++++++------ cryptoids/src/Data/CryptoID/Poly.hs | 24 ++++++++++++++++++---- uuid-crypto/changes.md | 3 +++ uuid-crypto/src/Data/UUID/Cryptographic.hs | 4 ++-- uuid-crypto/uuid-crypto.cabal | 4 ++-- 7 files changed, 51 insertions(+), 15 deletions(-) diff --git a/cryptoids/changes.md b/cryptoids/changes.md index 7800016..4a074a0 100644 --- a/cryptoids/changes.md +++ b/cryptoids/changes.md @@ -1,3 +1,7 @@ +# 0.3.0.0 + - Better exception type (does no longer leak private information) + - 'Data.CryptoID.Poly' now supports padding the plaintext to a certain length before encryption + # 0.2.0.0 - Rename 'Data.CryptoID.Poly' to 'Data.CryptoID.ByteString' - Introduce 'Data.CryptoID.Poly' doing actual serialization @@ -12,3 +16,4 @@ # 0.0.0 First published version + diff --git a/cryptoids/cryptoids.cabal b/cryptoids/cryptoids.cabal index 744868b..2aa9240 100644 --- a/cryptoids/cryptoids.cabal +++ b/cryptoids/cryptoids.cabal @@ -1,5 +1,5 @@ name: cryptoids -version: 0.2.0.0 +version: 0.3.0.0 synopsis: Reversable and secure encoding of object ids as a bytestring license: BSD3 license-file: LICENSE diff --git a/cryptoids/src/Data/CryptoID/ByteString.hs b/cryptoids/src/Data/CryptoID/ByteString.hs index 0e00a23..eb5d31d 100644 --- a/cryptoids/src/Data/CryptoID/ByteString.hs +++ b/cryptoids/src/Data/CryptoID/ByteString.hs @@ -29,6 +29,7 @@ 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) @@ -100,10 +101,19 @@ instance Binary CryptoIDKey where -- | Error cases that can be encountered during 'encrypt' and 'decrypt' +-- +-- Care has been taken to ensure that presenting values of 'CryptoIDError' to an +-- attacker leaks no plaintext (it does leak information about the length of the +-- plaintext). data CryptoIDError = AlgorithmError CryptoError -- ^ One of the underlying cryptographic algorithms -- ('CryptoHash' or 'CryptoCipher') failed. + | PlaintextIsWrongLength Int + -- ^ The length of the plaintext is not a multiple of the block size of + -- 'CryptoCipher' + -- + -- The length of the offending plaintext is included. | NamespaceHashIsWrongLength ByteString -- ^ The length of the digest produced by 'CryptoHash' does -- not match the block size of 'CryptoCipher'. @@ -112,10 +122,12 @@ data CryptoIDError -- -- 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) + | CiphertextConversionFailed ByteString + -- ^ The produced 'ByteString' is the wrong length for deserialization into + -- a ciphertext. + -- + -- The offending 'ByteString' is included. + | DeserializationError -- ^ 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. @@ -183,8 +195,8 @@ encrypt :: forall m namespace. encrypt (keyMaterial -> key) plaintext = do cipher <- cryptoFailable (cipherInit key :: CryptoFailable CryptoCipher) namespace <- namespace' (Proxy :: Proxy namespace) - when (ByteArray.length plaintext `mod` blockSize cipher /= 0) $ - throwM CiphertextConversionFailed + when (ByteString.length plaintext `mod` blockSize cipher /= 0) $ + throwM . PlaintextIsWrongLength $ ByteString.length plaintext return . CryptoID $ cbcEncrypt cipher namespace plaintext diff --git a/cryptoids/src/Data/CryptoID/Poly.hs b/cryptoids/src/Data/CryptoID/Poly.hs index 13c3848..17769e3 100644 --- a/cryptoids/src/Data/CryptoID/Poly.hs +++ b/cryptoids/src/Data/CryptoID/Poly.hs @@ -33,11 +33,15 @@ import qualified Data.CryptoID.ByteString as ByteString (encrypt, decrypt) import Data.Binary +import Data.Monoid + import Data.ByteString (ByteString) +import qualified Data.ByteString as ByteString import qualified Data.ByteString.Lazy as Lazy.ByteString import GHC.TypeLits +import Control.Monad import Control.Monad.Catch (MonadThrow(..)) @@ -50,10 +54,22 @@ encrypt :: forall a m c namespace. ( KnownSymbol namespace , MonadThrow m , Binary a - ) => (ByteString -> m c) -> CryptoIDKey -> a -> m (CryptoID namespace c) -encrypt encode' key plaintext = do - cID <- ByteString.encrypt key . Lazy.ByteString.toStrict $ encode plaintext + ) => Maybe Int -- ^ Ensure the resulting ciphertext is of this size (needs to be a multiple of the block size of 'CryptoCipher' in bytes, otherwise an exception will be thrown at runtime) + -> (ByteString -> m c) + -> CryptoIDKey + -> a + -> m (CryptoID namespace c) +encrypt pLength encode' key plaintext = do + cID <- ByteString.encrypt key <=< pad . Lazy.ByteString.toStrict $ encode plaintext _ciphertext encode' cID + where + pad str + | Just l <- pLength + , l' <= l = return $ str <> ByteString.replicate (l - l') 0 + | Just _ <- pLength = throwM $ CiphertextConversionFailed str + | otherwise = return str + where + l' = ByteString.length str -- | Decrypt a serialized value @@ -67,7 +83,7 @@ decrypt decode key cID = do plaintext <- Lazy.ByteString.fromStrict <$> ByteString.decrypt key cID' case decodeOrFail plaintext of - Left err -> throwM $ DeserializationError err + Left _ -> throwM DeserializationError Right (rem, _, res) | Lazy.ByteString.all (== 0) rem -> return res | otherwise -> throwM InvalidNamespaceDetected diff --git a/uuid-crypto/changes.md b/uuid-crypto/changes.md index 6541beb..b4c879b 100644 --- a/uuid-crypto/changes.md +++ b/uuid-crypto/changes.md @@ -1,3 +1,6 @@ +# 1.2.0.0 + - Pad plaintext before encryption, allowing encryption of payloads shorter than 128 bits + # 1.1.1.0 - Switch to using the new 'Data.CryptoID.Poly' diff --git a/uuid-crypto/src/Data/UUID/Cryptographic.hs b/uuid-crypto/src/Data/UUID/Cryptographic.hs index 8c15def..185b07d 100644 --- a/uuid-crypto/src/Data/UUID/Cryptographic.hs +++ b/uuid-crypto/src/Data/UUID/Cryptographic.hs @@ -53,7 +53,7 @@ encrypt :: forall a m namespace. , Binary a , MonadThrow m ) => CryptoIDKey -> a -> m (CryptoUUID namespace) -encrypt = Poly.encrypt $ maybe (throwM CiphertextConversionFailed) return . fromByteString . Lazy.ByteString.fromStrict +encrypt = Poly.encrypt (Just 16) $ \str -> maybe (throwM $ CiphertextConversionFailed str) return . fromByteString $ Lazy.ByteString.fromStrict str -- | Decrypt an arbitrary serializable value @@ -68,7 +68,7 @@ decrypt :: forall a m namespace. ) => CryptoIDKey -> CryptoUUID namespace -> m a decrypt = Poly.decrypt $ check . decodeOrFail . toByteString where - check (Left err) = throwM $ DeserializationError err + check (Left _) = throwM DeserializationError check (Right (rem, _, res)) | Lazy.ByteString.all (== 0) rem = return res | otherwise = throwM InvalidNamespaceDetected diff --git a/uuid-crypto/uuid-crypto.cabal b/uuid-crypto/uuid-crypto.cabal index 8950b56..110fd7b 100644 --- a/uuid-crypto/uuid-crypto.cabal +++ b/uuid-crypto/uuid-crypto.cabal @@ -1,5 +1,5 @@ name: uuid-crypto -version: 1.1.1.0 +version: 1.2.0.0 synopsis: Reversable and secure encoding of object ids as uuids license: BSD3 license-file: LICENSE @@ -28,7 +28,7 @@ library other-extensions: ScopedTypeVariables build-depends: base >=4.9 && <4.11 , cryptoids-types ==0.0.0 - , cryptoids ==0.2.0.* + , cryptoids ==0.3.0.* , uuid >=1.3.13 && <1.4 , binary >=0.8.3.0 && <0.9 , bytestring >=0.10.8.1 && <0.11