1-- | 2-- Module : Crypto.Cipher.ChaChaPoly1305 3-- License : BSD-style 4-- Maintainer : Vincent Hanquez <vincent@snarc.org> 5-- Stability : stable 6-- Portability : good 7-- 8-- A simple AEAD scheme using ChaCha20 and Poly1305. See 9-- <https://tools.ietf.org/html/rfc7539 RFC 7539>. 10-- 11-- The State is not modified in place, so each function changing the State, 12-- returns a new State. 13-- 14-- Authenticated Data need to be added before any call to 'encrypt' or 'decrypt', 15-- and once all the data has been added, then 'finalizeAAD' need to be called. 16-- 17-- Once 'finalizeAAD' has been called, no further 'appendAAD' call should be make. 18-- 19-- >import Data.ByteString.Char8 as B 20-- >import Data.ByteArray 21-- >import Crypto.Error 22-- >import Crypto.Cipher.ChaChaPoly1305 as C 23-- > 24-- >encrypt 25-- > :: ByteString -- nonce (12 random bytes) 26-- > -> ByteString -- symmetric key 27-- > -> ByteString -- optional associated data (won't be encrypted) 28-- > -> ByteString -- input plaintext to be encrypted 29-- > -> CryptoFailable ByteString -- ciphertext with a 128-bit tag attached 30-- >encrypt nonce key header plaintext = do 31-- > st1 <- C.nonce12 nonce >>= C.initialize key 32-- > let 33-- > st2 = C.finalizeAAD $ C.appendAAD header st1 34-- > (out, st3) = C.encrypt plaintext st2 35-- > auth = C.finalize st3 36-- > return $ out `B.append` Data.ByteArray.convert auth 37-- 38module Crypto.Cipher.ChaChaPoly1305 39 ( State 40 , Nonce 41 , nonce12 42 , nonce8 43 , incrementNonce 44 , initialize 45 , appendAAD 46 , finalizeAAD 47 , encrypt 48 , decrypt 49 , finalize 50 ) where 51 52import Control.Monad (when) 53import Crypto.Internal.ByteArray (ByteArrayAccess, ByteArray, Bytes, ScrubbedBytes) 54import qualified Crypto.Internal.ByteArray as B 55import Crypto.Internal.Imports 56import Crypto.Error 57import qualified Crypto.Cipher.ChaCha as ChaCha 58import qualified Crypto.MAC.Poly1305 as Poly1305 59import Data.Memory.Endian 60import qualified Data.ByteArray.Pack as P 61import Foreign.Ptr 62import Foreign.Storable 63 64-- | A ChaChaPoly1305 State. 65-- 66-- The state is immutable, and only new state can be created 67data State = State !ChaCha.State 68 !Poly1305.State 69 !Word64 -- AAD length 70 !Word64 -- ciphertext length 71 72-- | Valid Nonce for ChaChaPoly1305. 73-- 74-- It can be created with 'nonce8' or 'nonce12' 75data Nonce = Nonce8 Bytes | Nonce12 Bytes 76 77instance ByteArrayAccess Nonce where 78 length (Nonce8 n) = B.length n 79 length (Nonce12 n) = B.length n 80 81 withByteArray (Nonce8 n) = B.withByteArray n 82 withByteArray (Nonce12 n) = B.withByteArray n 83 84-- Based on the following pseudo code: 85-- 86-- chacha20_aead_encrypt(aad, key, iv, constant, plaintext): 87-- nonce = constant | iv 88-- otk = poly1305_key_gen(key, nonce) 89-- ciphertext = chacha20_encrypt(key, 1, nonce, plaintext) 90-- mac_data = aad | pad16(aad) 91-- mac_data |= ciphertext | pad16(ciphertext) 92-- mac_data |= num_to_4_le_bytes(aad.length) 93-- mac_data |= num_to_4_le_bytes(ciphertext.length) 94-- tag = poly1305_mac(mac_data, otk) 95-- return (ciphertext, tag) 96 97pad16 :: Word64 -> Bytes 98pad16 n 99 | modLen == 0 = B.empty 100 | otherwise = B.replicate (16 - modLen) 0 101 where 102 modLen = fromIntegral (n `mod` 16) 103 104-- | Nonce smart constructor 12 bytes IV, nonce constructor 105nonce12 :: ByteArrayAccess iv => iv -> CryptoFailable Nonce 106nonce12 iv 107 | B.length iv /= 12 = CryptoFailed CryptoError_IvSizeInvalid 108 | otherwise = CryptoPassed . Nonce12 . B.convert $ iv 109 110-- | 8 bytes IV, nonce constructor 111nonce8 :: ByteArrayAccess ba 112 => ba -- ^ 4 bytes constant 113 -> ba -- ^ 8 bytes IV 114 -> CryptoFailable Nonce 115nonce8 constant iv 116 | B.length constant /= 4 = CryptoFailed CryptoError_IvSizeInvalid 117 | B.length iv /= 8 = CryptoFailed CryptoError_IvSizeInvalid 118 | otherwise = CryptoPassed . Nonce8 . B.concat $ [constant, iv] 119 120-- | Increment a nonce 121incrementNonce :: Nonce -> Nonce 122incrementNonce (Nonce8 n) = Nonce8 $ incrementNonce' n 4 123incrementNonce (Nonce12 n) = Nonce12 $ incrementNonce' n 0 124 125incrementNonce' :: Bytes -> Int -> Bytes 126incrementNonce' b offset = B.copyAndFreeze b $ \s -> 127 loop s (s `plusPtr` offset) 128 where 129 loop :: Ptr Word8 -> Ptr Word8 -> IO () 130 loop s p 131 | s == (p `plusPtr` (B.length b - offset - 1)) = peek s >>= poke s . (+) 1 132 | otherwise = do 133 r <- (+) 1 <$> peek p 134 poke p r 135 when (r == 0) $ loop s (p `plusPtr` 1) 136 137-- | Initialize a new ChaChaPoly1305 State 138-- 139-- The key length need to be 256 bits, and the nonce 140-- procured using either `nonce8` or `nonce12` 141initialize :: ByteArrayAccess key 142 => key -> Nonce -> CryptoFailable State 143initialize key (Nonce8 nonce) = initialize' key nonce 144initialize key (Nonce12 nonce) = initialize' key nonce 145 146initialize' :: ByteArrayAccess key 147 => key -> Bytes -> CryptoFailable State 148initialize' key nonce 149 | B.length key /= 32 = CryptoFailed CryptoError_KeySizeInvalid 150 | otherwise = CryptoPassed $ State encState polyState 0 0 151 where 152 rootState = ChaCha.initialize 20 key nonce 153 (polyKey, encState) = ChaCha.generate rootState 64 154 polyState = throwCryptoError $ Poly1305.initialize (B.take 32 polyKey :: ScrubbedBytes) 155 156-- | Append Authenticated Data to the State and return 157-- the new modified State. 158-- 159-- Once no further call to this function need to be make, 160-- the user should call 'finalizeAAD' 161appendAAD :: ByteArrayAccess ba => ba -> State -> State 162appendAAD ba (State encState macState aadLength plainLength) = 163 State encState newMacState newLength plainLength 164 where 165 newMacState = Poly1305.update macState ba 166 newLength = aadLength + fromIntegral (B.length ba) 167 168-- | Finalize the Authenticated Data and return the finalized State 169finalizeAAD :: State -> State 170finalizeAAD (State encState macState aadLength plainLength) = 171 State encState newMacState aadLength plainLength 172 where 173 newMacState = Poly1305.update macState $ pad16 aadLength 174 175-- | Encrypt a piece of data and returns the encrypted Data and the 176-- updated State. 177encrypt :: ByteArray ba => ba -> State -> (ba, State) 178encrypt input (State encState macState aadLength plainLength) = 179 (output, State newEncState newMacState aadLength newPlainLength) 180 where 181 (output, newEncState) = ChaCha.combine encState input 182 newMacState = Poly1305.update macState output 183 newPlainLength = plainLength + fromIntegral (B.length input) 184 185-- | Decrypt a piece of data and returns the decrypted Data and the 186-- updated State. 187decrypt :: ByteArray ba => ba -> State -> (ba, State) 188decrypt input (State encState macState aadLength plainLength) = 189 (output, State newEncState newMacState aadLength newPlainLength) 190 where 191 (output, newEncState) = ChaCha.combine encState input 192 newMacState = Poly1305.update macState input 193 newPlainLength = plainLength + fromIntegral (B.length input) 194 195-- | Generate an authentication tag from the State. 196finalize :: State -> Poly1305.Auth 197finalize (State _ macState aadLength plainLength) = 198 Poly1305.finalize $ Poly1305.updates macState 199 [ pad16 plainLength 200 , either (error "finalize: internal error") id $ P.fill 16 (P.putStorable (toLE aadLength) >> P.putStorable (toLE plainLength)) 201 ] 202