diff --git a/Web/ClientSession.hs b/Web/ClientSession.hs
--- a/Web/ClientSession.hs
+++ b/Web/ClientSession.hs
@@ -1,5 +1,6 @@
-{-# OPTIONS_GHC -fno-warn-orphans #-}
-{-# LANGUAGE TypeSynonymInstances #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
 ---------------------------------------------------------
 --
 -- Module        : Web.ClientSession
@@ -21,37 +22,28 @@
       -- * Actual encryption/decryption
     , encrypt
     , decrypt
-      -- * Classes
-    , IsByteString (..)
       -- * Key types and classes.
     , Word256
     , AESKey
+      -- * Exceptions
+    , ClientSessionException
     ) where
 
 import Codec.Encryption.AES (AESKey)
 import qualified Data.ByteString as BS
-import qualified Data.ByteString.UTF8 as BSU
+import Control.Failure
+import Control.Monad (unless)
 
 import Data.LargeWord (Word256)
 import Codec.Utils (listFromOctets, listToOctets)
 import Data.Word (Word8)
 import System.Random (getStdGen, randoms, Random, randomR, random)
-import qualified Data.ByteString as BS
 import qualified Codec.Encryption.AES as AES
 import qualified Codec.Binary.Base64Url as Base64
 import qualified Data.Digest.MD5 as MD5
 
--- | A class for anything which can become a 'Data.ByteString'.
--- The 'String' instance uses UTF8 encoding.
-class IsByteString a where
-    toByteString :: a -> BS.ByteString
-    fromByteString :: BS.ByteString -> a
-instance IsByteString BS.ByteString where
-    toByteString = id
-    fromByteString = id
-instance IsByteString String where
-    toByteString = BSU.fromString
-    fromByteString = BSU.toString
+import Data.Typeable (Typeable)
+import Control.Exception (Exception)
 
 -- | The default key file.
 defaultKeyFile :: String
@@ -61,6 +53,15 @@
 getDefaultKey :: IO Word256
 getDefaultKey = getKey defaultKeyFile
 
+data ClientSessionException =
+      KeyTooSmall BS.ByteString
+    | InvalidBase64 String
+    | MismatchedHash { _expected :: [Word8]
+                     , _actual :: [Word8]
+                     }
+    deriving (Show, Typeable)
+instance Exception ClientSessionException
+
 -- | Get a 256-bit key from the given text file.
 -- If the file does not exist, or did not contain enough bits,
 -- a random key will be generated and stored in that file.
@@ -71,52 +72,51 @@
         loadKeyFromFile = do
                 contents <- BS.readFile keyFile
                 if BS.length contents < 32
-                        then fail "Key too small"
+                        then failure $ KeyTooSmall contents
                         else return $ head $ listFromOctets $ BS.unpack contents
         generateNewKey :: IO Word256
         generateNewKey = do
                 stdGen <- getStdGen
-                let word8s = take 32 $ randoms stdGen
+                let word8s = map unMyWord8 $ take 32 $ randoms stdGen
                 let newKey = head $ listFromOctets word8s
                 BS.writeFile keyFile $ BS.pack word8s
                 return newKey
 
-instance Random Word8 where
+newtype MyWord8 = MyWord8 { unMyWord8 :: Word8 }
+    deriving (Integral, Real, Enum, Num, Ord, Eq, Show)
+instance Random MyWord8 where
         randomR (a,b) g =
                 let (x, g') = randomR (toInteger a, toInteger b) g
-                in (toEnum $ fromEnum $ mod x 256, g')
-        random = randomR (minBound,maxBound)
+                in (fromIntegral $ mod x 256, g')
+        random = randomR (MyWord8 minBound, MyWord8 maxBound)
 
 -- | Encrypt with the given key and base-64 encode.
 -- A hash is stored inside the encrypted key so that, upon decryption,
 -- integrity can be guaranteed.
-encrypt :: (IsByteString b, AES.AESKey k)
+encrypt :: AES.AESKey k
         => k               -- ^ The key used for encryption.
-        -> b               -- ^ The data to encrypt.
+        -> BS.ByteString   -- ^ The data to encrypt.
         -> String          -- ^ Encrypted and encoded data.
 encrypt k x =
-        let unpacked = BS.unpack $ toByteString x
+        let unpacked = BS.unpack x
         in Base64.encode . listToOctets . map (AES.encrypt k) .
            listFromOctets $ MD5.hash unpacked ++ unpacked
 
--- | Helper function to convert a Maybe into any monad
-liftMaybe :: Monad m => Maybe a -> m a
-liftMaybe Nothing = fail "Nothing"
-liftMaybe (Just x) = return x
-
--- | Base-64 decode and decrypt with the given key, if possible.
--- If either the original string is not a valid base-64 encoded string,
--- or the hash at the beginning of the decrypted string does not match,
--- this function 'fail's.
-decrypt :: (AES.AESKey k, Monad m, IsByteString b)
+-- | Base-64 decode and decrypt with the given key, if possible.  Calls
+-- 'failure' if either the original string is not a valid base-64 encoded
+-- string, or the hash at the beginning of the decrypted string does not match.
+decrypt :: (AES.AESKey k, MonadFailure ClientSessionException m)
         => k                    -- ^ The key used for encryption.
         -> String               -- ^ Data to decrypt.
-        -> m b                  -- ^ The decrypted data, if possible.
+        -> m BS.ByteString      -- ^ The decrypted data, if possible.
 decrypt k x = do
-        decoded <- liftMaybe $ Base64.decode x
+        decoded <- case Base64.decode x of
+                        Nothing -> failure $ InvalidBase64 x
+                        Just y -> return y
         let decrypted = listToOctets $ map (AES.decrypt k)
                         $ listFromOctets decoded
-        let (hash, rest) = splitAt 16 decrypted
-        if hash == MD5.hash rest
-                then return $ fromByteString $ BS.pack rest
-                else fail "Invalid"
+        let (expected, rest) = splitAt 16 decrypted
+        let actual = MD5.hash rest
+        unless (expected == actual) $ failure
+                                    $ MismatchedHash expected actual
+        return $ BS.pack rest
diff --git a/clientsession.cabal b/clientsession.cabal
--- a/clientsession.cabal
+++ b/clientsession.cabal
@@ -1,5 +1,5 @@
 name:            clientsession
-version:         0.0.1
+version:         0.2.0
 license:         BSD3
 license-file:    LICENSE
 author:          Michael Snoyman <michael@snoyman.com>
@@ -15,10 +15,10 @@
 
 library
     build-depends:   base >=4 && <5,
-                     Crypto,
-                     dataenc,
-                     bytestring,
-                     random,
-                     utf8-string
+                     Crypto >= 4.2.0 && < 4.3,
+                     dataenc >= 0.13.0.2 && < 0.14,
+                     random >= 1.0.0.2 && < 1.1,
+                     failure >= 0.0.0.2 && < 0.1,
+                     bytestring >= 0.9 && < 0.10
     exposed-modules: Web.ClientSession
     ghc-options:     -Wall
