diff --git a/bench.hs b/bench.hs
new file mode 100644
--- /dev/null
+++ b/bench.hs
@@ -0,0 +1,19 @@
+import qualified Data.ByteString as B
+import Web.ClientSession
+import Data.Maybe
+import Data.Serialize
+
+import Criterion.Main
+import Text.Printf
+
+Right key = initKey (B.replicate 96 0xFE)
+Just iv = mkIV (B.replicate 16 0xB0)
+
+main :: IO ()
+main =
+  defaultMain
+    [ bgroup "encrypt then decrypt"
+        [ bench (printf "Message length = %d bytes" len) $
+          whnf (fromJust . decrypt key . encrypt key iv) (B.replicate len 0xAA)
+        | len <- [0, 50, 100, 400, 2000, 80000]]
+    ]
diff --git a/clientsession.cabal b/clientsession.cabal
--- a/clientsession.cabal
+++ b/clientsession.cabal
@@ -1,5 +1,5 @@
 name:            clientsession
-version:         0.7.3
+version:         0.7.3.1
 license:         BSD3
 license-file:    LICENSE
 author:          Michael Snoyman <michael@snoyman.com>, Felipe Lessa <felipe.lessa@gmail.com>
@@ -13,6 +13,8 @@
 cabal-version:   >= 1.8
 build-type:      Simple
 homepage:        http://github.com/snoyberg/clientsession/tree/master
+data-files:      bench.hs
+extra-source-files: tests/runtests.hs
 
 flag test
   description: Build the executable to run unit tests
diff --git a/src/Web/ClientSession.hs b/src/Web/ClientSession.hs
--- a/src/Web/ClientSession.hs
+++ b/src/Web/ClientSession.hs
@@ -20,12 +20,12 @@
 -- you to store sensitive information on the client side without
 -- worrying about eavesdropping.
 --
--- * Sign the encrypted cookie data using Skein-MAC-512-256.
--- Besides detecting potential errors in storage or transmission
--- of the cookies (integrity), the MAC also avoids malicious
--- modifications of the cookie data by assuring you that the
--- cookie data really was generated by this server
--- (authenticity).
+-- * Authenticate the encrypted cookie data using
+-- Skein-MAC-512-256.  Besides detecting potential errors in
+-- storage or transmission of the cookies (integrity), the MAC
+-- also avoids malicious modifications of the cookie data by
+-- assuring you that the cookie data really was generated by this
+-- server (authenticity).
 --
 -- * Encode everything using Base64.  Thus we avoid problems with
 -- non-printable characters by giving the browser a simple
@@ -54,6 +54,8 @@
 
 -- from base
 import Control.Monad (guard)
