Skip to content

Commit

Permalink
unpadded AES-GCM encryption now requires 12 bytes IV (#656)
Browse files Browse the repository at this point in the history
* unpadded AES-GCM encryption now requires 12 bytes IV

* update

* simplify AuthTag encoding
  • Loading branch information
epoberezkin authored Feb 25, 2023
1 parent 733c937 commit e4aad75
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 29 deletions.
79 changes: 50 additions & 29 deletions src/Simplex/Messaging/Crypto.hs
Original file line number Diff line number Diff line change
Expand Up @@ -91,17 +91,19 @@ module Simplex.Messaging.Crypto
-- * AES256 AEAD-GCM scheme
Key (..),
IV (..),
GCMIV (unGCMIV), -- constructor is not exported
AuthTag (..),
encryptAES,
decryptAES,
encryptAEAD,
decryptAEAD,
encryptAESNoPad,
decryptAESNoPad,
authTagSize,
randomAesKey,
randomIV,
randomGCMIV,
ivSize,
gcmIVSize,
gcmIV,

-- * NaCl crypto_box
CbNonce (unCbNonce),
Expand Down Expand Up @@ -170,7 +172,6 @@ import Data.ByteString.Base64 (decode, encode)
import qualified Data.ByteString.Base64.URL as U
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B
import Data.ByteString.Internal (c2w, w2c)
import Data.ByteString.Lazy (fromStrict, toStrict)
import Data.Constraint (Dict (..))
import Data.Kind (Constraint, Type)
Expand Down Expand Up @@ -761,11 +762,19 @@ instance Encoding IV where
smpEncode = unIV
smpP = IV <$> A.take (ivSize @AES256)

-- | GCMIV bytes newtype.
newtype GCMIV = GCMIV {unGCMIV :: ByteString}

gcmIV :: ByteString -> Either CryptoError GCMIV
gcmIV s
| B.length s == gcmIVSize = Right $ GCMIV s
| otherwise = Left CryptoIVError

newtype AuthTag = AuthTag {unAuthTag :: AES.AuthTag}

instance Encoding AuthTag where
smpEncode = B.pack . map w2c . BA.unpack . AES.unAuthTag . unAuthTag
smpP = AuthTag . AES.AuthTag . BA.pack . map c2w . B.unpack <$> A.take authTagSize
smpEncode = BA.convert . unAuthTag
smpP = AuthTag . AES.AuthTag . BA.convert <$> A.take authTagSize

-- | Certificate fingerpint newtype.
--
Expand All @@ -791,50 +800,47 @@ instance FromField KeyHash where fromField = blobFieldDecoder $ parseAll strP
sha256Hash :: ByteString -> ByteString
sha256Hash = BA.convert . (hash :: ByteString -> Digest SHA256)

-- | AEAD-GCM encryption with empty associated data.
--
-- Used as part of hybrid E2E encryption scheme and for SMP transport blocks encryption.
encryptAES :: Key -> IV -> Int -> ByteString -> ExceptT CryptoError IO (AuthTag, ByteString)
encryptAES key iv paddedLen = encryptAEAD key iv paddedLen ""

-- | AEAD-GCM encryption.
-- | AEAD-GCM encryption with associated data.
--
-- Used as part of hybrid E2E encryption scheme and for SMP transport blocks encryption.
-- Used as part of double ratchet encryption.
-- This function requires 16 bytes IV, it transforms IV in cryptonite_aes_gcm_init here:
-- https://github.com/haskell-crypto/cryptonite/blob/master/cbits/cryptonite_aes.c
encryptAEAD :: Key -> IV -> Int -> ByteString -> ByteString -> ExceptT CryptoError IO (AuthTag, ByteString)
encryptAEAD aesKey ivBytes paddedLen ad msg = do
aead <- initAEAD @AES256 aesKey ivBytes
msg' <- liftEither $ pad msg paddedLen
pure . first AuthTag $ AES.aeadSimpleEncrypt aead ad msg' authTagSize

encryptAESNoPad :: Key -> IV -> ByteString -> ExceptT CryptoError IO (AuthTag, ByteString)
-- Used to encrypt WebRTC frames.
-- This function requires 12 bytes IV, it does not transform IV.
encryptAESNoPad :: Key -> GCMIV -> ByteString -> ExceptT CryptoError IO (AuthTag, ByteString)
encryptAESNoPad key iv = encryptAEADNoPad key iv ""

encryptAEADNoPad :: Key -> IV -> ByteString -> ByteString -> ExceptT CryptoError IO (AuthTag, ByteString)
encryptAEADNoPad :: Key -> GCMIV -> ByteString -> ByteString -> ExceptT CryptoError IO (AuthTag, ByteString)
encryptAEADNoPad aesKey ivBytes ad msg = do
aead <- initAEAD @AES256 aesKey ivBytes
aead <- initAEADGCM aesKey ivBytes
pure . first AuthTag $ AES.aeadSimpleEncrypt aead ad msg authTagSize

-- | AEAD-GCM decryption with empty associated data.
-- | AEAD-GCM decryption with associated data.
--
-- Used as part of hybrid E2E encryption scheme and for SMP transport blocks decryption.
decryptAES :: Key -> IV -> ByteString -> AuthTag -> ExceptT CryptoError IO ByteString
decryptAES key iv = decryptAEAD key iv ""

-- | AEAD-GCM decryption.
--
-- Used as part of hybrid E2E encryption scheme and for SMP transport blocks decryption.
-- Used as part of double ratchet encryption.
-- This function requires 16 bytes IV, it transforms IV in cryptonite_aes_gcm_init here:
-- https://github.com/haskell-crypto/cryptonite/blob/master/cbits/cryptonite_aes.c
-- To make it compatible with WebCrypto we will need to start using initAEADGCM.
decryptAEAD :: Key -> IV -> ByteString -> ByteString -> AuthTag -> ExceptT CryptoError IO ByteString
decryptAEAD aesKey ivBytes ad msg (AuthTag authTag) = do
aead <- initAEAD @AES256 aesKey ivBytes
liftEither . unPad =<< maybeError AESDecryptError (AES.aeadSimpleDecrypt aead ad msg authTag)

decryptAESNoPad :: Key -> IV -> ByteString -> AuthTag -> ExceptT CryptoError IO ByteString
-- Used to decrypt WebRTC frames.
-- This function requires 12 bytes IV, it does not transform IV.
decryptAESNoPad :: Key -> GCMIV -> ByteString -> AuthTag -> ExceptT CryptoError IO ByteString
decryptAESNoPad key iv = decryptAEADNoPad key iv ""

decryptAEADNoPad :: Key -> IV -> ByteString -> ByteString -> AuthTag -> ExceptT CryptoError IO ByteString
decryptAEADNoPad aesKey ivBytes ad msg (AuthTag authTag) = do
aead <- initAEAD @AES256 aesKey ivBytes
maybeError AESDecryptError (AES.aeadSimpleDecrypt aead ad msg authTag)
decryptAEADNoPad :: Key -> GCMIV -> ByteString -> ByteString -> AuthTag -> ExceptT CryptoError IO ByteString
decryptAEADNoPad aesKey iv ad msg (AuthTag tag) = do
aead <- initAEADGCM aesKey iv
maybeError AESDecryptError (AES.aeadSimpleDecrypt aead ad msg tag)

maxMsgLen :: Int
maxMsgLen = 2 ^ (16 :: Int) - 3
Expand Down Expand Up @@ -890,13 +896,22 @@ appendMaxLenBS (MLBS s1) (MLBS s2) = MLBS $ s1 <> s2
maxLength :: forall i. KnownNat i => Int
maxLength = fromIntegral (natVal $ Proxy @i)

-- this function requires 16 bytes IV, it transforms IV in cryptonite_aes_gcm_init here:
-- https://github.com/haskell-crypto/cryptonite/blob/master/cbits/cryptonite_aes.c
-- This is used for double ratchet encryption, so to make it compatible with WebCrypto we will need to deprecate it and start using initAEADGCM
initAEAD :: forall c. AES.BlockCipher c => Key -> IV -> ExceptT CryptoError IO (AES.AEAD c)
initAEAD (Key aesKey) (IV ivBytes) = do
iv <- makeIV @c ivBytes
cryptoFailable $ do
cipher <- AES.cipherInit aesKey
AES.aeadInit AES.AEAD_GCM cipher iv

-- this function requires 12 bytes IV, it does not transforms IV.
initAEADGCM :: Key -> GCMIV -> ExceptT CryptoError IO (AES.AEAD AES256)
initAEADGCM (Key aesKey) (GCMIV ivBytes) = cryptoFailable $ do
cipher <- AES.cipherInit aesKey
AES.aeadInit AES.AEAD_GCM cipher ivBytes

-- | Random AES256 key.
randomAesKey :: IO Key
randomAesKey = Key <$> getRandomBytes aesKeySize
Expand All @@ -905,9 +920,15 @@ randomAesKey = Key <$> getRandomBytes aesKeySize
randomIV :: IO IV
randomIV = IV <$> getRandomBytes (ivSize @AES256)

randomGCMIV :: IO GCMIV
randomGCMIV = GCMIV <$> getRandomBytes gcmIVSize

ivSize :: forall c. AES.BlockCipher c => Int
ivSize = AES.blockSize (undefined :: c)

gcmIVSize :: Int
gcmIVSize = 12

makeIV :: AES.BlockCipher c => ByteString -> ExceptT CryptoError IO (AES.IV c)
makeIV bs = maybeError CryptoIVError $ AES.makeIV bs

Expand Down
14 changes: 14 additions & 0 deletions tests/CoreTests/CryptoTests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

module CoreTests.CryptoTests (cryptoTests) where

import Control.Monad.Except
import Crypto.Random (getRandomBytes)
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy.Char8 as LB
import Data.Either (isRight)
Expand Down Expand Up @@ -76,6 +78,8 @@ cryptoTests = do
describe "lazy secretbox" $ do
testLazySecretBox
testLazySecretBoxFile
describe "AES GCM" $ do
testAESGCM
describe "X509 key encoding" $ do
describe "Ed25519" $ testEncoding C.SEd25519
describe "Ed448" $ testEncoding C.SEd448
Expand Down Expand Up @@ -148,6 +152,16 @@ testLazySecretBoxFile = it "should lazily encrypt / decrypt file with a random s
Right s'' <- LC.sbDecrypt k nonce <$> LB.readFile (f <> ".encrypted")
s'' `shouldBe` s

testAESGCM :: Spec
testAESGCM = it "should encrypt / decrypt string with a random symmetric key" $ do
k <- C.randomAesKey
iv <- C.randomGCMIV
s <- getRandomBytes 100
Right (tag, cipher) <- runExceptT $ C.encryptAESNoPad k iv s
Right plain <- runExceptT $ C.decryptAESNoPad k iv cipher tag
cipher `shouldNotBe` plain
s `shouldBe` plain

testEncoding :: (C.AlgorithmI a) => C.SAlgorithm a -> Spec
testEncoding alg = it "should encode / decode key" . ioProperty $ do
(k, pk) <- C.generateKeyPair alg
Expand Down

0 comments on commit e4aad75

Please sign in to comment.