diff --git a/jose.cabal b/jose.cabal
--- a/jose.cabal
+++ b/jose.cabal
@@ -1,6 +1,6 @@
 cabal-version:       2.2
 name:                jose
-version:             0.8.3.1
+version:             0.8.4
 synopsis:
   Javascript Object Signing and Encryption and JSON Web Token library
 description:
@@ -79,7 +79,7 @@
 
   build-depends:
     attoparsec
-    , base64-bytestring >= 1.0 && < 1.3
+    , base64-bytestring >= 1.1 && < 1.3
     , concise >= 0.1
     , containers >= 0.5
     , cryptonite >= 0.7
diff --git a/src/Crypto/JOSE/Header.hs b/src/Crypto/JOSE/Header.hs
--- a/src/Crypto/JOSE/Header.hs
+++ b/src/Crypto/JOSE/Header.hs
@@ -67,10 +67,9 @@
 import Data.Monoid ((<>))
 import Data.Proxy (Proxy(..))
 
-import Control.Lens (Lens', Getter, to)
+import Control.Lens (Lens', Getter, review, to)
 import Data.Aeson (FromJSON(..), Object, Value, encode, object)
 import Data.Aeson.Types (Pair, Parser)
-import qualified Data.ByteString.Base64.URL.Lazy as B64UL
 import qualified Data.ByteString.Lazy as L
 import qualified Data.HashMap.Strict as M
 import qualified Data.Text as T
@@ -78,7 +77,7 @@
 import qualified Crypto.JOSE.JWA.JWS as JWA.JWS
 import Crypto.JOSE.JWK (JWK)
 import Crypto.JOSE.Types.Orphans ()
-import Crypto.JOSE.Types.Internal (unpad)
+import Crypto.JOSE.Types.Internal (base64url)
 import qualified Crypto.JOSE.Types as Types
 
 
@@ -119,13 +118,13 @@
     [] -> Nothing
     xs -> Just (object xs)
 
--- | Return the encoded protected parameters
+-- | Return the base64url-encoded protected parameters
 --
 protectedParamsEncoded
   :: (HasParams a, ProtectionIndicator p)
   => a p -> L.ByteString
 protectedParamsEncoded =
-  maybe mempty (unpad . B64UL.encode . encode) . protectedParams
+  maybe mempty (review base64url . encode) . protectedParams
 
 -- | Return unprotected params as a JSON 'Value' (always an object)
 --
diff --git a/src/Crypto/JOSE/JWS.hs b/src/Crypto/JOSE/JWS.hs
--- a/src/Crypto/JOSE/JWS.hs
+++ b/src/Crypto/JOSE/JWS.hs
@@ -1,4 +1,4 @@
--- Copyright (C) 2013, 2014, 2015, 2016  Fraser Tweedale
+-- Copyright (C) 2013, 2014, 2015, 2016, 2020  Fraser Tweedale
 --
 -- Licensed under the Apache License, Version 2.0 (the "License");
 -- you may not use this file except in compliance with the License.
@@ -73,6 +73,7 @@
   , Signature
   , header
   , signature
+  , rawProtectedHeader
 
   -- * JWS headers
   , Alg(..)
@@ -313,9 +314,9 @@
     )
 
 instance (HasParams a, ProtectionIndicator p) => ToJSON (Signature p a) where
-  toJSON (Signature _ h sig) =
+  toJSON s@(Signature _ h sig) =
     let
-      pro = case protectedParamsEncoded h of
+      pro = case rawProtectedHeader s of
         "" -> id
         bs -> ("protected" .= String (T.decodeUtf8 (view recons bs)) :)
       unp = case unprotectedParams h of
@@ -430,22 +431,32 @@
 
 signingInput
   :: (HasParams a, ProtectionIndicator p)
-  => Either T.Text (a p)
-  -> B.ByteString
+  => Signature p a
+  -> Types.Base64Octets
   -> B.ByteString
-signingInput h p = B.intercalate "."
-  [ either T.encodeUtf8 (view recons . protectedParamsEncoded) h
-  , review Types.base64url p
-  ]
+signingInput sig (Types.Base64Octets p) =
+  rawProtectedHeader sig <> "." <> review Types.base64url p
 
+-- | Return the raw base64url-encoded protected header value.
+-- If the Signature was decoded from JSON, this returns the
+-- original string value as-is.
+--
+-- Application code should never need to use this.  It is exposed
+-- for testing purposes.
+rawProtectedHeader
+  :: (HasParams a, ProtectionIndicator p)
+  => Signature p a -> B.ByteString
+rawProtectedHeader (Signature raw h _) =
+  maybe (view recons $ protectedParamsEncoded h) T.encodeUtf8 raw
+
 -- Convert JWS to compact serialization.
 --
 -- The operation is defined only when there is exactly one
 -- signature and returns Nothing otherwise
 --
 instance HasParams a => ToCompact (JWS Identity () a) where
-  toCompact (JWS (Types.Base64Octets p) (Identity (Signature raw h (Types.Base64Octets sig)))) =
-    [ view recons $ signingInput (maybe (Right h) Left raw) p
+  toCompact (JWS p (Identity s@(Signature _ _ (Types.Base64Octets sig)))) =
+    [ view recons $ signingInput s p
     , review Types.base64url sig
     ]
 
@@ -487,8 +498,14 @@
      )
   => B.ByteString -> a p -> JWK -> m (Signature p a)
 mkSignature p h k =
-  Signature Nothing h . Types.Base64Octets
-  <$> sign (view (alg . param) h) (k ^. jwkMaterial) (signingInput (Right h) p)
+  let
+    almostSig = Signature Nothing h . Types.Base64Octets
+  in
+    almostSig
+    <$> sign
+          (view (alg . param) h)
+          (k ^. jwkMaterial)
+          (signingInput (almostSig "") (Types.Base64Octets p))
 
 
 -- | Validation policy.
@@ -633,7 +650,7 @@
   -> Signature p a
   -> JWK
   -> Either Error Bool
-verifySig (Types.Base64Octets m) (Signature raw h (Types.Base64Octets s)) k =
+verifySig msg sig@(Signature _ h (Types.Base64Octets s)) k =
   verify (view (alg . param) h) (view jwkMaterial k) tbs s
   where
-  tbs = signingInput (maybe (Right h) Left raw) m
+  tbs = signingInput sig msg
diff --git a/src/Crypto/JOSE/Types/Internal.hs b/src/Crypto/JOSE/Types/Internal.hs
--- a/src/Crypto/JOSE/Types/Internal.hs
+++ b/src/Crypto/JOSE/Types/Internal.hs
@@ -28,8 +28,6 @@
   , parseB64
   , encodeB64Url
   , parseB64Url
-  , pad
-  , unpad
   , bsToInteger
   , integerToBS
   , intBytes
@@ -38,7 +36,6 @@
   ) where
 
 import Data.Bifunctor (first)
