ppad-aead 0.1.0 → 0.2.0
raw patch · 5 files changed
+84/−59 lines, 5 filesdep +deepseqdep ~ppad-chachadep ~ppad-poly1305PVP ok
version bump matches the API change (PVP)
Dependencies added: deepseq
Dependency ranges changed: ppad-chacha, ppad-poly1305
API changes (from Hackage documentation)
+ Crypto.AEAD.ChaCha20Poly1305: instance GHC.Classes.Eq Crypto.AEAD.ChaCha20Poly1305.Error
+ Crypto.AEAD.ChaCha20Poly1305: instance GHC.Show.Show Crypto.AEAD.ChaCha20Poly1305.Error
- Crypto.AEAD.ChaCha20Poly1305: decrypt :: ByteString -> ByteString -> ByteString -> (ByteString, ByteString) -> Maybe ByteString
+ Crypto.AEAD.ChaCha20Poly1305: decrypt :: ByteString -> ByteString -> ByteString -> (ByteString, ByteString) -> Either Error ByteString
- Crypto.AEAD.ChaCha20Poly1305: encrypt :: ByteString -> ByteString -> ByteString -> ByteString -> (ByteString, ByteString)
+ Crypto.AEAD.ChaCha20Poly1305: encrypt :: ByteString -> ByteString -> ByteString -> ByteString -> Either Error (ByteString, ByteString)
Files
- CHANGELOG +5/−0
- bench/Main.hs +9/−0
- lib/Crypto/AEAD/ChaCha20Poly1305.hs +49/−38
- ppad-aead.cabal +4/−3
- test/Main.hs +17/−18
CHANGELOG view
@@ -1,5 +1,10 @@ # Changelog +- 0.2.0 (2025-06-21)+ * The 'encrypt' and 'decrypt' functions are now total, returning 'Left+ InvalidKey', 'Left InvalidNonce', or 'Left InvalidMAC' values when+ passed the corresponding bad inputs.+ - 0.1.0 (2025-03-09) * Initial release, supporting the ChaCha20-Poly1305 configuration.
bench/Main.hs view
@@ -1,13 +1,22 @@+{-# OPTIONS_GHC -fno-warn-orphans #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE StandaloneDeriving #-} module Main where +import Control.DeepSeq import Criterion.Main import qualified Crypto.AEAD.ChaCha20Poly1305 as AEAD import qualified Data.ByteString as BS import qualified Data.ByteString.Base16 as B16 import Data.Maybe (fromJust)+import GHC.Generics++deriving instance Generic AEAD.Error++instance NFData AEAD.Error main :: IO () main = defaultMain [
lib/Crypto/AEAD/ChaCha20Poly1305.hs view
@@ -18,6 +18,9 @@ encrypt , decrypt + -- * Error information+ , Error(..)+ -- testing , _poly1305_key_gen ) where@@ -56,10 +59,11 @@ _poly1305_key_gen :: BS.ByteString -- ^ 256-bit initial keying material -> BS.ByteString -- ^ 96-bit nonce- -> BS.ByteString -- ^ 256-bit key (suitable for poly1305)-_poly1305_key_gen key@(BI.PS _ _ l) nonce- | l /= 32 = error "ppad-aead (poly1305_key_gen): invalid key"- | otherwise = BS.take 32 (ChaCha20.block key 0 nonce)+ -> Either Error BS.ByteString -- ^ 256-bit key (suitable for poly1305)+_poly1305_key_gen key nonce = case ChaCha20.block key 0 nonce of+ Left ChaCha20.InvalidKey -> Left InvalidKey+ Left ChaCha20.InvalidNonce -> Left InvalidNonce+ Right k -> pure (BS.take 32 k) {-# INLINEABLE _poly1305_key_gen #-} pad16 :: BS.ByteString -> BS.ByteString@@ -68,6 +72,12 @@ | otherwise = BS.replicate (16 - l `rem` 16) 0 {-# INLINE pad16 #-} +data Error =+ InvalidKey+ | InvalidNonce+ | InvalidMAC+ deriving (Eq, Show)+ -- RFC8439 2.8 -- | Perform authenticated encryption on a plaintext and some additional@@ -76,14 +86,11 @@ -- -- Produces a ciphertext and 128-bit message authentication code pair. ----- Providing an invalid key or nonce will result in an 'ErrorCall'--- exception being thrown.--- -- >>> let key = "don't tell anyone my secret key!" -- >>> let non = "or my nonce!" -- >>> let pan = "and here's my plaintext" -- >>> let aad = "i approve this message"--- >>> let (cip, mac) = encrypt aad key nonce pan+-- >>> let Right (cip, mac) = encrypt aad key nonce pan -- >>> (cip, mac) -- <(ciphertext, 128-bit MAC)> encrypt@@ -91,51 +98,55 @@ -> BS.ByteString -- ^ 256-bit key -> BS.ByteString -- ^ 96-bit nonce -> BS.ByteString -- ^ arbitrary-length plaintext- -> (BS.ByteString, BS.ByteString) -- ^ (ciphertext, 128-bit MAC)+ -> Either Error (BS.ByteString, BS.ByteString) -- ^ (ciphertext, 128-bit MAC) encrypt aad key nonce plaintext- | BS.length key /= 32 = error "ppad-aead (encrypt): invalid key"- | BS.length nonce /= 12 = error "ppad-aead (encrypt): invalid nonce"- | otherwise =- let otk = _poly1305_key_gen key nonce- cip = ChaCha20.cipher key 1 nonce plaintext- md0 = aad <> pad16 aad- md1 = md0 <> cip <> pad16 cip- md2 = md1 <> unroll8 (fi (BS.length aad))- md3 = md2 <> unroll8 (fi (BS.length cip))- tag = Poly1305.mac otk md3- in (cip, tag)+ | BS.length key /= 32 = Left InvalidKey+ | BS.length nonce /= 12 = Left InvalidNonce+ | otherwise = do+ otk <- _poly1305_key_gen key nonce+ case ChaCha20.cipher key 1 nonce plaintext of+ Left ChaCha20.InvalidKey -> Left InvalidKey -- impossible, but..+ Left ChaCha20.InvalidNonce -> Left InvalidNonce -- ditto+ Right cip -> do+ let md0 = aad <> pad16 aad+ md1 = md0 <> cip <> pad16 cip+ md2 = md1 <> unroll8 (fi (BS.length aad))+ md3 = md2 <> unroll8 (fi (BS.length cip))+ case Poly1305.mac otk md3 of+ Nothing -> Left InvalidKey+ Just tag -> pure (cip, tag) -- | Decrypt an authenticated ciphertext, given a message authentication -- code and some additional authenticated data, via a 256-bit key and -- 96-bit nonce. ----- Returns 'Nothing' if the MAC fails to validate.------ Providing an invalid key or nonce will result in an 'ErrorCall'--- exception being thrown.--- -- >>> decrypt aad key non (cip, mac)--- Just "and here's my plaintext"+-- Right "and here's my plaintext" -- >>> decrypt aad key non (cip, "it's a valid mac")--- Nothing+-- Left InvalidMAC decrypt :: BS.ByteString -- ^ arbitrary-length AAD -> BS.ByteString -- ^ 256-bit key -> BS.ByteString -- ^ 96-bit nonce -> (BS.ByteString, BS.ByteString) -- ^ (arbitrary-length ciphertext, 128-bit MAC)- -> Maybe BS.ByteString+ -> Either Error BS.ByteString decrypt aad key nonce (cip, mac)- | BS.length key /= 32 = error "ppad-aead (decrypt): invalid key"- | BS.length nonce /= 12 = error "ppad-aead (decrypt): invalid nonce"- | BS.length mac /= 16 = Nothing- | otherwise =- let otk = _poly1305_key_gen key nonce- md0 = aad <> pad16 aad+ | BS.length key /= 32 = Left InvalidKey+ | BS.length nonce /= 12 = Left InvalidNonce+ | BS.length mac /= 16 = Left InvalidMAC+ | otherwise = do+ otk <- _poly1305_key_gen key nonce+ let md0 = aad <> pad16 aad md1 = md0 <> cip <> pad16 cip md2 = md1 <> unroll8 (fi (BS.length aad)) md3 = md2 <> unroll8 (fi (BS.length cip))- tag = Poly1305.mac otk md3- in if mac == tag- then pure (ChaCha20.cipher key 1 nonce cip)- else Nothing+ case Poly1305.mac otk md3 of+ Nothing -> Left InvalidKey+ Just tag+ | mac == tag -> case ChaCha20.cipher key 1 nonce cip of+ Left ChaCha20.InvalidKey -> Left InvalidKey+ Left ChaCha20.InvalidNonce -> Left InvalidNonce+ Right v -> pure v+ | otherwise ->+ Left InvalidMAC
ppad-aead.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: ppad-aead-version: 0.1.0+version: 0.2.0 synopsis: A pure AEAD-ChaCha20-Poly1305 construction license: MIT license-file: LICENSE@@ -29,8 +29,8 @@ build-depends: base >= 4.9 && < 5 , bytestring >= 0.9 && < 0.13- , ppad-chacha >= 0.1 && < 0.2- , ppad-poly1305 >= 0.2 && < 0.3+ , ppad-chacha >= 0.2 && < 0.3+ , ppad-poly1305 >= 0.3 && < 0.4 test-suite aead-tests type: exitcode-stdio-1.0@@ -67,6 +67,7 @@ base , bytestring , criterion+ , deepseq , ppad-base16 , ppad-aead
test/Main.hs view
@@ -5,7 +5,6 @@ module Main where -import Control.Exception import qualified Crypto.AEAD.ChaCha20Poly1305 as AEAD import Data.ByteString as BS import qualified Data.Aeson as A@@ -43,14 +42,14 @@ Just e = B16.decode "8ad5a08b905f81cc815040274ab29471a833b637e3fd0da508dbb8e2fdd1a646" - o = AEAD._poly1305_key_gen key non+ Right o = AEAD._poly1305_key_gen key non H.assertEqual mempty e o crypt :: TestTree crypt = H.testCase "encrypt/decrypt" $ do let nonce = salt <> iv - (o_cip, o_tag) = AEAD.encrypt aad key nonce sunscreen+ Right (o_cip, o_tag) = AEAD.encrypt aad key nonce sunscreen e_cip = fromJust . B16.decode $ "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b6116"@@ -59,10 +58,10 @@ "1ae10b594f09e26a7e902ecbd0600691" - o_dec = AEAD.decrypt aad key nonce (o_cip, o_tag)+ Right o_dec = AEAD.decrypt aad key nonce (o_cip, o_tag) H.assertEqual mempty (e_cip, e_tag) (o_cip, o_tag)- H.assertEqual mempty (Just sunscreen) o_dec+ H.assertEqual mempty sunscreen o_dec where sunscreen :: BS.ByteString sunscreen = fromJust . B16.decode $@@ -94,7 +93,7 @@ tag = fromJust . B16.decode $ "eead9d67890cbb22392336fea1851f38" - Just pan = AEAD.decrypt aad key non (cip, tag)+ Right pan = AEAD.decrypt aad key non (cip, tag) H.assertEqual mempty e_pan pan @@ -115,18 +114,18 @@ msg = aeadt_msg ct = aeadt_ct tag = aeadt_tag- if aeadt_result == "invalid"- then do- out <- try (pure $! AEAD.decrypt aad key iv (ct, tag))- :: IO (Either ErrorCall (Maybe BS.ByteString))- case out of- Left _ -> H.assertBool "invalid (bogus key/nonce)" True- Right Nothing -> H.assertBool "invalid (bogus MAC)" True- Right (Just o) -> H.assertBool "invalid" (msg /= o)- else do- let out = AEAD.decrypt aad key iv (ct, tag)- H.assertEqual mempty (Just msg) out+ case AEAD.decrypt aad key iv (ct, tag) of+ Left _+ | aeadt_result == "invalid" -> H.assertBool "invalid" True+ | otherwise -> H.assertFailure mempty+ Right out+ | aeadt_result == "invalid" -> H.assertFailure mempty+ | otherwise -> case AEAD.encrypt aad key iv msg of+ Left _ -> H.assertFailure mempty+ Right (out_cip, out_mac) -> do+ H.assertEqual mempty ct out_cip+ H.assertEqual mempty tag out_mac+ H.assertEqual mempty msg out where t_msg = "test " <> show aeadt_tcId-