diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+0.7.5
+-----
+
+* A JWT parser is now used to separate parsing and decoding into separaate stages (internal change).
+
 0.7.4
 -----
 
diff --git a/Jose/Internal/Crypto.hs b/Jose/Internal/Crypto.hs
--- a/Jose/Internal/Crypto.hs
+++ b/Jose/Internal/Crypto.hs
@@ -26,7 +26,7 @@
 import           Control.Monad (when, unless)
 import           Crypto.Error
 import           Crypto.Cipher.AES
-import           Crypto.Cipher.Types
+import           Crypto.Cipher.Types hiding (IV)
 import           Crypto.Hash.Algorithms
 import           Crypto.Number.Serialize (os2ip)
 import qualified Crypto.PubKey.ECC.ECDSA as ECDSA
@@ -46,6 +46,7 @@
 
 import           Jose.Jwa
 import           Jose.Types (JwtError(..))
+import           Jose.Internal.Parser (IV(..), Tag(..))
 
 -- | Sign a message with an HMAC key.
 hmacSign :: JwsAlg      -- ^ HMAC algorithm to use
@@ -186,44 +187,45 @@
 
 -- | Decrypt an AES encrypted message.
 decryptPayload :: Enc        -- ^ Encryption algorithm
-               -> ByteString -- ^ Content management key
-               -> ByteString -- ^ IV
+               -> ByteString -- ^ Content encryption key
+               -> IV         -- ^ IV
                -> ByteString -- ^ Additional authentication data
-               -> AuthTag    -- ^ The integrity protection value to be checked
+               -> Tag        -- ^ The integrity protection value to be checked
                -> ByteString -- ^ The encrypted JWT payload
                -> Maybe ByteString
-decryptPayload enc cek iv aad sig ct = case enc of
-    A128GCM       -> doGCM (C :: C AES128)
-    A192GCM       -> doGCM (C :: C AES192)
-    A256GCM       -> doGCM (C :: C AES256)
-    A128CBC_HS256 -> doCBC (C :: C AES128) SHA256 16
-    A192CBC_HS384 -> doCBC (C :: C AES192) SHA384 24
-    A256CBC_HS512 -> doCBC (C :: C AES256) SHA512 32
+decryptPayload enc cek iv_ aad tag_ ct = case (enc, iv_, tag_) of
+    (A128GCM, IV12 b, Tag16 t) -> doGCM (C :: C AES128) b t
+    (A192GCM, IV12 b, Tag16 t) -> doGCM (C :: C AES192) b t
+    (A256GCM, IV12 b, Tag16 t) -> doGCM (C :: C AES256) b t
+    (A128CBC_HS256, IV16 b, Tag16 t) -> doCBC (C :: C AES128) b t SHA256 16
+    (A192CBC_HS384, IV16 b, Tag24 t) -> doCBC (C :: C AES192) b t SHA384 24
+    (A256CBC_HS512, IV16 b, Tag32 t) -> doCBC (C :: C AES256) b t SHA512 32
+    _ -> Nothing -- This shouldn't be possible if the JWT was parsed first
   where
     (cbcMacKey, cbcEncKey) = B.splitAt (B.length cek `div` 2) cek
     al = fromIntegral (B.length aad) * 8 :: Word64
 
-    doGCM :: BlockCipher c => C c -> Maybe ByteString
-    doGCM c = do
+    doGCM :: BlockCipher c => C c -> ByteString -> ByteString -> Maybe ByteString
+    doGCM c iv tag = do
         cipher <- rightToMaybe (initCipher c cek)
         aead <- maybeCryptoError (aeadInit AEAD_GCM cipher iv)
-        aeadSimpleDecrypt aead aad ct (AuthTag $ BA.convert sig)
+        aeadSimpleDecrypt aead aad ct (AuthTag $ BA.convert tag)
 