+import Data.Bits ((.|.), xor)
+import Data.List (foldl')
 
 -- from directory
 import System.Directory (doesFileExist)
@@ -172,9 +174,9 @@
     iv <- randomIV
     return $ encrypt key iv x
 
--- | Encrypt (AES-CTR), sign (Skein-MAC-512-256) and encode
--- (Base64) the given cookie data.  The returned byte string is
--- ready to be used in a response header.
+-- | Encrypt (AES-CTR), authenticate (Skein-MAC-512-256) and
+-- encode (Base64) the given cookie data.  The returned byte
+-- string is ready to be used in a response header.
 encrypt :: Key          -- ^ Key of the server.
         -> IV           -- ^ New, random initialization vector (see 'randomIV').
         -> S.ByteString -- ^ Serialized cookie data.
@@ -199,8 +201,15 @@
     guard (S.length dataBS >= 48) -- 16 bytes of IV + 32 bytes of Skein-MAC-512-256
     let (auth, toBeAuthed) = S.splitAt 32 dataBS
         auth' = macKey key toBeAuthed
-    guard (encode auth' == auth)
+    guard (encode auth' `compareHash` auth)
     let (iv_e, encrypted) = S.splitAt 16 toBeAuthed
     iv <- either (const Nothing) Just $ decode iv_e
     let (x, _) = Modes.unCtr' Modes.incIV (aesKey key) iv encrypted
     return x
+
+-- | Compare two bytestrings.  Always takes the same ammount of
+-- time, avoiding timing attacks.
+compareHash :: S.ByteString -> S.ByteString -> Bool
+compareHash s1 s2 =
+    S.length s1 == S.length s2 &&
+    foldl' (.|.) 0 (S.zipWith xor s1 s2) == 0
diff --git a/tests/runtests.hs b/tests/runtests.hs
new file mode 100644
--- /dev/null
+++ b/tests/runtests.hs
@@ -0,0 +1,75 @@
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+import Test.HUnit
+import Test.Hspec.Monadic
+import Test.QuickCheck hiding (property)
+import Test.Hspec.QuickCheck
+import Test.Hspec.HUnit ()
+import Control.Monad (replicateM)
+
+import qualified Data.ByteString as S
+import qualified Data.ByteString.Char8 as S8
+
+import Web.ClientSession
+import System.IO.Unsafe
+
+main :: IO ()
+main = hspecX $ describe "client session" $ do
+    it "encrypt/decrypt success" $ property propEncDec
+    it "encrypt/decrypt failure" $ property propEncDecFailure
+    it "AES encrypt/decrypt success" $ property propAES
+    it "AES encryption changes bs" $ property propAESChanges
+    it "specific values" caseSpecific
+
+propEncDec :: S.ByteString -> Bool
+propEncDec bs = unsafePerformIO $ do
+    key <- getDefaultKey
+    s <- encryptIO key bs
+    let bs' = decrypt key s
+    return $ Just bs == bs'
+
+propEncDecFailure :: S.ByteString -> Bool
+propEncDecFailure bs = unsafePerformIO $ do
+    key <- getDefaultKey
+    s <- encryptIO key bs
+    let bs' = decrypt key $ (S.head s + 1) `S.cons` S.drop 1 s
+    return $ Just bs /= bs'
+
+propAES :: MyKey -> MyIV -> S.ByteString -> Bool
+propAES (MyKey key) (MyIV iv) bs = decrypt key (encrypt key iv bs) == Just bs
+
+propAESChanges :: MyKey -> MyIV -> S.ByteString -> Bool
+propAESChanges (MyKey key) (MyIV iv) bs = encrypt key iv bs /= bs
+
+caseSpecific :: Assertion
+caseSpecific = do
+    let s = S8.pack $ show [("lo\ENQ\143XAq","\DC2\207\226\DC1;.z56|\203\222"),("\USnu#\139\ETXB\201 ","l"),("\RS\b,zM2U\184\191F)\EOT\220S\NUL","O\\\GSd\247\246\n\EOT\SYN\182U2G"),("\219\NAK\217\CAN\252","ym\STX\188\232?\\\145"),("\239k","\vRZP\a\DC2F>"),("\FS\180P &\RS\174zSL\\?@","p\170\237vZ|\GS>\SYNk\176n\r"),("","\199D\DC3\200m)"),("6\152tVhB\246)9","\ENQdfU\SUB"),("I\ACK\181\NUL","\129\&6s\130q\US)oR1\197\FSp\US\SYN0"),("\183\200<\250","\211  \131g4\207N\155"),("\248O6k\CANK\135\234.","`\205!+&Z&9\DLE\244\214HP\SI\161"),("\"I'\ACK\149 \CAN\197","\141N\201\SO\204\\o.\128\148")]
+    key <- getDefaultKey
+    iv <- randomIV
+    Just s @=? decrypt key (encrypt key iv s)
+    let s' = S.concat $ replicate 500 s
+    Just s' @=? decrypt key (encrypt key iv s')
+
+instance Arbitrary S.ByteString where
+    arbitrary = S.pack `fmap` arbitrary
+
+newtype MyKey = MyKey Key
+
+instance Arbitrary MyKey where
+    arbitrary = do
+        ws <- replicateM 96 arbitrary
+        either error (return . MyKey) $ initKey $ S.pack ws
+
+instance Show MyKey where
+    show _ = "<Key>"
+
+newtype MyIV = MyIV IV
+
+instance Arbitrary MyIV where
+    arbitrary = do
+        ws <- replicateM 16 arbitrary
+        maybe (error "Invalid IV") (return . MyIV) $ mkIV $ S.pack ws
+
+instance Show MyIV where
+    show _ = "<Iv>"
