diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c)2012, Felipe Lessa
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Felipe Lessa nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README b/README
new file mode 100644
--- /dev/null
+++ b/README
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/crypto-conduit.cabal b/crypto-conduit.cabal
new file mode 100644
--- /dev/null
+++ b/crypto-conduit.cabal
@@ -0,0 +1,56 @@
+Cabal-version:       >= 1.8
+Name:                crypto-conduit
+Version:             0.1
+Synopsis:            Conduit interface for cryptographic operations (from crypto-api).
+Homepage:            https://github.com/meteficha/crypto-conduit
+License:             BSD3
+License-file:        LICENSE
+Author:              Felipe Lessa <felipe.lessa@gmail.com>
+Maintainer:          Felipe Lessa <felipe.lessa@gmail.com>
+Category:            Cryptography
+Build-type:          Simple
+
+Description:
+  This package contains everything that you need to use a
+  cryptographic package that supports the @crypto-api@ package
+  using conduits from the @conduit@ package.
+
+Extra-source-files:
+  README
+  tests/runtests.hs
+
+Source-repository head
+  Type:     git
+  Location: git://github.com/meteficha/crypto-conduit.git
+
+Library
+  Hs-Source-Dirs: src
+  Exposed-modules:
+    Crypto.Conduit
+  Build-depends:
+    base         >= 3   && < 5,
+    bytestring   >= 0.9 && < 0.10,
+    cereal       >= 0.3 && < 0.4,
+    crypto-api   >= 0.8 && < 0.9,
+    conduit      >= 0.0 && < 0.1
+  GHC-options: -Wall
+
+Test-suite runtests
+  Type: exitcode-stdio-1.0
+  Build-depends:
+    base         >= 3   && < 5,
+    bytestring   >= 0.9 && < 0.10,
+    cereal       >= 0.3 && < 0.4,
+    crypto-api   >= 0.8 && < 0.9,
+    conduit      >= 0.0 && < 0.1,
+
+    cryptocipher == 0.3.*,
+    cryptohash   == 0.7.*,
+    skein        == 0.1.*,
+    hspec        == 0.9.*,
+
+    -- finally, our own package
+    crypto-conduit
+  GHC-options: -Wall
+  Hs-source-dirs: tests
+  Main-is: runtests.hs
diff --git a/src/Crypto/Conduit.hs b/src/Crypto/Conduit.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Conduit.hs
@@ -0,0 +1,419 @@
+{-# LANGUAGE BangPatterns #-}
+-- | This module contains wrappers for cryptographic functions
+-- using the @conduit@ package.  Currently there is support for
+-- hashes, HMACs and many modes of block ciphers (but not
+-- everything @crypto-api@ supports has a counterpart here).
+-- All functions on this package work in constant memory.
+module Crypto.Conduit
+    ( -- * Cryptographic hash functions
+      sinkHash
+
+      -- * Hash-based message authentication code (HMAC)
+    , sinkHmac
+
+      -- * Block ciphers
+      -- ** Electronic codebook mode (ECB)
+    , conduitEncryptEcb
+    , conduitDecryptEcb
+      -- ** Cipher-block chaining mode (CBC)
+    , conduitEncryptCbc
+    , conduitDecryptCbc
+      -- ** Cipher feedback mode (CFB)
+    , conduitEncryptCfb
+    , conduitDecryptCfb
+      -- ** Output feedback mode (OFB)
+    , conduitEncryptOfb
+    , conduitDecryptOfb
+      -- ** Counter mode (CTR)
+    , conduitEncryptCtr
+    , conduitDecryptCtr
+    , sourceCtr
+      -- ** Cipher-block chaining message authentication code (CBC-MAC)
+    , sinkCbcMac
+
+      -- * Helpers
+    , blocked
+    , BlockMode(..)
+    , Block(..)
+    ) where
+
+-- from base
+import Control.Applicative ((<$>))
+import Control.Arrow (first)
+import Data.Bits (xor)
+
+-- from bytestring
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as L
+
+-- from cereal
+import qualified Data.Serialize as S
+
+-- from crypto-api
+import Crypto.Classes ((.::.))
+import qualified Crypto.Classes as C
+import qualified Crypto.HMAC as C
+import qualified Crypto.Modes as C
+--import qualified Crypto.Padding as C
+--import qualified Crypto.Random as C
+import qualified Crypto.Types as C
+
+-- from conduit
+import Data.Conduit
+
+
+-- | Helper to get our return type.
+getType :: Monad m => sink input m output -> output
+getType = undefined
+
+
+----------------------------------------------------------------------
+
+
+-- | A 'Sink' that hashes a stream of 'B.ByteString'@s@ and
+-- creates a digest @d@.
+sinkHash :: (Resource m, C.Hash ctx d) => Sink B.ByteString m d
+sinkHash = blocked AnyMultiple blockSize =$ sink
+    where
+      sink = sinkState C.initialCtx
+                       push
+                       (const $ fail "sinkHash")
+
+      push ctx (Full bs) =
+          let !ctx' = C.updateCtx ctx bs
+          in return (ctx', Processing)
+      push ctx (LastOne bs) =
+          let !ret = C.finalize ctx bs
+          in return (error "sinkHash", Done Nothing ret)
+
+      blockSize = (C.blockLength .::. getType sink) `div` 8
+
+
+----------------------------------------------------------------------
+
+
+-- | A 'Sink' that computes the HMAC of a stream of
+-- 'B.ByteString'@s@ and creates a digest @d@.
+sinkHmac :: (Resource m, C.Hash ctx d) => C.MacKey -> Sink B.ByteString m d
+sinkHmac (C.MacKey key) = blocked AnyMultiple blockSize =$ sink
+    where
+      --------- Taken and modified from Crypto.HMAC:
+      key' =
+          case B.length key `compare` blockSize of
+            GT -> B.append
+                    (S.encode $ C.hashFunc' d key)
+                    (B.replicate (blockSize - outputSize) 0x00)
+            EQ -> key
+            LT -> B.append key (B.replicate (blockSize - B.length key) 0x00)
+      ko = B.map (`xor` 0x5c) key'
+      ki = B.map (`xor` 0x36) key'
+      ---------
+
+      sink = sinkState (C.updateCtx C.initialCtx ki)
+                       push
+                       (const $ fail "sinkHmac")
+
+      push ctx (Full bs) =
+          let !ctx' = C.updateCtx ctx bs
+          in return (ctx', Processing)
+      push ctx (LastOne bs) =
+          let !inner = C.finalize ctx bs `asTypeOf` d
+              !outer = C.hash $ L.fromChunks [ko, S.encode inner]
+          in return (error "sinkHmac", Done Nothing outer)
+
+      d = getType sink
+      blockSize  = (C.blockLength  .::. d) `div` 8
+      outputSize = (C.outputLength .::. d) `div` 8
+
+
+----------------------------------------------------------------------
+
+
+-- | A 'Conduit' that encrypts a stream of 'B.ByteString'@s@
+-- using ECB mode.  Expects the input length to be a multiple of
+-- the block size of the cipher and fails otherwise.  (Note that
+-- ECB has many undesirable cryptographic properties, please
+-- avoid it if you don't know what you're doing.)
+conduitEncryptEcb :: (Resource m, C.BlockCipher k) =>
+                     k -- ^ Cipher key.
+                  -> Conduit B.ByteString m B.ByteString
+conduitEncryptEcb k =
+    blockCipherConduit k
+      AnyMultiple
+      ()
+      (\_ input -> ((), C.encryptBlock k input))
+      (\_ _ -> fail "conduitEncryptEcb: input has an incomplete final block.")
+
+
+-- | A 'Conduit' that decrypts a stream of 'B.ByteString'@s@
+-- using ECB mode.  Expects the input length to be a multiple of
+-- the block size of the cipher and fails otherwise.
+conduitDecryptEcb :: (Resource m, C.BlockCipher k) =>
+                     k -- ^ Cipher key.
+                  -> Conduit B.ByteString m B.ByteString
+conduitDecryptEcb k =
+    blockCipherConduit k
+      AnyMultiple
+      ()
+      (\_ input -> ((), C.decryptBlock k input))
+      (\_ _ -> fail "conduitDecryptEcb: input has an incomplete final block.")
+
+
+----------------------------------------------------------------------
+
+
+-- | A 'Conduit' that encrypts a stream of 'B.ByteString'@s@
+-- using CBC mode.  Expects the input length to be a multiple of
+-- the block size of the cipher and fails otherwise.
+conduitEncryptCbc :: (Resource m, C.BlockCipher k) =>
+                     k      -- ^ Cipher key.
+                  -> C.IV k -- ^ Initialization vector.
+                  -> Conduit B.ByteString m B.ByteString
+conduitEncryptCbc k iv =
+    blockCipherConduit k
+      StrictBlockSize
+      (S.encode iv)
+      (\iv' input -> let output = C.encryptBlock k (iv' `zwp` input)
+                     in (output, output))
+      (\_ _ -> fail "conduitEncryptCbc: input has an incomplete final block.")
+
+
+-- | A 'Conduit' that decrypts a stream of 'B.ByteString'@s@
+-- using CBC mode.  Expects the input length to be a multiple of
+-- the block size of the cipher and fails otherwise.
+conduitDecryptCbc :: (Resource m, C.BlockCipher k) =>
+                     k      -- ^ Cipher key.
+                  -> C.IV k -- ^ Initialization vector.
+                  -> Conduit B.ByteString m B.ByteString
+conduitDecryptCbc k iv =
+    blockCipherConduit k
+      StrictBlockSize
+      (S.encode iv)
+      (\iv' input -> let output = C.decryptBlock k input `zwp` iv'
+                     in (input, output))
+      (\_ _ -> fail "conduitDecryptCbc: input has an incomplete final block.")
+
+
+----------------------------------------------------------------------
+
+
+-- | A 'Conduit' that encrypts a stream of 'B.ByteString'@s@
+-- using CFB mode.  Expects the input length to be a multiple of
+-- the block size of the cipher and fails otherwise.
+conduitEncryptCfb :: (Resource m, C.BlockCipher k) =>
+                     k      -- ^ Cipher key.
+                  -> C.IV k -- ^ Initialization vector.
+                  -> Conduit B.ByteString m B.ByteString
+conduitEncryptCfb k iv =
+    blockCipherConduit k
+      StrictBlockSize
+      (S.encode iv)
+      (\iv' input -> let output = C.encryptBlock k iv' `zwp` input
+                     in (output, output))
+      (\_ _ -> fail "conduitEncryptCfb: input has an incomplete final block.")
+
+
+-- | A 'Conduit' that decrypts a stream of 'B.ByteString'@s@
+-- using CFB mode.  Expects the input length to be a multiple of
+-- the block size of the cipher and fails otherwise.
+conduitDecryptCfb :: (Resource m, C.BlockCipher k) =>
+                     k      -- ^ Cipher key.
+                  -> C.IV k -- ^ Initialization vector.
+                  -> Conduit B.ByteString m B.ByteString
+conduitDecryptCfb k iv =
+    blockCipherConduit k
+      StrictBlockSize
+      (S.encode iv)
+      (\iv' input -> let output = C.encryptBlock k iv' `zwp` input
+                     in (input, output))
+      (\_ _ -> fail "conduitDecryptCfb: input has an incomplete final block.")
+
+
+----------------------------------------------------------------------
+
+
+-- | A 'Conduit' that encrypts a stream of 'B.ByteString'@s@
+-- using OFB mode.  Expects the input length to be a multiple of
+-- the block size of the cipher and fails otherwise.
+conduitEncryptOfb :: (Resource m, C.BlockCipher k) =>
+                     k      -- ^ Cipher key.
+                  -> C.IV k -- ^ Initialization vector.
+                  -> Conduit B.ByteString m B.ByteString
+conduitEncryptOfb k iv =
+    blockCipherConduit k
+      StrictBlockSize
+      (S.encode iv)
+      (\iv' input -> let inter = C.encryptBlock k iv'
+                     in (inter, inter `zwp` input))
+      (\_ _ -> fail "conduitEncryptOfb: input has an incomplete final block.")
+
+
+-- | Synonym for 'conduitEncryptOfb', since for OFB mode both
+-- encryption and decryption are the same.
+conduitDecryptOfb :: (Resource m, C.BlockCipher k) =>
+                     k      -- ^ Cipher key.
+                  -> C.IV k -- ^ Initialization vector.
+                  -> Conduit B.ByteString m B.ByteString
+conduitDecryptOfb = conduitEncryptOfb
+
+
+----------------------------------------------------------------------
+
+
+-- | A 'Conduit' that encrypts a stream of 'B.ByteString'@s@
+-- using CTR mode.  The input may have any length, even
+-- non-multiples of the block size.
+conduitEncryptCtr :: (Resource m, C.BlockCipher k) =>
+                     k      -- ^ Cipher key.
+                  -> C.IV k -- ^ Initialization vector.
+                  -> (C.IV k -> C.IV k) -- ^ Increment counter ('C.incIV' is recommended)
+                  -> Conduit B.ByteString m B.ByteString
+conduitEncryptCtr k iv incIV =
+    blockCipherConduit k
+      StrictBlockSize
+      iv
+      (\iv' input -> let !iv''  = incIV iv'
+                         output = C.encryptBlock k (S.encode iv') `zwp` input
+                     in (iv'', output))
+      (\iv' input -> let output = C.encryptBlock k (S.encode iv') `zwp` input
+                     in return output)
+
+
+-- | Synonym for 'conduitEncryptCtr', since for CTR mode both
+-- encryption and decryption are the same.
+conduitDecryptCtr :: (Resource m, C.BlockCipher k) =>
+                     k      -- ^ Cipher key.
+                  -> C.IV k -- ^ Initialization vector.
+                  -> (C.IV k -> C.IV k) -- ^ Increment counter ('C.incIV' is recommended)
+                  -> Conduit B.ByteString m B.ByteString
+conduitDecryptCtr = conduitEncryptCtr
+
+
+-- | An infinite stream of bytes generated by a block cipher on
+-- CTR mode.
+sourceCtr :: (Resource m, C.BlockCipher k) =>
+             k      -- ^ Cipher key.
+          -> C.IV k -- ^ Initialization vector.
+          -> Source m B.ByteString
+sourceCtr k iv = sourceState iv pull
+    where
+      pull iv' =
+          let !iv'' = C.incIV iv'
+              block = C.encryptBlock k $ S.encode iv'
+          in return (iv'', Open block)
+
+
+----------------------------------------------------------------------
+
+
+-- | A 'Sink' that computes the CBC-MAC of a stream of
+-- 'B.ByteString'@s@ and creates a digest @d@.  Expects the input
+-- length to be a multiple of the block size of the cipher and
+-- fails otherwise.  (Note that CBC-MAC is not secure for
+-- variable-length messages.)
+sinkCbcMac :: (Resource m, C.BlockCipher k) =>
+              k -- ^ Cipher key.
+           -> Sink B.ByteString m B.ByteString
+sinkCbcMac k = blocked StrictBlockSize blockSize =$ sink
+    where
+      sink = sinkState (B.replicate blockSize 0) push close
+
+      push iv (Full input) =
+          let !iv' = C.encryptBlock k (iv `zwp` input)
+          in return (iv', Processing)
+      push iv (LastOne input)
+          | B.null input = return (error "sinkCbcMac", Done Nothing iv)
+          | otherwise    = fail "sinkCbcMac: input has an incomplete final block."
+
+      close _ = fail "sinkCbcMac"
+
+      blockSize = (C.blockSize .::. k) `div` 8
+
+
+----------------------------------------------------------------------
+
+
+-- | A 'Conduit' that takes arbitrary 'B.ByteString'@s@ and
+-- outputs 'Block'@s@.  Each 'Full' block will have a length that
+-- is multiple of the given block size (either exactly the block
+-- size or a multiple of at least 1x the block size, depending on
+-- the 'BlockMode').  All 'Block'@s@ beside the last one will be
+-- 'Full'.  The last block will always be 'LastOne' with less
+-- bytes than the block size, possibly zero.
+blocked :: Resource m =>
+           BlockMode
+        -> C.ByteLength -- ^ Block size
+        -> Conduit B.ByteString m Block
+blocked mode blockSize = conduitState B.empty push close
+    where
+      block = case mode of
+                StrictBlockSize -> blockStrict []
+                AnyMultiple     -> blockAny
+        where
+          blockStrict acc bs
+              | B.length bs < blockSize = (reverse acc, bs)
+              | otherwise               = blockStrict (Full this : acc) rest
+              where (this, rest) = B.splitAt blockSize bs
+
+          blockAny bs
+              | n >= 1    = first ((:[]) . Full) $ B.splitAt (n * blockSize) bs
+              | otherwise = ([], bs)
+              where n = B.length bs `div` blockSize
+
+      append bs1 bs2
+          | B.null bs1 = bs2
+          | otherwise  = B.append bs1 bs2
+
+      push acc = return . mk . block . append acc
+          where
+            mk (blks, rest) = (rest, Producing blks)
+
+      close = return . (:[]) . LastOne
+
+
+-- | How 'Block's should be returned, either with strictly the
+-- block size or with a multiple of at least 1x the block size.
+data BlockMode = StrictBlockSize | AnyMultiple
+                 deriving (Eq, Ord, Show, Enum)
+
+
+-- | A block returned by 'blocked'.
+data Block = Full B.ByteString | LastOne B.ByteString
+             deriving (Eq, Ord, Show)
+
+
+-- | Constructs a 'Conduit' for a 'BlockCipher'.
+blockCipherConduit :: (Resource m, C.BlockCipher k) =>
+                      k -- ^ Cipher key (not used, just for getting block size).
+                   -> BlockMode
+                   -> s -- ^ Initial state.
+                   -> (s -> B.ByteString -> (s, B.ByteString))        -- ^ Encrypt block.
+                   -> (s -> B.ByteString -> ResourceT m B.ByteString) -- ^ Final encryption.
+                   -> Conduit B.ByteString m B.ByteString
+blockCipherConduit key mode initialState apply final = blocked mode blockSize =$= conduit
+    where
+      blockSize = (C.blockSize .::. key) `div` 8
+
+      conduit = conduitState initialState push close
+
+      push state (Full input) =
+          let (!state', !output) = apply state input
+          in return (state', Producing [output])
+      push _ (LastOne input) | B.null input =
+          return (error "blockCipherConduit", Finished Nothing [])
+      push state (LastOne input) = mk <$> final state input
+          where mk output = (error "blockCipherConduit", Finished Nothing [output])
+
+      close _ = fail "blockCipherConduit"
+
+
+-- | zipWith xor + pack
+--
+-- As a result of rewrite rules, this should automatically be
+-- optimized (at compile time) to use the bytestring libraries
+-- 'zipWith'' function.
+--
+-- Taken from crypto-api.
+zwp :: B.ByteString -> B.ByteString -> B.ByteString
+zwp a = B.pack . B.zipWith xor a
+{-# INLINEABLE zwp #-}
diff --git a/tests/runtests.hs b/tests/runtests.hs
new file mode 100644
--- /dev/null
+++ b/tests/runtests.hs
@@ -0,0 +1,219 @@
+{-# LANGUAGE Rank2Types #-}
+
+-- from base
+import Control.Applicative ((<$>))
+import Control.Monad.ST (runST)
+import Data.Word (Word8)
+
+-- from bytestring
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as L
+
+-- from crypto-api
+import Crypto.Classes ((.::.))
+import qualified Crypto.Classes as C
+import qualified Crypto.HMAC as C
+import qualified Crypto.Modes as C
+--import qualified Crypto.Padding as C
+--import qualified Crypto.Random as C
+import qualified Crypto.Types as C
+
+-- from conduit
+import Data.Conduit
+import Data.Conduit.Binary (isolate)
+import Data.Conduit.List (sourceList, consume)
+
+-- from cryptocipher
+import Crypto.Cipher.AES (AES128, AES192, AES256)
+
+-- from cryptohash
+import Crypto.Hash.MD2 (MD2)
+import Crypto.Hash.MD4 (MD4)
+import Crypto.Hash.MD5 (MD5)
+import Crypto.Hash.RIPEMD160 (RIPEMD160)
+import Crypto.Hash.SHA1 (SHA1)
+import Crypto.Hash.SHA224 (SHA224)
+import Crypto.Hash.SHA256 (SHA256)
+import Crypto.Hash.SHA384 (SHA384)
+import Crypto.Hash.SHA512 (SHA512)
+import Crypto.Hash.Skein256 (Skein256)
+import Crypto.Hash.Skein512 (Skein512)
+import Crypto.Hash.Tiger (Tiger)
+
+-- from skein
+import qualified Crypto.Skein as Skein
+
+-- from hspec
+import Test.Hspec.Monadic
+import Test.Hspec.QuickCheck
+import Test.Hspec.HUnit ()
+
+-- from this package
+import Crypto.Conduit
+
+
+
+main :: IO ()
+main = hspecX $ do
+  describe "cryptohash's MD2"        $ testHash (undefined :: MD2)
+  describe "cryptohash's MD4"        $ testHash (undefined :: MD4)
+  describe "cryptohash's MD5"        $ testHash (undefined :: MD5)
+  describe "cryptohash's RIPEMD160"  $ testHash (undefined :: RIPEMD160)
+  describe "cryptohash's SHA1"       $ testHash (undefined :: SHA1)
+  describe "cryptohash's SHA224"     $ testHash (undefined :: SHA224)
+  describe "cryptohash's SHA256"     $ testHash (undefined :: SHA256)
+  describe "cryptohash's SHA384"     $ testHash (undefined :: SHA384)
+  describe "cryptohash's SHA512"     $ testHash (undefined :: SHA512)
+  describe "cryptohash's Skein256"   $ testHash (undefined :: Skein256)
+  describe "cryptohash's Skein512"   $ testHash (undefined :: Skein512)
+  describe "cryptohash's Tiger"      $ testHash (undefined :: Tiger)
+  describe "skein's Skein_512_512"   $ testHash (undefined :: Skein.Skein_512_512)
+  describe "skein's Skein_1024_1024" $ testHash (undefined :: Skein.Skein_1024_1024)
+  describe "skein's Skein_256_256"   $ testHash (undefined :: Skein.Skein_256_256)
+  describe "skein's Skein_256_128"   $ testHash (undefined :: Skein.Skein_256_128)
+  describe "skein's Skein_256_160"   $ testHash (undefined :: Skein.Skein_256_160)
+  describe "skein's Skein_256_224"   $ testHash (undefined :: Skein.Skein_256_224)
+  describe "skein's Skein_512_128"   $ testHash (undefined :: Skein.Skein_512_128)
+  describe "skein's Skein_512_160"   $ testHash (undefined :: Skein.Skein_512_160)
+  describe "skein's Skein_512_224"   $ testHash (undefined :: Skein.Skein_512_224)
+  describe "skein's Skein_512_256"   $ testHash (undefined :: Skein.Skein_512_256)
+  describe "skein's Skein_512_384"   $ testHash (undefined :: Skein.Skein_512_384)
+  describe "skein's Skein_1024_384"  $ testHash (undefined :: Skein.Skein_1024_384)
+  describe "skein's Skein_1024_512"  $ testHash (undefined :: Skein.Skein_1024_512)
+  describe "cryptocipher's AES128"   $ testBlockCipher (undefined :: AES128)
+  describe "cryptocipher's AES192"   $ testBlockCipher (undefined :: AES192)
+  describe "cryptocipher's AES256"   $ testBlockCipher (undefined :: AES256)
+
+
+----------------------------------------------------------------------
+
+
+testHash :: C.Hash ctx d => d -> Specs
+testHash d = do
+  prop "works with sinkHash" $
+    \str -> prop_sinkHash d (L.pack str)
+  prop "works with sinkHmac" $
+    \key str -> prop_sinkHmac d (C.MacKey $ B.pack key) (L.pack str)
+
+
+prop_sinkHash :: C.Hash ctx d => d -> L.ByteString -> Bool
+prop_sinkHash d input =
+    let d1 = runPureResource $ sourceList (L.toChunks input) $$ sinkHash
+        d2 = C.hashFunc d input
+    in d1 == d2
+
+
+prop_sinkHmac :: C.Hash ctx d => d -> C.MacKey -> L.ByteString -> Bool
+prop_sinkHmac d mackey input =
+    let d1 = runPureResource $ sourceList (L.toChunks input) $$ sinkHmac mackey
+        d2 = C.hmac mackey input `asTypeOf` d
+    in d1 == d2
+
+
+----------------------------------------------------------------------
+
+
+testBlockCipher :: C.BlockCipher k => k -> Specs
+testBlockCipher undefinedKey = do
+  let Just k =
+          let len = (C.keyLength .::. k) `div` 8
+          in C.buildKey (B.replicate len 0xFF) `asTypeOf` Just undefinedKey
+      blockSize = (C.blockSize .::. k) `div` 8
+
+  prop "works with conduitEncryptEcb" $
+    testBlockCipherConduit
+      (Just blockSize)
+      (conduitEncryptEcb k)
+      (C.ecb k)
+  prop "works with conduitDecryptEcb" $
+    testBlockCipherConduit
+      (Just blockSize)
+      (conduitDecryptEcb k)
+      (C.unEcb k)
+
+  prop "works with conduitEncryptCbc" $
+    testBlockCipherConduit
+      (Just blockSize)
+      (conduitEncryptCbc k C.zeroIV)
+      (fst . C.cbc k C.zeroIV)
+  prop "works with conduitDecryptCbc" $
+    testBlockCipherConduit
+      (Just blockSize)
+      (conduitDecryptCbc k C.zeroIV)
+      (fst . C.unCbc k C.zeroIV)
+
+  prop "works with conduitEncryptCfb" $
+    testBlockCipherConduit
+      (Just blockSize)
+      (conduitEncryptCfb k C.zeroIV)
+      (fst . C.cfb k C.zeroIV)
+  prop "works with conduitDecryptCfb" $
+    testBlockCipherConduit
+      (Just blockSize)
+      (conduitDecryptCfb k C.zeroIV)
+      (fst . C.unCfb k C.zeroIV)
+
+  prop "works with conduitEncryptOfb" $
+    testBlockCipherConduit
+      (Just blockSize)
+      (conduitEncryptOfb k C.zeroIV)
+      (fst . C.ofb k C.zeroIV)
+  prop "works with conduitDecryptOfb" $
+    testBlockCipherConduit
+      (Just blockSize)
+      (conduitDecryptOfb k C.zeroIV)
+      (fst . C.unOfb k C.zeroIV)
+
+  prop "works with conduitEncryptCtr" $
+    testBlockCipherConduit
+      Nothing
+      (conduitEncryptCtr k C.zeroIV C.incIV)
+      (fst . C.ctr C.incIV k C.zeroIV)
+  prop "works with conduitDecryptCtr" $
+    testBlockCipherConduit
+      Nothing
+      (conduitDecryptCtr k C.zeroIV C.incIV)
+      (fst . C.unCtr C.incIV k C.zeroIV)
+
+  it "works with sourceCtr" $
+    let len :: Num a => a
+        len = 1024 * 1024 -- 1 MiB
+        r1 = runPureResource $ sourceCtr k C.zeroIV $$ isolate len =$ consumeAsLazy
+        r2 = fst $ C.ctr C.incIV k C.zeroIV (L.replicate len 0)
+    in r1 == r2
+
+  prop "works with sinkCbcMac" $
+    \input -> let inputL = fixBlockedSize blockSize (L.pack input)
+                  r1 = runPureResource $ sourceList (L.toChunks inputL) $$ sinkCbcMac k
+                  r2 = B.concat $ L.toChunks $ C.cbcMac k inputL
+              in r1 == r2
+
+
+testBlockCipherConduit ::
+       Maybe C.ByteLength -- ^ Fix input length to be a multiple of the block size?
+    -> (forall m. Resource m => Conduit B.ByteString m B.ByteString)
+    -> (L.ByteString -> L.ByteString)
+    -> [Word8]
+    -> Bool
+testBlockCipherConduit mblockSize conduit lazyfun input =
+    let inputL = maybe id fixBlockedSize mblockSize (L.pack input)
+        r1 = runPureResource $ sourceList (L.toChunks inputL) $$ conduit =$ consumeAsLazy
+        r2 = lazyfun inputL
+    in r1 == r2
+
+
+----------------------------------------------------------------------
+
+
+runPureResource :: (forall m. Resource m => ResourceT m a) -> a
+runPureResource r = runST (runResourceT r)
+
+consumeAsLazy :: Resource m => Sink B.ByteString m L.ByteString
+consumeAsLazy = L.fromChunks <$> consume
+
+fixBlockedSize :: C.ByteLength -> L.ByteString -> L.ByteString
+fixBlockedSize blockSize lbs =
+    let blockSize' = fromIntegral blockSize
+        toFill     = let leftovers = L.length lbs `mod` blockSize'
+                     in if leftovers == 0 then 0 else blockSize' - leftovers
+    in L.append lbs $ L.replicate toFill 0xFF