-    doCBC :: (HashAlgorithm a, BlockCipher c) => C c -> a -> Int -> Maybe ByteString
-    doCBC c a tagLen = do
-        checkMac a tagLen
+    doCBC :: (HashAlgorithm a, BlockCipher c) => C c -> ByteString -> ByteString -> a -> Int -> Maybe ByteString
+    doCBC c iv tag a tagLen = do
+        checkMac a tag iv tagLen
         cipher <- rightToMaybe (initCipher c cbcEncKey)
         iv'    <- makeIV iv
         unless (B.length ct `mod` blockSize cipher == 0) Nothing
         unpad $ cbcDecrypt cipher iv' ct
 
-    checkMac :: HashAlgorithm a => a -> Int -> Maybe ()
-    checkMac a l = do
-        let mac = BA.take l $ BA.convert $ doMac a :: BA.Bytes
-        unless (sig `BA.constEq` mac) Nothing
+    checkMac :: HashAlgorithm a => a -> ByteString -> ByteString -> Int -> Maybe ()
+    checkMac a tag iv l = do
+        let mac = BA.take l $ BA.convert $ doMac a iv :: BA.Bytes
+        unless (tag `BA.constEq` mac) Nothing
 
-    doMac :: HashAlgorithm a => a -> HMAC a
-    doMac _ = hmac cbcMacKey $ B.concat [aad, iv, ct, Serialize.encode al]
+    doMac :: HashAlgorithm a => a -> ByteString -> HMAC a
+    doMac _ iv = hmac cbcMacKey $ B.concat [aad, iv, ct, Serialize.encode al]
 
 -- | Encrypt a message using AES.
 encryptPayload :: Enc                   -- ^ Encryption algorithm
