ChaCha20Poly1305.hs (4689B)
1 {-# OPTIONS_HADDOCK prune #-} 2 {-# LANGUAGE BangPatterns #-} 3 {-# LANGUAGE LambdaCase #-} 4 {-# LANGUAGE OverloadedStrings #-} 5 {-# LANGUAGE ViewPatterns #-} 6 7 -- | 8 -- Module: Crypto.AEAD.ChaCha20Poly1305 9 -- Copyright: (c) 2025 Jared Tobin 10 -- License: MIT 11 -- Maintainer: Jared Tobin <jared@ppad.tech> 12 -- 13 -- A pure AEAD-ChaCha20-Poly1305 implementation, as specified by 14 -- [RFC 8439](https://datatracker.ietf.org/doc/html/rfc8439). 15 16 module Crypto.AEAD.ChaCha20Poly1305 ( 17 -- * AEAD construction 18 encrypt 19 , decrypt 20 21 -- testing 22 , _poly1305_key_gen 23 ) where 24 25 import qualified Crypto.Cipher.ChaCha20 as ChaCha20 26 import qualified Crypto.MAC.Poly1305 as Poly1305 27 import Data.Bits ((.>>.)) 28 import qualified Data.ByteString as BS 29 import qualified Data.ByteString.Internal as BI 30 import Data.Word (Word64) 31 32 fi :: (Integral a, Num b) => a -> b 33 fi = fromIntegral 34 {-# INLINE fi #-} 35 36 -- little-endian bytestring encoding 37 unroll :: Word64 -> BS.ByteString 38 unroll i = case i of 39 0 -> BS.singleton 0 40 _ -> BS.unfoldr coalg i 41 where 42 coalg = \case 43 0 -> Nothing 44 m -> Just $! (fi m, m .>>. 8) 45 {-# INLINE unroll #-} 46 47 -- little-endian bytestring encoding for 64-bit ints, right-padding with zeros 48 unroll8 :: Word64 -> BS.ByteString 49 unroll8 (unroll -> u@(BI.PS _ _ l)) 50 | l < 8 = u <> BS.replicate (8 - l) 0 51 | otherwise = u 52 {-# INLINE unroll8 #-} 53 54 -- RFC8439 2.6 55 56 _poly1305_key_gen 57 :: BS.ByteString -- ^ 256-bit initial keying material 58 -> BS.ByteString -- ^ 96-bit nonce 59 -> BS.ByteString -- ^ 256-bit key (suitable for poly1305) 60 _poly1305_key_gen key@(BI.PS _ _ l) nonce 61 | l /= 32 = error "ppad-aead (poly1305_key_gen): invalid key" 62 | otherwise = BS.take 32 (ChaCha20.block key 0 nonce) 63 {-# INLINEABLE _poly1305_key_gen #-} 64 65 pad16 :: BS.ByteString -> BS.ByteString 66 pad16 (BI.PS _ _ l) 67 | l `rem` 16 == 0 = mempty 68 | otherwise = BS.replicate (16 - l `rem` 16) 0 69 {-# INLINE pad16 #-} 70 71 -- RFC8439 2.8 72 73 -- | Perform authenticated encryption on a plaintext and some additional 74 -- authenticated data, given a 256-bit key and 96-bit nonce, using 75 -- AEAD-ChaCha20-Poly1305. 76 -- 77 -- Produces a ciphertext and 128-bit message authentication code pair. 78 -- 79 -- Providing an invalid key or nonce will result in an 'ErrorCall' 80 -- exception being thrown. 81 -- 82 -- >>> let key = "don't tell anyone my secret key!" 83 -- >>> let non = "or my nonce!" 84 -- >>> let pan = "and here's my plaintext" 85 -- >>> let aad = "i approve this message" 86 -- >>> let (cip, mac) = encrypt aad key nonce pan 87 -- >>> (cip, mac) 88 -- <(ciphertext, 128-bit MAC)> 89 encrypt 90 :: BS.ByteString -- ^ arbitrary-length additional authenticated data 91 -> BS.ByteString -- ^ 256-bit key 92 -> BS.ByteString -- ^ 96-bit nonce 93 -> BS.ByteString -- ^ arbitrary-length plaintext 94 -> (BS.ByteString, BS.ByteString) -- ^ (ciphertext, 128-bit MAC) 95 encrypt aad key nonce plaintext 96 | BS.length key /= 32 = error "ppad-aead (encrypt): invalid key" 97 | BS.length nonce /= 12 = error "ppad-aead (encrypt): invalid nonce" 98 | otherwise = 99 let otk = _poly1305_key_gen key nonce 100 cip = ChaCha20.cipher key 1 nonce plaintext 101 md0 = aad <> pad16 aad 102 md1 = md0 <> cip <> pad16 cip 103 md2 = md1 <> unroll8 (fi (BS.length aad)) 104 md3 = md2 <> unroll8 (fi (BS.length cip)) 105 tag = Poly1305.mac otk md3 106 in (cip, tag) 107 108 -- | Decrypt an authenticated ciphertext, given a message authentication 109 -- code and some additional authenticated data, via a 256-bit key and 110 -- 96-bit nonce. 111 -- 112 -- Returns 'Nothing' if the MAC fails to validate. 113 -- 114 -- Providing an invalid key or nonce will result in an 'ErrorCall' 115 -- exception being thrown. 116 -- 117 -- >>> decrypt aad key non (cip, mac) 118 -- Just "and here's my plaintext" 119 -- >>> decrypt aad key non (cip, "it's a valid mac") 120 -- Nothing 121 decrypt 122 :: BS.ByteString -- ^ arbitrary-length AAD 123 -> BS.ByteString -- ^ 256-bit key 124 -> BS.ByteString -- ^ 96-bit nonce 125 -> (BS.ByteString, BS.ByteString) -- ^ (arbitrary-length ciphertext, 128-bit MAC) 126 -> Maybe BS.ByteString 127 decrypt aad key nonce (cip, mac) 128 | BS.length key /= 32 = error "ppad-aead (decrypt): invalid key" 129 | BS.length nonce /= 12 = error "ppad-aead (decrypt): invalid nonce" 130 | BS.length mac /= 16 = Nothing 131 | otherwise = 132 let otk = _poly1305_key_gen key nonce 133 md0 = aad <> pad16 aad 134 md1 = md0 <> cip <> pad16 cip 135 md2 = md1 <> unroll8 (fi (BS.length aad)) 136 md3 = md2 <> unroll8 (fi (BS.length cip)) 137 tag = Poly1305.mac otk md3 138 in if mac == tag 139 then pure (ChaCha20.cipher key 1 nonce cip) 140 else Nothing 141