From ce849fb0d2708f446b58fd78088403ca0e08ca59 Mon Sep 17 00:00:00 2001 From: Vincent Hanquez Date: Sun, 19 Jul 2015 17:53:56 +0100 Subject: [PATCH] [ChaChaPoly1305] add implementation and simple KAT test --- Crypto/Cipher/ChaChaPoly1305.hs | 112 ++++++++++++++++++++++++++++++++ cryptonite.cabal | 1 + tests/ChaChaPoly1305.hs | 31 +++++++++ tests/Tests.hs | 2 + 4 files changed, 146 insertions(+) create mode 100644 Crypto/Cipher/ChaChaPoly1305.hs create mode 100644 tests/ChaChaPoly1305.hs diff --git a/Crypto/Cipher/ChaChaPoly1305.hs b/Crypto/Cipher/ChaChaPoly1305.hs new file mode 100644 index 0000000..4bf0bac --- /dev/null +++ b/Crypto/Cipher/ChaChaPoly1305.hs @@ -0,0 +1,112 @@ +-- | +-- Module : Crypto.Cipher.ChaChaPoly1305 +-- License : BSD-style +-- Maintainer : Vincent Hanquez +-- Stability : stable +-- Portability : good +-- +-- A simple AEAD scheme using ChaCha20 and Poly1305. +-- +-- See RFC7539. +-- +module Crypto.Cipher.ChaChaPoly1305 + ( State + , Nonce + , nonce12 + , nonce8 + , initialize + , appendAAD + , finalizeAAD + , combine + , finalize + ) where + +import Crypto.Internal.ByteArray (ByteArrayAccess, ByteArray, Bytes, ScrubbedBytes) +import qualified Crypto.Internal.ByteArray as B +import Crypto.Internal.Imports +import Crypto.Error +import qualified Crypto.Cipher.ChaCha as ChaCha +import qualified Crypto.MAC.Poly1305 as Poly1305 +import Data.Memory.Endian +import qualified Data.ByteArray.Pack as P + +data State = State !ChaCha.State + !Poly1305.State + !Word64 -- AAD length + !Word64 -- ciphertext length + +newtype Nonce = Nonce Bytes + +-- Based on the following pseudo code: +-- +-- chacha20_aead_encrypt(aad, key, iv, constant, plaintext): +-- nonce = constant | iv +-- otk = poly1305_key_gen(key, nonce) +-- ciphertext = chacha20_encrypt(key, 1, nonce, plaintext) +-- mac_data = aad | pad16(aad) +-- mac_data |= ciphertext | pad16(ciphertext) +-- mac_data |= num_to_4_le_bytes(aad.length) +-- mac_data |= num_to_4_le_bytes(ciphertext.length) +-- tag = poly1305_mac(mac_data, otk) +-- return (ciphertext, tag) + +pad16 :: Word64 -> Bytes +pad16 n + | modLen == 0 = B.empty + | otherwise = B.replicate (16 - modLen) 0 + where + modLen = fromIntegral (n `mod` 16) + +-- | Nonce smart constructor 12 bytes IV, nonce constructor +nonce12 :: ByteArrayAccess iv => iv -> CryptoFailable Nonce +nonce12 iv + | B.length iv /= 12 = CryptoFailed $ CryptoError_IvSizeInvalid + | otherwise = CryptoPassed $ Nonce (B.convert iv) + +-- | 8 bytes IV, nonce constructor +nonce8 :: ByteArrayAccess ba + => ba -- ^ 4 bytes constant + -> ba -- ^ 8 bytes IV + -> CryptoFailable Nonce +nonce8 constant iv + | B.length constant /= 4 = CryptoFailed $ CryptoError_IvSizeInvalid + | B.length iv /= 8 = CryptoFailed $ CryptoError_IvSizeInvalid + | otherwise = CryptoPassed $ Nonce $ B.concat [constant, iv] + +initialize :: ByteArrayAccess key + => key -> Nonce -> CryptoFailable State +initialize key (Nonce nonce) + | B.length key /= 32 = CryptoFailed $ CryptoError_KeySizeInvalid + | otherwise = CryptoPassed $ State encState polyState 0 0 + where + rootState = ChaCha.initialize 20 key nonce + (polyKey, encState) = ChaCha.generate rootState 64 + polyState = Poly1305.initialize (B.take 32 polyKey :: ScrubbedBytes) + +appendAAD :: ByteArrayAccess ba => ba -> State -> State +appendAAD ba (State encState macState aadLength plainLength) = + State encState newMacState newLength plainLength + where + newMacState = Poly1305.update macState ba + newLength = aadLength + fromIntegral (B.length ba) + +finalizeAAD :: State -> State +finalizeAAD (State encState macState aadLength plainLength) = + State encState newMacState aadLength plainLength + where + newMacState = Poly1305.update macState $ pad16 aadLength + +combine :: ByteArray ba => ba -> State -> (ba, State) +combine input (State encState macState aadLength plainLength) = + (output, State newEncState newMacState aadLength newPlainLength) + where + (output, newEncState) = ChaCha.combine encState input + newMacState = Poly1305.update macState output + newPlainLength = plainLength + fromIntegral (B.length input) + +finalize :: State -> Poly1305.Auth +finalize (State _ macState aadLength plainLength) = + Poly1305.finalize $ Poly1305.updates macState + [ pad16 plainLength + , either (error "finalize: internal error") id $ P.fill 16 (P.putStorable (LE aadLength) >> P.putStorable (LE plainLength)) + ] diff --git a/cryptonite.cabal b/cryptonite.cabal index 953714a..091391c 100644 --- a/cryptonite.cabal +++ b/cryptonite.cabal @@ -76,6 +76,7 @@ Library Crypto.Cipher.Blowfish Crypto.Cipher.Camellia Crypto.Cipher.ChaCha + Crypto.Cipher.ChaChaPoly1305 Crypto.Cipher.DES Crypto.Cipher.RC4 Crypto.Cipher.Salsa diff --git a/tests/ChaChaPoly1305.hs b/tests/ChaChaPoly1305.hs new file mode 100644 index 0000000..73fafe9 --- /dev/null +++ b/tests/ChaChaPoly1305.hs @@ -0,0 +1,31 @@ +{-# LANGUAGE OverloadedStrings #-} +module ChaChaPoly1305 where + +import qualified Crypto.Cipher.ChaChaPoly1305 as AEAD +import Imports +import Crypto.Error +import Poly1305 () + +import qualified Data.ByteString as B +import qualified Data.ByteArray as B (convert) + +plaintext, aad, key, iv, ciphertext, tag :: B.ByteString +plaintext = "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it." +aad = "\x50\x51\x52\x53\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7" +key = "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f" +iv = "\x40\x41\x42\x43\x44\x45\x46\x47" +constant = "\x07\x00\x00\x00" +ciphertext = "\xd3\x1a\x8d\x34\x64\x8e\x60\xdb\x7b\x86\xaf\xbc\x53\xef\x7e\xc2\xa4\xad\xed\x51\x29\x6e\x08\xfe\xa9\xe2\xb5\xa7\x36\xee\x62\xd6\x3d\xbe\xa4\x5e\x8c\xa9\x67\x12\x82\xfa\xfb\x69\xda\x92\x72\x8b\x1a\x71\xde\x0a\x9e\x06\x0b\x29\x05\xd6\xa5\xb6\x7e\xcd\x3b\x36\x92\xdd\xbd\x7f\x2d\x77\x8b\x8c\x98\x03\xae\xe3\x28\x09\x1b\x58\xfa\xb3\x24\xe4\xfa\xd6\x75\x94\x55\x85\x80\x8b\x48\x31\xd7\xbc\x3f\xf4\xde\xf0\x8e\x4b\x7a\x9d\xe5\x76\xd2\x65\x86\xce\xc6\x4b\x61\x16" +tag = "\x1a\xe1\x0b\x59\x4f\x09\xe2\x6a\x7e\x90\x2e\xcb\xd0\x60\x06\x91" + +tests = testGroup "ChaChaPoly1305" + [ testCase "V1" runEncrypt + ] + where runEncrypt = + let ini = throwCryptoError $ AEAD.initialize key (throwCryptoError $ AEAD.nonce8 constant iv) + afterAAD = AEAD.finalizeAAD (AEAD.appendAAD aad ini) + (out, afterEncrypt) = AEAD.combine plaintext afterAAD + outtag = AEAD.finalize afterEncrypt + in propertyHoldCase [ eqTest "ciphertext" ciphertext out + , eqTest "tag" tag (B.convert outtag) + ] diff --git a/tests/Tests.hs b/tests/Tests.hs index f422de0..ebba3fd 100644 --- a/tests/Tests.hs +++ b/tests/Tests.hs @@ -8,6 +8,7 @@ import qualified Hash import qualified Poly1305 import qualified Salsa import qualified ChaCha +import qualified ChaChaPoly1305 import qualified KAT_HMAC import qualified KAT_PBKDF2 import qualified KAT_Curve25519 @@ -48,6 +49,7 @@ tests = testGroup "cryptonite" , testGroup "stream-cipher" [ KAT_RC4.tests , ChaCha.tests + , ChaChaPoly1305.tests , Salsa.tests ] , KAT_AFIS.tests