diff --git a/Jose/Internal/Parser.hs b/Jose/Internal/Parser.hs
new file mode 100644
--- /dev/null
+++ b/Jose/Internal/Parser.hs
@@ -0,0 +1,142 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# OPTIONS_HADDOCK prune #-}
+
+-- | Parses encoded JWTs into data structures which can be handled
+
+module Jose.Internal.Parser
+    ( parseJwt
+    , DecodableJwt (..)
+    , EncryptedCEK (..)
+    , Payload (..)
+    , IV (..)
+    , Tag (..)
+    , AAD (..)
+    , Sig (..)
+    , SigTarget (..)
+    )
+where
+
+import           Control.Applicative
+import Data.Aeson (eitherDecodeStrict')
+import           Data.Attoparsec.ByteString (Parser)
+import qualified Data.Attoparsec.ByteString as P
+import qualified Data.Attoparsec.ByteString.Char8 as PC
+import           Data.ByteArray.Encoding (convertFromBase, Base(..))
+import           Data.ByteString (ByteString)
+import qualified Data.ByteString as B
+import           Data.Either.Combinators (mapLeft)
+
+import           Jose.Jwa
+import           Jose.Types (JwtError(..), JwtHeader(..), JwsHeader(..), JweHeader(..))
+
+
+data DecodableJwt
+     = Unsecured ByteString
+     | DecodableJws JwsHeader Payload Sig SigTarget
+     | DecodableJwe JweHeader EncryptedCEK IV Payload Tag AAD
+
+
+data Tag
+    = Tag16 ByteString
+    | Tag24 ByteString
+    | Tag32 ByteString
+
+
+data IV
+    = IV12 ByteString
+    | IV16 ByteString
+
+
+newtype Sig = Sig ByteString
+newtype SigTarget = SigTarget ByteString
+newtype AAD = AAD ByteString
+newtype Payload = Payload ByteString
+newtype EncryptedCEK = EncryptedCEK ByteString
+
+
+parseJwt :: ByteString -> Either JwtError DecodableJwt
+parseJwt bs = mapLeft (const BadCrypto) $ P.parseOnly jwt bs
+
+
+jwt :: Parser DecodableJwt
+jwt = do
+    (hdr, raw) <- jwtHeader
+    case hdr of
+        UnsecuredH -> Unsecured <$> base64Chunk
+        JwsH h -> do
+            payloadB64 <- PC.takeWhile ('.' /=) <* PC.char '.'
+            payload <- b64Decode payloadB64
+            s <- sig (jwsAlg h)
+            pure $ DecodableJws h (Payload payload) s (SigTarget (B.concat [raw, ".", payloadB64]))
+        JweH h ->
+            DecodableJwe
+                <$> pure h
+                <*> encryptedCEK
+                <*> iv (jweEnc h)
+                <*> encryptedPayload
+                <*> authTag (jweEnc h)
+                <*> pure (AAD raw)
+
+
+sig :: JwsAlg -> Parser Sig
+sig _ = do
+    t <- P.takeByteString >>= b64Decode
+    pure (Sig t)
+
+
+authTag :: Enc -> Parser Tag
+authTag e = do
+    t <- P.takeByteString >>= b64Decode
+    case e of
+        A128GCM -> tag16 t
+        A192GCM -> tag16 t
+        A256GCM -> tag16 t
+        A128CBC_HS256 -> tag16 t
+        A192CBC_HS384 -> tag24 t
+        A256CBC_HS512 -> tag32 t
+  where
+    badTag = "invalid auth tag"
+    tag16 t = if B.length t /= 16 then fail badTag else pure (Tag16 t)
+    tag24 t = if B.length t /= 24 then fail badTag else pure (Tag24 t)
+    tag32 t = if B.length t /= 32 then fail badTag else pure (Tag32 t)
+
+
+iv :: Enc -> Parser IV
+iv e = do
+    bs <- base64Chunk
+    case e of
+        A128GCM -> iv12 bs
+        A192GCM -> iv12 bs
+        A256GCM -> iv12 bs
+        _ -> iv16 bs
+  where
+    iv12 bs = if B.length bs /= 12 then fail "invalid iv" else pure (IV12 bs)
+    iv16 bs = if B.length bs /= 16 then fail "invalid iv" else pure (IV16 bs)
+
+
+encryptedCEK :: Parser EncryptedCEK
+encryptedCEK = EncryptedCEK <$> base64Chunk
+
+
+encryptedPayload :: Parser Payload
+encryptedPayload = Payload <$> base64Chunk
+
+
+jwtHeader :: P.Parser (JwtHeader, ByteString)
+jwtHeader = do
+    hdrB64 <- PC.takeWhile ('.' /=) <* PC.char '.'
+    hdrBytes <- b64Decode hdrB64 :: P.Parser ByteString
+    hdr <- parseHdr hdrBytes
+    return (hdr, hdrB64)
+  where
+    parseHdr bs = either fail return (eitherDecodeStrict' bs)
+
+
+base64Chunk :: P.Parser ByteString
+base64Chunk = do
+    bs <- PC.takeWhile ('.' /=) <* PC.char '.'
+    b64Decode bs
+
+
+b64Decode :: ByteString -> P.Parser ByteString
+b64Decode bs = either (fail "Invalid Base64") return $ convertFromBase Base64URLUnpadded bs
diff --git a/Jose/Jwe.hs b/Jose/Jwe.hs
--- a/Jose/Jwe.hs
+++ b/Jose/Jwe.hs
@@ -20,7 +20,6 @@
     )
 where
 
-import Control.Monad (unless)
 import Control.Monad.Trans (lift)
 import Control.Monad.Trans.Either
 import Crypto.Cipher.Types (AuthTag(..))
@@ -29,12 +28,12 @@
 import qualified Data.ByteArray as BA
 import Data.ByteString (ByteString)
 import qualified Data.ByteString as B
-import qualified Data.ByteString.Char8 as BC
 import Jose.Types
 import qualified Jose.Internal.Base64 as B64
 import Jose.Internal.Crypto
 import Jose.Jwa
 import Jose.Jwk
+import qualified Jose.Internal.Parser as P
 
 -- | Create a JWE using a JWK.
 -- The key and algorithms must be consistent or an error
@@ -57,6 +56,7 @@
         Claims c       -> (Nothing, c)
         Nested (Jwt b) -> (Just "JWT", b)
 
+
 -- | Try to decode a JWE using a JWK.
 -- If the key type does not match the content encoding algorithm,
 -- an error will be returned.
@@ -72,36 +72,26 @@
     SymmetricJwk kb   _ _ _ -> fmap Jwe (doDecode (keyUnwrap kb) jwt)
     _                       -> left $ KeyError "JWK cannot decode a JWE"
 
+
 doDecode :: MonadRandom m
     => (JweAlg -> ByteString -> Either JwtError ByteString)
     -> ByteString
     -> EitherT JwtError m Jwe
 doDecode decodeCek jwt = do
-    checkDots
-    let components = BC.split '.' jwt
-    let aad = head components
-    [h, ek, providedIv, payload, sig] <- mapM B64.decode components
-    hdr <- case parseHeader h of
-        Right (JweH jweHdr) -> return jweHdr
-        Right (JwsH _)      -> left (BadHeader "Header is for a JWS")
-        Right UnsecuredH    -> left (BadHeader "Header is for an unsecured JWT")
-        Left e              -> left e
-    let alg = jweAlg hdr
-        enc = jweEnc hdr
-    (dummyCek, dummyIv) <- lift $ generateCmkAndIV enc
-    let decryptedCek = either (const dummyCek) id $ decodeCek alg ek
-        cek = if B.length decryptedCek == B.length dummyCek
-                 then decryptedCek
-                 else dummyCek
-        iv  = if B.length providedIv == B.length dummyIv
-                 then providedIv
-                 else dummyIv
-        authTag = AuthTag $ BA.convert sig
-    claims <- maybe (left BadCrypto) return $ decryptPayload enc cek iv aad authTag payload
-    return (hdr, claims)
+    encodedJwt <- hoistEither (P.parseJwt jwt)
+    case encodedJwt of
+        P.DecodableJwe hdr (P.EncryptedCEK ek) iv (P.Payload payload) tag (P.AAD aad) -> do
+            let alg = jweAlg hdr
+                enc = jweEnc hdr
+            (dummyCek, _) <- lift $ generateCmkAndIV enc
+            let decryptedCek = either (const dummyCek) id $ decodeCek alg ek
+                cek = if B.length decryptedCek == B.length dummyCek
+                        then decryptedCek
+                        else dummyCek
+            claims <- maybe (left BadCrypto) return $ decryptPayload enc cek iv aad tag payload
+            return (hdr, claims)
 
-  where
-    checkDots = unless (BC.count '.' jwt == 4) $ left (BadDots 4)
+        _ -> left (BadHeader "Content is not a JWE")
 
 
 doEncode :: MonadRandom m
diff --git a/Jose/Jws.hs b/Jose/Jws.hs
--- a/Jose/Jws.hs
+++ b/Jose/Jws.hs
@@ -25,17 +25,16 @@
 where
 
 import Control.Applicative
-import Control.Monad (unless)
 import qualified Crypto.PubKey.ECC.ECDSA as ECDSA
 import Crypto.PubKey.RSA (PrivateKey(..), PublicKey(..), generateBlinder)
 import Crypto.Random (MonadRandom)
 import Data.ByteString (ByteString)
 import qualified Data.ByteString as B
-import qualified Data.ByteString.Char8 as BC
 
 import Jose.Types
 import qualified Jose.Internal.Base64 as B64
 import Jose.Internal.Crypto
+import qualified Jose.Internal.Parser as P
 import Jose.Jwa
 import Jose.Jwk (Jwk (..))
 
@@ -63,7 +62,7 @@
                    -> ByteString
                    -> ByteString
                    -> Either JwtError Jwt
-hmacEncodeInternal a key st = Jwt <$> (\mac -> B.concat [st, ".", B64.encode mac]) <$> hmacSign a key st
+hmacEncodeInternal a key st = Jwt . (\mac -> B.concat [st, ".", B64.encode mac]) <$> hmacSign a key st
 
 -- | Decodes and validates an HMAC signed JWS.
 hmacDecode :: ByteString          -- ^ The HMAC key
@@ -115,20 +114,13 @@
 
 type JwsVerifier = JwsAlg -> ByteString -> ByteString -> Bool
 
+
 decode :: JwsVerifier -> ByteString -> Either JwtError Jws
 decode verify jwt = do
-    unless (BC.count '.' jwt == 2) $ Left $ BadDots 2
-    let (hdrPayload, sig) = spanEndDot jwt
-    sigBytes <- B64.decode sig
-    [h, payload] <- mapM B64.decode $ BC.split '.' hdrPayload
-    hdr <- case parseHeader h of
-        Right (JwsH jwsHdr) -> return jwsHdr
-        Right (JweH _)      -> Left (BadHeader "Header is for a JWE")
-        Right UnsecuredH    -> Left (BadHeader "Header is for an unsecured JWT")
-        Left e              -> Left e
-    if verify (jwsAlg hdr) hdrPayload sigBytes
-      then Right (hdr, payload)
-      else Left BadSignature
-  where
-    spanEndDot bs = let (toDot, end) = BC.spanEnd (/= '.') bs
-                    in  (B.init toDot, end)
+    decodableJwt <- P.parseJwt jwt
+    case decodableJwt of
+        P.DecodableJws hdr (P.Payload p) (P.Sig sig) (P.SigTarget signed) ->
+          if verify (jwsAlg hdr) signed sig
+              then Right (hdr, p)
+              else Left BadSignature
+        _ -> Left (BadHeader "JWT is not a JWS")
diff --git a/Jose/Jwt.hs b/Jose/Jwt.hs
--- a/Jose/Jwt.hs
+++ b/Jose/Jwt.hs
@@ -25,7 +25,7 @@
     )
 where
 