-import Data.Char (ord)
 import Data.Monoid ((<>))
 import Data.Tuple (swap)
 import Data.Word (Word8)
@@ -48,7 +45,6 @@
 import Crypto.Number.Basic (log2)
 import Data.Aeson.Types
 import qualified Data.ByteString as B
-import qualified Data.ByteString.Lazy as L
 import qualified Data.ByteString.Base64 as B64
 import qualified Data.ByteString.Base64.URL as B64U
 import qualified Data.HashMap.Strict as M
@@ -74,63 +70,7 @@
 encodeB64 :: B.ByteString -> Value
 encodeB64 = String . E.decodeUtf8 . B64.encode
 
-class IsChar a where
-  fromChar :: Char -> a
 
-instance IsChar Char where
-  fromChar = id
-
-instance IsChar Word8 where
-  fromChar = fromIntegral . ord
-
--- | Add appropriate base64 '=' padding.
---
-pad :: (Snoc s s a a, IsChar a) => s -> s
-pad = rpad 4 (fromChar '=')
-{-# INLINE [2] pad #-}
-
-rpad :: (Snoc s s a a) => Int -> a -> s -> s
-rpad w a s =
-  let n = ((w - snocLength s `mod` w) `mod` w)
-  in foldr (.) id (replicate n (`snoc` a)) s
-{-# INLINE rpad #-}
-
-snocLength :: (Snoc s s a a) => s -> Int
-snocLength s = case unsnoc s of
-  Nothing -> 0
-  Just (s', _) -> 1 + snocLength s'
-{-# INLINE snocLength #-}
-
-padB :: B.ByteString -> B.ByteString
-padB s = s <> B.replicate ((4 - B.length s `mod` 4) `mod` 4) 61
-{-# RULES "pad/padB" pad = padB #-}
-
-padL :: L.ByteString -> L.ByteString
-padL s = s <> L.replicate ((4 - L.length s `mod` 4) `mod` 4) 61
-{-# RULES "pad/padL" pad = padL #-}
-
-
--- | Strip base64 '=' padding.
---
-unpad :: (Snoc s s a a, IsChar a, Eq a) => s -> s
-unpad = rstrip (== fromChar '=')
-{-# INLINE [2] unpad #-}
-
-rstrip :: (Snoc s s a a) => (a -> Bool) -> s -> s
-rstrip p s = case unsnoc s of
-  Nothing -> s
-  Just (s', a) -> if p a then rstrip p s' else s
-{-# INLINE rstrip #-}
-
-unpadB :: B.ByteString -> B.ByteString
-unpadB = B.reverse . B.dropWhile (== 61) . B.reverse
-{-# RULES "unpad/unpadB" unpad = unpadB #-}
-
-unpadL :: L.ByteString -> L.ByteString
-unpadL = L.reverse . L.dropWhile (== 61) . L.reverse
-{-# RULES "unpad/unpadL" unpad = unpadL #-}
-
-
 -- | Prism for encoding / decoding base64url.
 --
 -- To encode, @'review' base64url@.
@@ -143,10 +83,9 @@
   , Cons s1 s1 Word8 Word8
   , Cons s2 s2 Word8 Word8
   ) => Prism' s1 s2
-base64url = reconsIso . padder . b64u . reconsIso
+base64url = reconsIso . b64u . reconsIso
   where
-    padder = iso pad unpad
-    b64u = prism B64U.encode (\s -> first (const s) (B64U.decode s))
+    b64u = prism B64U.encodeUnpadded (\s -> first (const s) (B64U.decodeUnpadded s))
     reconsIso = iso (view recons) (view recons)
 
 
diff --git a/test/JWS.hs b/test/JWS.hs
--- a/test/JWS.hs
+++ b/test/JWS.hs
@@ -50,6 +50,8 @@
   appendixA5Spec
   appendixA6Spec
   cfrgSpec
+  base64urlSpec
+  reserialiseSpec
 
 
 -- Extension of JWSHeader to test "crit" behaviour
@@ -519,3 +521,48 @@
   it "validates signatures correctly" $
     verify JWA.JWS.EdDSA (view jwkMaterial jwk) signingInput sig
       `shouldBe` (Right True :: Either Error Bool)
+
+base64urlSpec :: Spec
+base64urlSpec = describe "base64url-decoding behaviour" $
+  it "rejects baseurl-encoded values with padding" $
+    -- from https://github.com/frasertweedale/hs-jose/issues/99
+    --  and https://github.com/frasertweedale/hs-jose/issues/97
+    let
+      inJws = "{\"signature\":\"fake\",\"protected\":\"eyJraWQiOiI1ODJhMTdkZS0zYjg4LTQxZTgtOGM3MC0zZjBkNmYwMDNmY2QiLCJhbGciOiJSUzI1NiJ9\",\"payload\":\"eyJ1c2VybmFtZSI6Imp1c3BheSIsImdzdGluIjoiMjBhYWFhYTEyMzRhMWEyIn0=\"}"
+    in
+      (eitherDecode inJws :: Either String (FlattenedJWS JWSHeader))
+        `shouldSatisfy` is _Left
+
+reserialiseSpec :: Spec
+reserialiseSpec = describe "reserialisation of JWS" $
+  it "preserves raw protected header value" $ do
+    -- https://github.com/frasertweedale/hs-jose/issues/98
+    --
+    -- Test two different JWKs with same protected header fields
+    -- but different order.  For each input parse then reserialise,
+    -- then parse again and ensure that the raw protected header
+    -- string is the same for both parses.
+    --
+    -- The two strings with different orders are needed to test
+    -- that we preserve and use the recorded raw protected header
+    -- when reserialising the object.  (We don't know what order
+    -- the keys will be in the aeson object hashmap, so we test
+    -- with two different orders to avoid a false success).
+    let
+      -- {"kid":"0","alg":"RS256"}
+      s1 = "{\"protected\":\"eyJraWQiOiIwIiwiYWxnIjoiUlMyNTYifQ\",\"payload\":\"\",\"signature\":\"\"}"
+      -- {"alg":"RS256","kid":"0"}
+      s2 = "{\"signature\":\"\",\"protected\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjAifQ\",\"payload\":\"\"}"
+
+      check s = do
+        let
+          jws = eitherDecode s :: Either String (FlattenedJWS JWSHeader)
+          s' = encode <$> jws
+          jws' = s' >>= eitherDecode :: Either String (FlattenedJWS JWSHeader)
+        jws `shouldSatisfy` is _Right
+        toListOf (signatures . to rawProtectedHeader) <$> jws
+          `shouldBe`
+          toListOf (signatures . to rawProtectedHeader) <$> jws'
+
+    check s1
+    check s2
diff --git a/test/Types.hs b/test/Types.hs
--- a/test/Types.hs
+++ b/test/Types.hs
@@ -35,8 +35,8 @@
 base64OctetsSpec :: Spec
 base64OctetsSpec = describe "Base64Octets" $
   it "can be read from JSON" $ do
-    decode "[\"AxY8DCtDaGlsbGljb3RoZQ\"]" `shouldBe` Just [Base64Octets iv]
-    decode "[\"9hH0vgRfYgPnAHOd8stkvw\"]" `shouldBe` Just [Base64Octets tag]
+    eitherDecode "[\"AxY8DCtDaGlsbGljb3RoZQ\"]" `shouldBe` Right [Base64Octets iv]
+    eitherDecode "[\"9hH0vgRfYgPnAHOd8stkvw\"]" `shouldBe` Right [Base64Octets tag]
   where
     iv = BS.pack [3, 22, 60, 12, 43, 67, 104, 105, 108, 108, 105, 99, 111, 116, 104, 101]
     tag = BS.pack [246, 17, 244, 190, 4, 95, 98, 3, 231, 0, 115, 157, 242, 203, 100, 191]
