From 908f979d44467d61b3fabef9f06e77417b2bf1bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Ch=C3=A9ron?= Date: Sun, 25 Aug 2019 08:55:52 +0200 Subject: [PATCH] Add AES-GCM-SIV --- Crypto/Cipher/AESGCMSIV.hs | 187 +++++++++++++++++++++++++++++++++++++ cbits/aes/block128.h | 7 ++ cbits/cryptonite_aes.c | 52 +++++++++++ cbits/cryptonite_aes.h | 10 ++ cryptonite.cabal | 1 + 5 files changed, 257 insertions(+) create mode 100644 Crypto/Cipher/AESGCMSIV.hs diff --git a/Crypto/Cipher/AESGCMSIV.hs b/Crypto/Cipher/AESGCMSIV.hs new file mode 100644 index 0000000..81a8c76 --- /dev/null +++ b/Crypto/Cipher/AESGCMSIV.hs @@ -0,0 +1,187 @@ +-- | +-- Module : Crypto.Cipher.AESGCMSIV +-- License : BSD-style +-- Maintainer : Olivier Chéron +-- Stability : experimental +-- Portability : unknown +-- +-- Implementation of AES-GCM-SIV, an AEAD scheme with nonce misuse resistance +-- defined in . +-- +-- To achieve the nonce misuse-resistance property, encryption requires two +-- passes on the plaintext, hence no streaming API is provided. This AEAD +-- operates on complete inputs held in memory. For simplicity, the +-- implementation of decryption uses a similar pattern, with performance +-- penalty compared to an implementation which is able to merge both passes. +-- +-- The specification allows inputs up to 2^36 bytes but this implementation +-- requires AAD and plaintext/ciphertext to be both smaller than 2^32 bytes. +{-# LANGUAGE ForeignFunctionInterface #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +module Crypto.Cipher.AESGCMSIV + ( Nonce + , nonce + , encrypt + , decrypt + ) where + +import Data.Bits +import Data.Word + +import Foreign.C.Types +import Foreign.C.String +import Foreign.Ptr (Ptr, plusPtr) +import Foreign.Storable (peekElemOff, poke, pokeElemOff) + +import Data.ByteArray +import qualified Data.ByteArray as B +import Data.Memory.Endian (toLE) +import Data.Memory.PtrMethods (memXor) + +import Crypto.Cipher.AES.Primitive +import Crypto.Cipher.Types +import Crypto.Error +import Crypto.Internal.Compat (unsafeDoIO) + + +-- 12-byte nonces + +-- | Nonce value for AES-GCM-SIV, always 12 bytes. +newtype Nonce = Nonce Bytes deriving (Show, Eq, ByteArrayAccess) + +-- | Nonce smart constructor. Accepts only 12-byte inputs. +nonce :: ByteArrayAccess iv => iv -> CryptoFailable Nonce +nonce iv + | B.length iv == 12 = CryptoPassed (Nonce $ B.convert iv) + | otherwise = CryptoFailed CryptoError_IvSizeInvalid + + +-- POLYVAL (mutable context) + +newtype Polyval = Polyval Bytes + +polyvalInit :: ScrubbedBytes -> IO Polyval +polyvalInit h = Polyval <$> doInit + where doInit = B.alloc 272 $ \pctx -> B.withByteArray h $ \ph -> + c_aes_polyval_init pctx ph + +polyvalUpdate :: ByteArrayAccess ba => Polyval -> ba -> IO () +polyvalUpdate (Polyval ctx) bs = B.withByteArray ctx $ \pctx -> + B.withByteArray bs $ \pbs -> c_aes_polyval_update pctx pbs sz + where sz = fromIntegral (B.length bs) + +polyvalFinalize :: Polyval -> IO ScrubbedBytes +polyvalFinalize (Polyval ctx) = B.alloc 16 $ \dst -> + B.withByteArray ctx $ \pctx -> c_aes_polyval_finalize pctx dst + +foreign import ccall unsafe "cryptonite_aes.h cryptonite_aes_polyval_init" + c_aes_polyval_init :: Ptr Polyval -> CString -> IO () + +foreign import ccall "cryptonite_aes.h cryptonite_aes_polyval_update" + c_aes_polyval_update :: Ptr Polyval -> CString -> CUInt -> IO () + +foreign import ccall unsafe "cryptonite_aes.h cryptonite_aes_polyval_finalize" + c_aes_polyval_finalize :: Ptr Polyval -> CString -> IO () + + +-- Key Generation + +le32iv :: Word32 -> Nonce -> Bytes +le32iv n (Nonce iv) = B.allocAndFreeze 16 $ \ptr -> do + poke ptr (toLE n) + copyByteArrayToPtr iv (ptr `plusPtr` 4) + +deriveKeys :: BlockCipher128 aes => aes -> Nonce -> (ScrubbedBytes, AES) +deriveKeys aes iv = + case cipherKeySize aes of + KeySizeFixed sz | sz `mod` 8 == 0 -> + let mak = buildKey [0 .. 1] + key = buildKey [2 .. fromIntegral (sz `div` 8) + 1] + mek = throwCryptoError (cipherInit key) + in (mak, mek) + _ -> error "AESGCMSIV: invalid cipher" + where + idx n = ecbEncrypt aes (le32iv n iv) `takeView` 8 + buildKey = B.concat . map idx + + +-- Encryption and decryption + +lengthInvalid :: ByteArrayAccess ba => ba -> Bool +lengthInvalid bs + | finiteBitSize len > 32 = len >= 1 `unsafeShiftL` 32 + | otherwise = False + where len = B.length bs + +-- | AEAD encryption with the specified key and nonce. The key must be given +-- as an initialized 'Crypto.Cipher.AES.AES128' or 'Crypto.Cipher.AES.AES256' +-- cipher. +-- +-- Lengths of additional data and plaintext must be less than 2^32 bytes, +-- otherwise an exception is thrown. +encrypt :: (BlockCipher128 aes, ByteArrayAccess aad, ByteArray ba) + => aes -> Nonce -> aad -> ba -> (AuthTag, ba) +encrypt aes iv aad plaintext + | lengthInvalid aad = error "AESGCMSIV: aad is too large" + | lengthInvalid plaintext = error "AESGCMSIV: plaintext is too large" + | otherwise = (AuthTag tag, ciphertext) + where + (mak, mek) = deriveKeys aes iv + ss = getSs mak aad plaintext + tag = buildTag mek ss iv + ciphertext = combineC32 mek (transformTag tag) plaintext + +-- | AEAD decryption with the specified key and nonce. The key must be given +-- as an initialized 'Crypto.Cipher.AES.AES128' or 'Crypto.Cipher.AES.AES256' +-- cipher. +-- +-- Lengths of additional data and ciphertext must be less than 2^32 bytes, +-- otherwise an exception is thrown. +decrypt :: (BlockCipher128 aes, ByteArrayAccess aad, ByteArray ba) + => aes -> Nonce -> aad -> ba -> AuthTag -> Maybe ba +decrypt aes iv aad ciphertext (AuthTag tag) + | lengthInvalid aad = error "AESGCMSIV: aad is too large" + | lengthInvalid ciphertext = error "AESGCMSIV: ciphertext is too large" + | tag `constEq` buildTag mek ss iv = Just plaintext + | otherwise = Nothing + where + (mak, mek) = deriveKeys aes iv + ss = getSs mak aad plaintext + plaintext = combineC32 mek (transformTag tag) ciphertext + +-- Calculate S_s = POLYVAL(mak, X_1, X_2, ...). +getSs :: (ByteArrayAccess aad, ByteArrayAccess ba) + => ScrubbedBytes -> aad -> ba -> ScrubbedBytes +getSs mak aad plaintext = unsafeDoIO $ do + ctx <- polyvalInit mak + polyvalUpdate ctx aad + polyvalUpdate ctx plaintext + polyvalUpdate ctx (lb :: Bytes) -- the "length block" + polyvalFinalize ctx + where + lb = B.allocAndFreeze 16 $ \ptr -> do + pokeElemOff ptr 0 (toLE64 $ B.length aad) + pokeElemOff ptr 1 (toLE64 $ B.length plaintext) + toLE64 x = toLE (fromIntegral x * 8 :: Word64) + +-- XOR the first 12 bytes of S_s with the nonce and clear the most significant +-- bit of the last byte. +tagInput :: ScrubbedBytes -> Nonce -> Bytes +tagInput ss (Nonce iv) = + B.copyAndFreeze ss $ \ptr -> + B.withByteArray iv $ \ivPtr -> do + memXor ptr ptr ivPtr 12 + b <- peekElemOff ptr 15 + pokeElemOff ptr 15 (b .&. (0x7f :: Word8)) + +-- Encrypt the result with AES using the message-encryption key to produce the +-- tag. +buildTag :: BlockCipher128 aes => aes -> ScrubbedBytes -> Nonce -> Bytes +buildTag mek ss iv = ecbEncrypt mek (tagInput ss iv) + +-- The initial counter block is the tag with the most significant bit of the +-- last byte set to one. +transformTag :: Bytes -> IV AES +transformTag tag = toIV $ B.copyAndFreeze tag $ \ptr -> + peekElemOff ptr 15 >>= pokeElemOff ptr 15 . (.|. (0x80 :: Word8)) + where toIV bs = let Just iv = makeIV (bs :: Bytes) in iv diff --git a/cbits/aes/block128.h b/cbits/aes/block128.h index 12d842f..f09fcf2 100644 --- a/cbits/aes/block128.h +++ b/cbits/aes/block128.h @@ -108,6 +108,13 @@ static inline void block128_vxor(block128 *d, const block128 *s1, const block128 } } +static inline void block128_byte_reverse(block128 *a) +{ + uint64_t s0 = a->q[0], s1 = a->q[1]; + a->q[0] = bitfn_swap64(s1); + a->q[1] = bitfn_swap64(s0); +} + static inline void block128_inc_be(block128 *b) { uint64_t v = be64_to_cpu(b->q[1]); diff --git a/cbits/cryptonite_aes.c b/cbits/cryptonite_aes.c index e70aa07..ab88fd8 100644 --- a/cbits/cryptonite_aes.c +++ b/cbits/cryptonite_aes.c @@ -1055,3 +1055,55 @@ void cryptonite_aes_generic_ocb_decrypt(uint8_t *output, aes_ocb *ocb, aes_key * { ocb_generic_crypt(output, ocb, key, input, length, 0); } + +static inline void gf_mulx_rev(block128 *a, const block128 *h) +{ + uint64_t v1 = cpu_to_le64(h->q[0]); + uint64_t v0 = cpu_to_le64(h->q[1]); + a->q[1] = cpu_to_be64(v1 >> 1 | v0 << 63); + a->q[0] = cpu_to_be64(v0 >> 1 ^ ((0-(v1 & 1)) & 0xe100000000000000ULL)); +} + +void cryptonite_aes_polyval_init(aes_polyval *ctx, const aes_block *h) +{ + aes_block r; + + /* ByteReverse(S_0) = 0 */ + block128_zero(&ctx->s); + + /* ByteReverse(H) * x */ + gf_mulx_rev(&r, h); + cryptonite_hinit(ctx->htable, &r); +} + +void cryptonite_aes_polyval_update(aes_polyval *ctx, const uint8_t *input, uint32_t length) +{ + aes_block r; + const uint8_t *p; + uint32_t sz; + + /* This automatically pads with zeros if input is not a multiple of the + block size. */ + for (p = input; length > 0; p += 16, length -= sz) + { + sz = length < 16 ? length : 16; + + /* ByteReverse(X_j) */ + block128_zero(&r); + memcpy(&r, p, sz); + block128_byte_reverse(&r); + + /* ByteReverse(S_{j-1}) + ByteReverse(X_j) */ + block128_xor_aligned(&ctx->s, &r); + + /* ByteReverse(S_j) */ + cryptonite_gf_mul(&ctx->s, ctx->htable); + } +} + +void cryptonite_aes_polyval_finalize(aes_polyval *ctx, aes_block *dst) +{ + /* S_s */ + block128_copy_aligned(dst, &ctx->s); + block128_byte_reverse(dst); +} diff --git a/cbits/cryptonite_aes.h b/cbits/cryptonite_aes.h index 509ff16..feff121 100644 --- a/cbits/cryptonite_aes.h +++ b/cbits/cryptonite_aes.h @@ -77,6 +77,12 @@ typedef struct { block128 li[4]; } aes_ocb; +/* size = 17*16= 272 */ +typedef struct { + aes_block htable[16]; + aes_block s; +} aes_polyval; + /* in bytes: either 16,24,32 */ void cryptonite_aes_initkey(aes_key *ctx, uint8_t *key, uint8_t size); @@ -117,4 +123,8 @@ void cryptonite_aes_ccm_finish(uint8_t *tag, aes_ccm *ccm, aes_key *key); uint8_t *cryptonite_aes_cpu_init(void); +void cryptonite_aes_polyval_init(aes_polyval *ctx, const aes_block *h); +void cryptonite_aes_polyval_update(aes_polyval *ctx, const uint8_t *input, uint32_t length); +void cryptonite_aes_polyval_finalize(aes_polyval *ctx, aes_block *dst); + #endif diff --git a/cryptonite.cabal b/cryptonite.cabal index f1475e2..416c847 100644 --- a/cryptonite.cabal +++ b/cryptonite.cabal @@ -104,6 +104,7 @@ Flag check_alignment Library Exposed-modules: Crypto.Cipher.AES + Crypto.Cipher.AESGCMSIV Crypto.Cipher.Blowfish Crypto.Cipher.CAST5 Crypto.Cipher.Camellia