-import Control.Monad (msum, when, unless, liftM)
+import Control.Monad (msum, when, unless)
 import Control.Monad.Trans (lift)
 import Control.Monad.Trans.Either
 import qualified Crypto.PubKey.ECC.ECDSA as ECDSA
@@ -37,6 +37,7 @@
 import qualified Data.ByteString.Char8 as BC
 
 import qualified Jose.Internal.Base64 as B64
+import qualified Jose.Internal.Parser as P
 import Jose.Types
 import Jose.Jwk
 import Jose.Jwa
@@ -85,21 +86,20 @@
     -> ByteString                      -- ^ The encoded JWT
     -> m (Either JwtError JwtContent)  -- ^ The decoded JWT payload, if successful
 decode keySet encoding jwt = runEitherT $ do
-    let components = BC.split '.' jwt
-    when (length components < 3) $ left $ BadDots 2
-    hdr <- B64.decode (head components) >>= hoistEither . parseHeader
-    ks  <- findDecodingKeys hdr keySet
-    -- Now we have one or more suitable keys (or none for the unsecured case).
-    -- Try each in turn until successful
-    decodings <- case hdr of
-        UnsecuredH -> do
-            unless (encoding == Just (JwsEncoding None)) $ left (BadAlgorithm "JWT is unsecured but expected 'alg' was not 'none'")
-            B64.decode (components !! 1) >>= \p -> return [Just (Unsecured p)]
-        JwsH h     -> do
-            unless (isNothing encoding || encoding == Just (JwsEncoding (jwsAlg h))) $ left (BadAlgorithm "Expected 'alg' doesn't match JWS header")
+    decodableJwt <- hoistEither (P.parseJwt jwt)
+
+    decodings <- case (decodableJwt, encoding) of
+        (P.Unsecured p, Just (JwsEncoding None)) -> return [Just (Unsecured p)]
+        (P.Unsecured _, _) -> left (BadAlgorithm "JWT is unsecured but expected 'alg' was not 'none'")
+        (P.DecodableJws hdr _ _ _, e) -> do
+            unless (isNothing e || e == Just (JwsEncoding (jwsAlg hdr))) $
+                left (BadAlgorithm "Expected 'alg' doesn't match JWS header")
+            ks <- checkKeys $ filter (canDecodeJws hdr) keySet
             mapM decodeWithJws ks
