packages feed

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 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-