-        JweH h     -> do
-            unless (isNothing encoding || encoding == Just (JweEncoding (jweAlg h) (jweEnc h))) $ left (BadAlgorithm "Expected encoding doesn't match JWE header")
+        (P.DecodableJwe hdr _ _ _ _ _, e) -> do
+            unless (isNothing e || e == Just (JweEncoding (jweAlg hdr) (jweEnc hdr))) $
+                left (BadAlgorithm "Expected encoding doesn't match JWE header")
+            ks <- checkKeys $ filter (canDecodeJwe hdr) keySet
             mapM decodeWithJwe ks
     case msum decodings of
         Nothing  -> left $ KeyError "None of the keys was able to decode the JWT"
@@ -114,8 +114,12 @@
         SymmetricJwk  kb   _ _ _ -> Jws.hmacDecode kb jwt
 
     decodeWithJwe :: MonadRandom m => Jwk -> EitherT JwtError m (Maybe JwtContent)
-    decodeWithJwe k = liftM (either (const Nothing) Just) (lift (Jwe.jwkDecode k jwt))
+    decodeWithJwe k = fmap (either (const Nothing) Just) (lift (Jwe.jwkDecode k jwt))
 
+    checkKeys [] = left $ KeyError "No suitable key was found to decode the JWT"
+    checkKeys ks = return ks
+
+
 -- | Convenience function to return the claims contained in a JWT.
 -- This is required in situations such as client assertion authentication,
 -- where the contents of the JWT may be required in order to work out
@@ -132,14 +136,3 @@
     return (hdr, claims)
   where
     parseClaims bs = maybe (Left BadClaims) Right $ decodeStrict' bs
-
-
-findDecodingKeys :: Monad m => JwtHeader -> [Jwk] -> EitherT JwtError m [Jwk]
-findDecodingKeys hdr jwks = case hdr of
-    JweH h -> checkKeys $ filter (canDecodeJwe h) jwks
-    JwsH h -> checkKeys $ filter (canDecodeJws h) jwks
-    UnsecuredH -> return []
-  where
-    -- TODO Move checks to JWK and support better error messages
-    checkKeys [] = left $ KeyError "No suitable key was found to decode the JWT"
-    checkKeys ks = return ks
diff --git a/benchmarks/bench.hs b/benchmarks/bench.hs
--- a/benchmarks/bench.hs
+++ b/benchmarks/bench.hs
@@ -1,36 +1,66 @@
-{-# LANGUAGE OverloadedStrings, CPP #-}
+{-# LANGUAGE OverloadedStrings #-}
 module Main where
 
 import Criterion.Main
 import Crypto.Random
 import Data.Word (Word64)
 import Jose.Jws
+import qualified Jose.Jwe as Jwe
 import Jose.Jwa
 import Jose.Jwt
+import Jose.Jwk
 import Keys
 
 benchRNG = drgNewTest (w, w, w, w, w) where w = 1 :: Word64
 
 fstWithRNG = fst . withDRG benchRNG
 
+msg = "The best laid schemes o' mice and men"
+
 main = do
-    let msg = "The best laid schemes o' mice and men"
-        rsaE a m  = case fstWithRNG (rsaEncode a jwsRsaPrivateKey m) of
-            Left  _       -> error "RSA encode shouldn't fail"
-            Right (Jwt j) -> j
-        hmacE a m = case hmacEncode a jwsHmacKey m of
-            Left _        -> error "HMAC shouldn't fail"
-            Right (Jwt j) -> j
+    kwKek <- getRandomBytes 32 >>= \k -> return $ SymmetricJwk k Nothing Nothing Nothing :: IO Jwk
+    Right rsaOAEPJwe <- Jwe.rsaEncode RSA_OAEP A256GCM jwsRsaPublicKey msg
+    Right keywrapJwe <- Jwe.jwkEncode A256KW A256GCM kwKek (Claims msg)
 
     defaultMain
-      [ bgroup "JWS"
-          [ bench "encode RSA256" $ nf (rsaE RS256)  msg
-#if MIN_VERSION_cryptonite(0,13,0)
-          , bench "encode RSA384" $ nf (rsaE RS384)  msg
-#endif
-          , bench "encode RSA512" $ nf (rsaE RS512)  msg
-          , bench "encode HS256"  $ nf (hmacE HS256) msg
-          , bench "encode HS384"  $ nf (hmacE HS384) msg
-          , bench "encode HS512"  $ nf (hmacE HS512) msg
-          ]
+      [ benchJwsHmac
+      , benchJwsRsa
+      , benchJweKeywrap (unJwt keywrapJwe) kwKek
+      , benchJweRsa (unJwt rsaOAEPJwe)
       ]
+
+benchJweRsa jwe = bgroup "JWE-RSA"
+    [ bench "decode RSA_OAEP" $ nf rsaDecrypt jwe
+    ]
+  where
+     rsaDecrypt m = case fstWithRNG (Jwe.rsaDecode jwsRsaPrivateKey m) of
+        Left _ -> error "RSA decode of JWE shouldn't fail"
+        Right j -> snd j
+
+benchJweKeywrap jwe jwk = bgroup "JWE-KW"
+    [ bench "decode A256KW" $ nf keywrapDecode jwe
+    ]
+  where
+     keywrapDecode m = case fstWithRNG (Jwe.jwkDecode jwk m) of
+        Right (Jwe j) -> snd j
+        _ -> error "RSA decode of JWE shouldn't fail"
+
+benchJwsRsa = bgroup "JWS-RSA"
+    [ bench "encode RSA256" $ nf (rsaE RS256)  msg
+    , bench "encode RSA384" $ nf (rsaE RS384)  msg
+    , bench "encode RSA512" $ nf (rsaE RS512)  msg
+    ]
+  where
+    rsaE a m  = case fstWithRNG (rsaEncode a jwsRsaPrivateKey m) of
+        Left  _       -> error "RSA encode shouldn't fail"
+        Right (Jwt j) -> j
+
+benchJwsHmac = bgroup "JWS-HMAC"
+    [ bench "encode HS256"  $ nf (hmacE HS256) msg
+    , bench "encode HS384"  $ nf (hmacE HS384) msg
+    , bench "encode HS512"  $ nf (hmacE HS512) msg
+    ]
+  where
+    hmacE a m = case hmacEncode a jwsHmacKey m of
+        Left _        -> error "HMAC shouldn't fail"
+        Right (Jwt j) -> j
diff --git a/jose-jwt.cabal b/jose-jwt.cabal
--- a/jose-jwt.cabal
+++ b/jose-jwt.cabal
@@ -1,5 +1,5 @@
 Name:               jose-jwt
-Version:            0.7.4
+Version:            0.7.5
 Synopsis:           JSON Object Signing and Encryption Library
 Homepage:           http://github.com/tekul/jose-jwt
 Bug-Reports:        http://github.com/tekul/jose-jwt/issues
@@ -42,9 +42,11 @@
                     , Jose.Jwk
                     , Jose.Internal.Base64
                     , Jose.Internal.Crypto
+                    , Jose.Internal.Parser
   Other-Modules:      Jose.Types
   Build-Depends:      base >= 4.6 && < 5
                     , aeson >= 0.8.0.2
+                    , attoparsec >= 0.12.0.0
                     , bytestring >= 0.9
                     , cereal >= 0.4
                     , containers >= 0.4
diff --git a/tests/Tests/JweSpec.hs b/tests/Tests/JweSpec.hs
--- a/tests/Tests/JweSpec.hs
+++ b/tests/Tests/JweSpec.hs
@@ -25,6 +25,7 @@
 import Jose.Jwa
 import qualified Jose.Jwk as Jwk
 import Jose.Internal.Crypto
+import Jose.Internal.Parser(Tag(..), IV(..))
 import qualified Jose.Internal.Base64 as B64
 
 --------------------------------------------------------------------------------
@@ -77,6 +78,13 @@
             Right ct = B64.decode payload
         withBlinder (Jwe.rsaDecode a1PrivKey (B.intercalate "." [hdr, ek, iv, B64.encode (B.tail ct), tag])) @?= Left BadCrypto
 
+      it "a truncated auth tag returns BadCrypto" $ do
+        let [hdr, ek, iv, payload, tag] = BC.split '.' a1
+            Right tagBytes = B64.decode tag
+            badTag = B64.encode $ BC.take 2 tagBytes
+        withBlinder (Jwe.rsaDecode a1PrivKey (B.intercalate "." [hdr, ek, iv, payload, badTag])) @?= Left BadCrypto
+
+
       it "a truncated IV returns BadCrypto" $ do
         let (fore, aft) = BC.breakSubstring (B64.encode a1iv) a1
             newIv = B64.encode (B.tail a1iv)
@@ -91,14 +99,14 @@
         withDRG (RNG a2seed) (rsaEncrypt a2PubKey RSA1_5 a2cek) @?= (Right a2jweKey, RNG "")
 
       it "encrypts the payload to the expected ciphertext and authentication tag" $
-        encryptPayload A128CBC_HS256 a2cek a2iv aad a2Payload @?= Just (a2Tag, a2Ciphertext)
+        encryptPayload A128CBC_HS256 a2cek a2iv aad a2Payload @?= Just (AuthTag (BA.convert a2Tag), a2Ciphertext)
 
       it "encodes the payload to the expected JWT" $
         withDRG (RNG $ B.concat [a2cek, a2iv, a2seed])
             (Jwe.rsaEncode RSA1_5 A128CBC_HS256 a2PubKey a2Payload) @?= (Right (Jwt a2), RNG "")
 
       it "decrypts the ciphertext to the correct payload" $
-        decryptPayload A128CBC_HS256 a2cek a2iv aad a2Tag a2Ciphertext @?= Just a2Payload
+        decryptPayload A128CBC_HS256 a2cek (IV16 a2iv) aad (Tag16 a2Tag) a2Ciphertext @?= Just a2Payload
 
       it "decodes the JWT to the expected header and payload" $
         withBlinder (Jwe.rsaDecode a2PrivKey a2) @?= Right (a2Header, a2Payload)
@@ -156,7 +164,7 @@
 
 -- A decidedly non-random, random number generator which allows specific
 -- sequences of bytes to be supplied which match the JWE test data.
-data RNG = RNG B.ByteString deriving (Eq, Show)
+newtype RNG = RNG B.ByteString deriving (Eq, Show)
 
 genBytes :: BA.ByteArray ba => Int -> RNG -> (ba, RNG)
 genBytes 0 g = (BA.empty, g)
@@ -221,7 +229,7 @@
 
 a2Ciphertext = B.pack [40, 57, 83, 181, 119, 33, 133, 148, 198, 185, 243, 24, 152, 230, 6, 75, 129, 223, 127, 19, 210, 82, 183, 230, 168, 33, 215, 104, 143, 112, 56, 102]
 
-a2Tag = AuthTag $ BA.pack [246, 17, 244, 190, 4, 95, 98, 3, 231, 0, 115, 157, 242, 203, 100, 191]
+a2Tag = B.pack [246, 17, 244, 190, 4, 95, 98, 3, 231, 0, 115, 157, 242, 203, 100, 191]
 
 Right a2jweKey = B64.decode $ BC.pack "UGhIOguC7IuEvf_NPVaXsGMoLOmwvc1GyqlIKOK1nN94nHPoltGRhWhw7Zx0-kFm1NJn8LE9XShH59_i8J0PH5ZZyNfGy2xGdULU7sHNF6Gp2vPLgNZ__deLKxGHZ7PcHALUzoOegEI-8E66jX2E4zyJKx-YxzZIItRzC5hlRirb6Y5Cl_p-ko3YvkkysZIFNPccxRU7qve1WYPxqbb2Yw8kZqa2rMWI5ng8OtvzlV7elprCbuPhcCdZ6XDP0_F8rkXds2vE4X-ncOIM8hAYHHi29NX0mcKiRaD0-D-ljQTP-cFPgwCp6X-nZZd9OHBv-B3oWh2TbqmScqXMR4gp_A"
 
