diff --git a/Data/OpenPGP/CryptoAPI.hs b/Data/OpenPGP/CryptoAPI.hs
--- a/Data/OpenPGP/CryptoAPI.hs
+++ b/Data/OpenPGP/CryptoAPI.hs
@@ -1,13 +1,19 @@
-module Data.OpenPGP.CryptoAPI (fingerprint, sign, verify) where
+module Data.OpenPGP.CryptoAPI (fingerprint, sign, verify, encrypt, decryptAsymmetric, decryptSymmetric, decryptSecretKey) where
 
-import Numeric
-import Data.Word
 import Data.Char
 import Data.Bits
 import Data.List (find)
-import Data.Binary
+import Data.Maybe (mapMaybe, catMaybes, listToMaybe)
+import Control.Arrow
+import Control.Applicative
+import Control.Monad
+import Control.Monad.Trans.Class (lift)
+import Control.Monad.Trans.State (StateT(..), runStateT)
+import Data.Binary (encode, decode, get, Word16)
 import Crypto.Classes hiding (hash,sign,verify,encode)
-import Crypto.Random (CryptoRandomGen)
+import Data.Tagged (untag, asTaggedTypeOf, Tagged(..))
+import Crypto.Modes (cfb, unCfb, IV, zeroIV)
+import Crypto.Random (CryptoRandomGen, GenError(GenErrorOther), genBytes)
 import Crypto.Hash.MD5 (MD5)
 import Crypto.Hash.SHA1 (SHA1)
 import Crypto.Hash.RIPEMD160 (RIPEMD160)
@@ -15,25 +21,41 @@
 import Crypto.Hash.SHA384 (SHA384)
 import Crypto.Hash.SHA512 (SHA512)
 import Crypto.Hash.SHA224 (SHA224)
-import qualified Crypto.Classes as Serialize (encode)
+import Crypto.Cipher.AES (AES128,AES192,AES256)
+import qualified Data.Serialize as Serialize
 import qualified Crypto.Cipher.RSA as RSA
 import qualified Crypto.Cipher.DSA as DSA
 import qualified Data.ByteString as BS
 import qualified Data.ByteString.Lazy as LZ
-import qualified Data.ByteString.Lazy.UTF8 as LZ (fromString)
 
 import qualified Data.OpenPGP as OpenPGP
+import Data.OpenPGP.CryptoAPI.Util
+import Data.OpenPGP.CryptoAPI.Blowfish128
 
--- | Generate a key fingerprint from a PublicKeyPacket or SecretKeyPacket
--- <http://tools.ietf.org/html/rfc4880#section-12.2>
-fingerprint :: OpenPGP.Packet -> String
-fingerprint p
-	| OpenPGP.version p == 4 = snd $ hash OpenPGP.SHA1 material
-	| OpenPGP.version p `elem` [2, 3] = snd $ hash OpenPGP.MD5 material
-	| otherwise = error "Unsupported Packet version or type in fingerprint"
-	where
-	material = LZ.concat $ OpenPGP.fingerprint_material p
+-- | An encryption routine
+type Encrypt g = (LZ.ByteString -> g -> (LZ.ByteString, g))
 
+-- | A decryption routine, Bool=True to do resynchronization
+type Decrypt = (Bool -> LZ.ByteString -> (LZ.ByteString, LZ.ByteString))
+
+-- Start differently-formatted section
+-- | This should be in Crypto.Classes and is based on buildKeyIO
+buildKeyGen :: (BlockCipher k, CryptoRandomGen g) => g -> Either GenError (k, g)
+buildKeyGen = runStateT (go (0::Int))
+  where
+  go 1000 = lift $ Left $ GenErrorOther
+                  "Tried 1000 times to generate a key from the system entropy.\
+                  \  No keys were returned! Perhaps the system entropy is broken\
+                  \ or perhaps the BlockCipher instance being used has a non-flat\
+                  \ keyspace."
+  go i = do
+	let bs = keyLength
+	kd <- StateT $ genBytes ((7 + untag bs) `div` 8)
+	case buildKey kd of
+		Nothing -> go (i+1)
+		Just k  -> return $ k `asTaggedTypeOf` bs
+-- End differently-formatted section
+
 find_key :: OpenPGP.Message -> String -> Maybe OpenPGP.Packet
 find_key = OpenPGP.find_key fingerprint
 
@@ -54,13 +76,6 @@
 	pad s = replicate (len - length s) '0' ++ s
 	len = (outputLength `for` d) `div` 8
 
-hexString :: [Word8] -> String
-hexString = foldr (pad `oo` showHex) ""
-	where
-	oo = (.) . (.)
-	pad s | odd $ length s = '0':s
-	      | otherwise = s
-
 -- http://tools.ietf.org/html/rfc3447#page-43
 -- http://tools.ietf.org/html/rfc4880#section-5.2.2
 emsa_pkcs1_v1_5_hash_padding :: OpenPGP.HashAlgorithm -> BS.ByteString
@@ -74,22 +89,80 @@
 emsa_pkcs1_v1_5_hash_padding _ =
 	error "Unsupported HashAlgorithm in emsa_pkcs1_v1_5_hash_padding."
 
-toStrictBS :: LZ.ByteString -> BS.ByteString
-toStrictBS = BS.concat . LZ.toChunks
+blockBytes :: (BlockCipher k, Num n) => k -> n
+blockBytes k = fromIntegral $ blockSizeBytes `for` k
 
-toLazyBS :: BS.ByteString -> LZ.ByteString
-toLazyBS = LZ.fromChunks . (:[])
+pgpCFBPrefix :: (BlockCipher k, CryptoRandomGen g) => k -> g -> (LZ.ByteString, g)
+pgpCFBPrefix k g =
+	(toLazyBS $ str `BS.append` BS.reverse (BS.take 2 $ BS.reverse str), g')
+	where
+	Right (str,g') = genBytes (blockSizeBytes `for` k) g
 
-fromJustMPI :: Maybe OpenPGP.MPI -> Integer
-fromJustMPI (Just (OpenPGP.MPI x)) = x
-fromJustMPI _ = error "Not a Just MPI, Data.OpenPGP.CryptoAPI"
+pgpCFB :: (BlockCipher k, CryptoRandomGen g) => k -> (LZ.ByteString -> LZ.ByteString -> LZ.ByteString) -> Encrypt g
+pgpCFB k sufGen bs g =
+	(simpleCFB k zeroIV (LZ.concat [p, bs, sufGen p bs]), g')
+	where
+	(p,g') = pgpCFBPrefix k g
 
+simpleCFB :: (BlockCipher k) => k -> IV k -> LZ.ByteString -> LZ.ByteString
+simpleCFB k iv = padThenUnpad k (fst . cfb k iv)
+
+pgpUnCFB :: (BlockCipher k) => k -> Decrypt
+pgpUnCFB k False s = LZ.splitAt (2 + blockBytes k) $ simpleUnCFB k zeroIV s
+pgpUnCFB k True s = (simpleUnCFB k zeroIV prefix, simpleUnCFB k iv content)
+	where
+	Just iv = sDecode $ toStrictBS $ LZ.drop 2 prefix
+	(prefix, content) = LZ.splitAt (2 + blockBytes k) s
+
+simpleUnCFB :: (BlockCipher k) => k -> IV k -> LZ.ByteString -> LZ.ByteString
+simpleUnCFB k iv = padThenUnpad k (fst . unCfb k iv)
+
+padThenUnpad :: (BlockCipher k) => k -> (LZ.ByteString -> LZ.ByteString) -> LZ.ByteString -> LZ.ByteString
+padThenUnpad k f s = dropPadEnd (f padded)
+	where
+	dropPadEnd s = LZ.take (LZ.length s - padAmount) s
+	padded = s `LZ.append` LZ.replicate padAmount 0
+	padAmount = blockBytes k - (LZ.length s `mod` blockBytes k)
+
+addBitLen :: LZ.ByteString -> LZ.ByteString
+addBitLen bytes = encode (bitLen bytes :: Word16) `LZ.append` bytes
+	where
+	bitLen bytes = (fromIntegral (LZ.length bytes) - 1) * 8 + sigBit bytes
+	sigBit bytes = fst $ until ((==0) . snd)
+		(first (+1) . second (`shiftR` 1)) (0,LZ.index bytes 0)
+
+-- Drops 2 because the value is an MPI
+rsaDecrypt :: RSA.PrivateKey -> BS.ByteString -> Maybe BS.ByteString
+rsaDecrypt pk = hush . RSA.decrypt pk . BS.drop 2
+
+rsaEncrypt :: (CryptoRandomGen g) => RSA.PublicKey -> BS.ByteString -> StateT g (Either GenError) BS.ByteString
+rsaEncrypt pk bs = StateT (\g ->
+		case RSA.encrypt g pk bs of
+			(Left (RSA.RandomGenFailure e)) -> Left e
+			(Left e) -> Left (GenErrorOther $ show e)
+			(Right v) -> Right v
+	)
+
 integerBytesize :: Integer -> Int
-integerBytesize i = length (LZ.unpack $ encode (OpenPGP.MPI i)) - 2
+integerBytesize i = fromIntegral $ LZ.length (encode (OpenPGP.MPI i)) - 2
 
 keyParam :: Char -> OpenPGP.Packet -> Integer
 keyParam c k = fromJustMPI $ lookup c (OpenPGP.key k)
 
+keyAlgorithmIs :: OpenPGP.KeyAlgorithm -> OpenPGP.Packet -> Bool
+keyAlgorithmIs algo p = OpenPGP.key_algorithm p == algo
+
+secretKeys :: OpenPGP.Message -> ([(String, RSA.PrivateKey)], [(String, DSA.PrivateKey)])
+secretKeys (OpenPGP.Message keys) =
+	(
+		map (fingerprint &&& privateRSAkey) rsa,
+		map (fingerprint &&& privateDSAkey) dsa
+	)
+	where
+	dsa = secrets OpenPGP.DSA
+	rsa = secrets OpenPGP.RSA
+	secrets algo = filter (swing all [isSecretKey, keyAlgorithmIs algo]) keys
+
 privateRSAkey :: OpenPGP.Packet -> RSA.PrivateKey
 privateRSAkey k =
 	-- Invert p and q because u is pinv not qinv
@@ -117,80 +190,77 @@
 dsaKey k = DSA.PublicKey
 	(keyParam 'p' k, keyParam 'g' k, keyParam 'q' k) (keyParam 'y' k)
 
+-- | Generate a key fingerprint from a PublicKeyPacket or SecretKeyPacket
+-- <http://tools.ietf.org/html/rfc4880#section-12.2>
+fingerprint :: OpenPGP.Packet -> String
+fingerprint p
+	| OpenPGP.version p == 4 = snd $ hash OpenPGP.SHA1 material
+	| OpenPGP.version p `elem` [2, 3] = snd $ hash OpenPGP.MD5 material
+	| otherwise = error "Unsupported Packet version or type in fingerprint"
+	where
+	material = LZ.concat $ OpenPGP.fingerprint_material p
+
 -- | Verify a message signature
-verify :: OpenPGP.Message    -- ^ Keys that may have made the signature
-          -> OpenPGP.Message -- ^ LiteralData message to verify
-          -> Int             -- ^ Index of signature to verify (0th, 1st, etc)
-          -> Bool
-verify keys message sigidx =
+verify ::
+	OpenPGP.Message          -- ^ Keys that may have made the signature
+	-> OpenPGP.SignatureOver -- ^ Signatures to verify
+	-> OpenPGP.SignatureOver -- ^ Will only contain signatures that passed
+verify keys over =
+	over {OpenPGP.signatures_over = mapMaybe (uncurry $ verifyOne keys) sigs}
+	where
+	sigs = map (\s -> (s, toStrictBS $ encode over `LZ.append` OpenPGP.trailer s))
+		(OpenPGP.signatures_over over)
+
+verifyOne :: OpenPGP.Message -> OpenPGP.Packet -> BS.ByteString -> Maybe OpenPGP.Packet
+verifyOne keys sig over = fmap (const sig) $ maybeKey >>=
 	case OpenPGP.key_algorithm sig of
 		OpenPGP.DSA -> dsaVerify
 		alg | alg `elem` [OpenPGP.RSA,OpenPGP.RSA_S] -> rsaVerify
-		    | otherwise -> error ("Unsupported key algorithm " ++ show alg)
+		    | otherwise -> const Nothing
 	where
-	dsaVerify = let k' = dsaKey k in
-		case DSA.verify dsaSig (dsaTruncate k' . bhash) k' signature_over of
-			Left _ -> False
-			Right v -> v
-	rsaVerify =
-		case RSA.verify bhash padding (rsaKey k) signature_over rsaSig of
-			Left _ -> False
-			Right v -> v
-	rsaSig = toStrictBS $ LZ.drop 2 $ encode (head $ OpenPGP.signature sig)
+	dsaVerify k = let k' = dsaKey k in
+		hush $ DSA.verify dsaSig (dsaTruncate k' . bhash) k' over
+	rsaVerify k = hush $ RSA.verify bhash padding (rsaKey k) over rsaSig
+	[rsaSig] = map (toStrictBS . LZ.drop 2 . encode) (OpenPGP.signature sig)
 	dsaSig = let [OpenPGP.MPI r, OpenPGP.MPI s] = OpenPGP.signature sig in
 		(r, s)
 	dsaTruncate (DSA.PublicKey (_,_,q) _) = BS.take (integerBytesize q)
 	bhash = fst . hash hash_algo . toLazyBS
 	padding = emsa_pkcs1_v1_5_hash_padding hash_algo
 	hash_algo = OpenPGP.hash_algorithm sig
-	signature_over = toStrictBS $ dta `LZ.append` OpenPGP.trailer sig
-	Just k = OpenPGP.signature_issuer sig >>= find_key keys
-	sig = sigs !! sigidx
-	(sigs, (OpenPGP.LiteralDataPacket {OpenPGP.content = dta}):_) =
-		OpenPGP.signatures_and_data message
+	maybeKey = OpenPGP.signature_issuer sig >>= find_key keys
 
--- | Sign data or key/userID pair.
+-- | Make a signature
+--
+-- In order to set more options on a signature, pass in a signature packet.
 sign :: (CryptoRandomGen g) =>
-        OpenPGP.Message    -- ^ SecretKeys, one of which will be used
-        -> OpenPGP.Message -- ^ Message containing data or key to sign, and optional signature packet
-        -> OpenPGP.HashAlgorithm -- ^ HashAlgorithm to use in signature
-        -> String  -- ^ KeyID of key to choose or @[]@ for first
-        -> Integer -- ^ Timestamp for signature (unless sig supplied)
-        -> g       -- ^ Random number generator
-        -> OpenPGP.Packet
-sign keys message hsh keyid timestamp g =
-	-- WARNING: this style of update is unsafe on most fields
-	-- it is safe on signature and hash_head, though
-	sig {
-		OpenPGP.signature = map OpenPGP.MPI final,
-		OpenPGP.hash_head = 0 -- TODO
-	}
+	OpenPGP.Message          -- ^ SecretKeys, one of which will be used
+	-> OpenPGP.SignatureOver -- ^ Data to sign, and optional signature packet
+	-> OpenPGP.HashAlgorithm -- ^ HashAlgorithm to use in signature
+	-> String                -- ^ KeyID of key to choose
+	-> Integer               -- ^ Timestamp for signature (unless sig supplied)
+	-> g                     -- ^ Random number generator
+	-> (OpenPGP.SignatureOver, g)
+sign keys over hsh keyid timestamp g = (over {OpenPGP.signatures_over = [sig]}, g')
 	where
-	final   = case OpenPGP.key_algorithm sig of
-		OpenPGP.DSA -> [dsaR, dsaS]
-		kalgo | kalgo `elem` [OpenPGP.RSA,OpenPGP.RSA_S] -> [toNum rsaFinal]
+	(final, g') = case OpenPGP.key_algorithm sig of
+		OpenPGP.DSA -> ([dsaR, dsaS], dsaG)
+		kalgo | kalgo `elem` [OpenPGP.RSA,OpenPGP.RSA_S] -> ([toNum rsaFinal], g)
 		      | otherwise ->
 			error ("Unsupported key algorithm " ++ show kalgo ++ "in sign")
-	Right ((dsaR,dsaS),_) = let k' = privateDSAkey k in
+	Right ((dsaR,dsaS),dsaG) = let k' = privateDSAkey k in
 		DSA.sign g (dsaTruncate k' . bhash) k' dta
 	Right rsaFinal = RSA.sign bhash padding (privateRSAkey k) dta
 	dsaTruncate (DSA.PrivateKey (_,_,q) _) = BS.take (integerBytesize q)
-	dta     = toStrictBS $ case signOver of {
-		OpenPGP.LiteralDataPacket {OpenPGP.content = c} -> c;
-		_ -> LZ.concat $ OpenPGP.fingerprint_material signOver ++ [
-			LZ.singleton 0xB4,
-			encode (fromIntegral (length firstUserID) :: Word32),
-			LZ.fromString firstUserID
-		]
-	} `LZ.append` OpenPGP.trailer sig
-	sig     = findSigOrDefault (find OpenPGP.isSignaturePacket m)
+	dta     = toStrictBS $ encode over `LZ.append` OpenPGP.trailer sig
+	sig     = findSigOrDefault (listToMaybe $ OpenPGP.signatures_over over)
 	padding = emsa_pkcs1_v1_5_hash_padding hsh
 	bhash   = fst . hash hsh . toLazyBS
 	toNum   = BS.foldl (\a b -> a `shiftL` 8 .|. fromIntegral b) 0
+	Just k  = find_key keys keyid
 
 	-- Either a SignaturePacket was found, or we need to make one
-	findSigOrDefault (Just s) =
-		OpenPGP.signaturePacket
+	findSigOrDefault (Just s) = OpenPGP.signaturePacket
 		(OpenPGP.version s)
 		(OpenPGP.signature_type s)
 		(OpenPGP.key_algorithm k) -- force to algo of key
@@ -198,7 +268,7 @@
 		(OpenPGP.hashed_subpackets s)
 		(OpenPGP.unhashed_subpackets s)
 		(OpenPGP.hash_head s)
-		(OpenPGP.signature s)
+		(map OpenPGP.MPI final)
 	findSigOrDefault Nothing  = OpenPGP.signaturePacket
 		4
 		defaultStype
@@ -207,32 +277,277 @@
 		([
 			-- Do we really need to pass in timestamp just for the default?
 			OpenPGP.SignatureCreationTimePacket $ fromIntegral timestamp,
-			OpenPGP.IssuerPacket keyid'
-		] ++ (case signOver of
-			OpenPGP.LiteralDataPacket {} -> []
-			_ -> [] -- TODO: OpenPGP.KeyFlagsPacket [0x01, 0x02]
+			OpenPGP.IssuerPacket $ fingerprint k
+		] ++ (case over of
+			OpenPGP.KeySignature  {} -> [OpenPGP.KeyFlagsPacket {
+					OpenPGP.certify_keys = True,
+					OpenPGP.sign_data = True,
+					OpenPGP.encrypt_communication = False,
+					OpenPGP.encrypt_storage = False,
+					OpenPGP.split_key = False,
+					OpenPGP.authentication = False,
+					OpenPGP.group_key = False
+				}]
+			_ -> []
 		))
 		[]
-		undefined
-		undefined
+		0 -- TODO
+		(map OpenPGP.MPI final)
 
-	keyid'  = reverse $ take 16 $ reverse $ fingerprint k
-	Just k  = find_key keys keyid
+	defaultStype = case over of
+		OpenPGP.DataSignature ld _
+			| OpenPGP.format ld == 'b'     -> 0x00
+			| otherwise                    -> 0x01
+		OpenPGP.KeySignature {}           -> 0x1F
+		OpenPGP.SubkeySignature {}        -> 0x18
+		OpenPGP.CertificationSignature {} -> 0x13
 
-	Just (OpenPGP.UserIDPacket firstUserID) = find isUserID m
+encrypt :: (CryptoRandomGen g) =>
+	[BS.ByteString]               -- ^ Passphrases, all of which will be used
+	-> OpenPGP.Message            -- ^ PublicKeys, all of which will be used
+	-> OpenPGP.SymmetricAlgorithm -- ^ Cipher to use
+	-> OpenPGP.Message            -- ^ The 'OpenPGP.Message' to encrypt
+	-> g                          -- ^ Random number generator
+	-> Either GenError (OpenPGP.Message, g)
+encrypt pass (OpenPGP.Message keys) algo msg = runStateT $ do
+	(sk, encP) <- sessionFor algo msg
+	OpenPGP.Message . (++[encP]) <$> liftA2 (++)
+		(mapM (encryptSessionKeyAsymmetric sk) (filter isKey keys))
+		(mapM (encryptSessionKeySymmetric (LZ.take (LZ.length sk - 2) sk) algo) pass)
 
-	defaultStype = case signOver of
-		OpenPGP.LiteralDataPacket {OpenPGP.format = f} ->
-			if f == 'b' then 0x00 else 0x01
-		_ -> 0x13
+encryptSessionKeyAsymmetric :: (CryptoRandomGen g) => LZ.ByteString -> OpenPGP.Packet -> StateT g (Either GenError) OpenPGP.Packet
+encryptSessionKeyAsymmetric sk pk = OpenPGP.AsymmetricSessionKeyPacket 3
+	(fingerprint pk)
+	(OpenPGP.key_algorithm pk)
+	. addBitLen <$> encd (OpenPGP.key_algorithm pk)
+	where
+	encd OpenPGP.RSA = toLazyBS <$> rsaEncrypt (rsaKey pk) (toStrictBS sk)
+	encd _ = lift $ Left $ GenErrorOther $ "Unsupported PublicKey: " ++ show pk
 
-	Just signOver = find isSignable m
-	OpenPGP.Message m = message
+encryptSessionKeySymmetric :: (CryptoRandomGen g) => LZ.ByteString -> OpenPGP.SymmetricAlgorithm -> BS.ByteString -> StateT g (Either GenError) OpenPGP.Packet
+encryptSessionKeySymmetric sk salgo pass = do
+	s2k <- s2k
+	return $ OpenPGP.SymmetricSessionKeyPacket 4 salgo s2k
+		(string2sencrypt salgo s2k (toLazyBS pass) sk)
+	where
+	halgo = s2kHashAlgorithmFor salgo
+	s2k = OpenPGP.IteratedSaltedS2K halgo . decode . toLazyBS <$>
+		(StateT $ genBytes 8) <*> pure 65536
 
-	isSignable (OpenPGP.LiteralDataPacket {}) = True
-	isSignable (OpenPGP.PublicKeyPacket {})   = True
-	isSignable (OpenPGP.SecretKeyPacket {})   = True
-	isSignable _                              = False
+s2kHashAlgorithmFor :: OpenPGP.SymmetricAlgorithm -> OpenPGP.HashAlgorithm
+s2kHashAlgorithmFor OpenPGP.AES128 = s2kHashAlgorithm `for` (undefined :: AES128)
+s2kHashAlgorithmFor OpenPGP.AES192 = s2kHashAlgorithm `for` (undefined :: AES192)
+s2kHashAlgorithmFor OpenPGP.AES256 = s2kHashAlgorithm `for` (undefined :: AES256)
+s2kHashAlgorithmFor OpenPGP.Blowfish = s2kHashAlgorithm `for` (undefined :: Blowfish128)
+s2kHashAlgorithmFor algo = error $ "Unsupported SymmetricAlgorithm " ++ show algo ++ " in Data.OpenPGP.CryptoAPI.s2kHashAlgorithmFor"
 
-	isUserID (OpenPGP.UserIDPacket {})        = True
-	isUserID _                                = False
+s2kHashAlgorithm :: (BlockCipher k) => Tagged k OpenPGP.HashAlgorithm
+s2kHashAlgorithm = v
+	where
+	v = Tagged $ case () of
+		_ | ksize <= 160 -> OpenPGP.SHA1
+		  | ksize <= 256 -> OpenPGP.SHA256
+		  | otherwise    -> OpenPGP.SHA512
+	ksize = keyLength `tagOfTag` v
+
+tagOfTag :: Tagged a c -> Tagged a b -> c
+tagOfTag a b = a `for` (undefined `asTaggedTypeOf` b)
+
+sessionFor :: (CryptoRandomGen g) => OpenPGP.SymmetricAlgorithm -> OpenPGP.Message -> StateT g (Either GenError) (LZ.ByteString, OpenPGP.Packet)
+sessionFor algo@OpenPGP.AES128 msg = do
+	sk <- StateT buildKeyGen
+	encP <- newSession (sk :: AES128) msg
+	return (sessionKeyEncode sk algo, encP)
+sessionFor algo@OpenPGP.AES192 msg = do
+	sk <- StateT buildKeyGen
+	encP <- newSession (sk :: AES192) msg
+	return (sessionKeyEncode sk algo, encP)
+sessionFor algo@OpenPGP.AES256 msg = do
+	sk <- StateT buildKeyGen
+	encP <- newSession (sk :: AES256) msg
+	return (sessionKeyEncode sk algo, encP)
+sessionFor algo@OpenPGP.Blowfish msg = do
+	sk <- StateT buildKeyGen
+	encP <- newSession (sk :: Blowfish128) msg
+	return (sessionKeyEncode sk algo, encP)
+sessionFor algo _ = lift $ Left $ GenErrorOther $ "Unsupported cipher: " ++ show algo
+
+sessionKeyEncode :: (BlockCipher k) => k -> OpenPGP.SymmetricAlgorithm -> LZ.ByteString
+sessionKeyEncode sk algo =
+	LZ.concat [encode algo, toLazyBS bs, encode $ checksum bs]
+	where
+	bs = Serialize.encode sk
+
+newSession :: (BlockCipher k, CryptoRandomGen g, Monad m) => k -> OpenPGP.Message -> StateT g m OpenPGP.Packet
+newSession sk msg = do
+	encd <- StateT $ return . pgpCFB sk (encode `oo` mkMDC) (encode msg)
+	return $ OpenPGP.EncryptedDataPacket 1 encd
+
+mkMDC :: LZ.ByteString -> LZ.ByteString -> OpenPGP.Packet
+mkMDC prefix msg = OpenPGP.ModificationDetectionCodePacket $ toLazyBS $ fst $
+	hash OpenPGP.SHA1 $ LZ.concat [prefix, msg, LZ.pack [0xD3, 0x14]]
+
+checksum :: BS.ByteString -> Word16
+checksum key = fromIntegral $
+	BS.foldl' (\x y -> x + fromIntegral y) (0::Integer) key `mod` 65536
+
+decryptSecretKey ::
+	BS.ByteString           -- ^ Passphrase
+	-> OpenPGP.Packet       -- ^ Encrypted SecretKeyPacket
+	-> Maybe OpenPGP.Packet -- ^ Decrypted SecretKeyPacket
+decryptSecretKey pass k@(OpenPGP.SecretKeyPacket {
+		OpenPGP.version = 4, OpenPGP.key_algorithm = kalgo,
+		OpenPGP.s2k = s2k, OpenPGP.symmetric_algorithm = salgo,
+		OpenPGP.key = existing, OpenPGP.encrypted_data = encd
+	}) | chkF material == toStrictBS chk =
+		fmap (\m -> k {
+			OpenPGP.s2k_useage = 0,
+			OpenPGP.symmetric_algorithm = OpenPGP.Unencrypted,
+			OpenPGP.encrypted_data = LZ.empty,
+			OpenPGP.key = m
+		}) parseMaterial
+	   | otherwise = Nothing
+	where
+	parseMaterial = maybeGet
+		(foldM (\m f -> do {mpi <- get; return $ (f,mpi):m}) existing
+		(OpenPGP.secret_key_fields kalgo)) material
+	(material, chk) = LZ.splitAt (LZ.length decd - chkSize) decd
+	(chkSize, chkF)
+		| OpenPGP.s2k_useage k == 254 = (20, fst . hash OpenPGP.SHA1)
+		| otherwise = (2, Serialize.encode . checksum . toStrictBS)
+	decd = string2sdecrypt salgo s2k (toLazyBS pass) (EncipheredWithIV encd)
+decryptSecretKey _ _ = Nothing
+
+-- | Decrypt an OpenPGP message using secret key
+decryptAsymmetric ::
+	OpenPGP.Message    -- ^ SecretKeys, one of which will be used
+	-> OpenPGP.Message -- ^ A 'OpenPGP.Message' containing AsymmetricSessionKey and EncryptedData
+	-> Maybe OpenPGP.Message
+decryptAsymmetric keys msg@(OpenPGP.Message pkts) = do
+	(_, d) <- getAsymmetricSessionKey keys msg
+	pkt <- find isEncryptedData pkts
+	decryptPacket d pkt
+
+-- | Decrypt an OpenPGP message using passphrase
+decryptSymmetric ::
+	[BS.ByteString]    -- ^ Passphrases, one of which will be used
+	-> OpenPGP.Message -- ^ A 'OpenPGP.Message' containing SymetricSessionKey and EncryptedData
+	-> Maybe OpenPGP.Message
+decryptSymmetric pass msg@(OpenPGP.Message pkts) = do
+	let ds = map snd $ getSymmetricSessionKey pass msg
+	pkt <- find isEncryptedData pkts
+	listToMaybe $ mapMaybe (`decryptPacket` pkt) ds
+
+-- | Decrypt a single packet, given the decryptor
+decryptPacket :: Decrypt -> OpenPGP.Packet -> Maybe OpenPGP.Message
+decryptPacket d (OpenPGP.EncryptedDataPacket {
+		OpenPGP.version = 1,
+		OpenPGP.encrypted_data = encd
+	}) | Just (mkMDC prefix msg) == maybeDecode mdc = maybeDecode msg
+	   | otherwise = Nothing
+	where
+	(msg,mdc) = LZ.splitAt (LZ.length content - 22) content
+	(prefix, content) = d False encd
+decryptPacket d (OpenPGP.EncryptedDataPacket {
+		OpenPGP.version = 0,
+		OpenPGP.encrypted_data = encd
+	}) = maybeDecode (snd $ d True encd)
+decryptPacket _ _ = error "Can only decrypt EncryptedDataPacket in Data.OpenPGP.CryptoAPI.decryptPacket"
+
+getSymmetricSessionKey ::
+	[BS.ByteString]    -- ^ Passphrases, one of which will be used
+	-> OpenPGP.Message -- ^ An OpenPGP Message containing SymmetricSessionKey
+	-> [(OpenPGP.SymmetricAlgorithm, Decrypt)]
+getSymmetricSessionKey pass (OpenPGP.Message ps) =
+	concatMap (\OpenPGP.SymmetricSessionKeyPacket {
+			OpenPGP.s2k = s2k, OpenPGP.symmetric_algorithm = algo,
+			OpenPGP.encrypted_data = encd
+		} ->
+		if LZ.null encd then
+			map ((,) algo . string2decrypt algo s2k) pass'
+		else
+			mapMaybe (\p -> decodeSess $ string2sdecrypt algo s2k p (EncipheredZeroIV encd)) pass'
+	) sessionKeys
+	where
+	decodeSess s = let (a, k) = LZ.splitAt 1 s in
+		(,) (decode a) <$> decodeSymKey (decode a) (toStrictBS k)
+	sessionKeys = filter isSymmetricSessionKey ps
+	pass' = map toLazyBS pass
+
+-- | Decrypt an asymmetrically encrypted symmetric session key
+getAsymmetricSessionKey ::
+	OpenPGP.Message    -- ^ SecretKeys, one of which will be used
+	-> OpenPGP.Message -- ^ An OpenPGP Message containing AssymetricSessionKey
+	-> Maybe (OpenPGP.SymmetricAlgorithm, Decrypt)
+getAsymmetricSessionKey keys (OpenPGP.Message ps) =
+	listToMaybe $ mapMaybe decodeSessionKey $ catMaybes $
+	concatMap (\(sk,ks) ->
+		map ($ toStrictBS $ OpenPGP.encrypted_data sk) ks
+	) toTry
+	where
+	toTry = map (id &&& lookupKey) sessionKeys
+
+	lookupKey (OpenPGP.AsymmetricSessionKeyPacket {
+		OpenPGP.key_algorithm = OpenPGP.RSA,
+		OpenPGP.key_id = key_id
+	}) | all (=='0') key_id = map (rsaDecrypt . snd) rsa
+	   | otherwise = map (rsaDecrypt . snd) $
+		filter (keyIdMatch key_id . fst) rsa
+	lookupKey _ = []
+
+	sessionKeys = filter isAsymmetricSessionKey ps
+	(rsa, _) = secretKeys keys
+
+decodeSessionKey :: BS.ByteString -> Maybe (OpenPGP.SymmetricAlgorithm, Decrypt)
+decodeSessionKey sk
+	| checksum key == (decode (toLazyBS chk) :: Word16) = do
+		algo <- maybeDecode (toLazyBS algoByte)
+		decrypt <- decodeSymKey algo key
+		return (algo, decrypt)
+	| otherwise = Nothing
+	where
+	(key, chk) = BS.splitAt (BS.length rest - 2) rest
+	(algoByte, rest) = BS.splitAt 1 sk
+
+decodeSymKey :: OpenPGP.SymmetricAlgorithm -> BS.ByteString -> Maybe Decrypt
+decodeSymKey OpenPGP.AES128 k = pgpUnCFB <$> (`asTypeOf` (undefined :: AES128)) <$> sDecode k
+decodeSymKey OpenPGP.AES192 k = pgpUnCFB <$> (`asTypeOf` (undefined :: AES192)) <$> sDecode k
+decodeSymKey OpenPGP.AES256 k = pgpUnCFB <$> (`asTypeOf` (undefined :: AES256)) <$> sDecode k
+decodeSymKey OpenPGP.Blowfish k = pgpUnCFB <$> (`asTypeOf` (undefined :: Blowfish128)) <$> sDecode k
+decodeSymKey _ _ = Nothing
+
+string2sencrypt :: OpenPGP.SymmetricAlgorithm -> OpenPGP.S2K -> LZ.ByteString -> LZ.ByteString -> LZ.ByteString
+string2sencrypt OpenPGP.AES128 s2k s = simpleCFB (string2key s2k s :: AES128) zeroIV
+string2sencrypt OpenPGP.AES192 s2k s = simpleCFB (string2key s2k s :: AES192) zeroIV
+string2sencrypt OpenPGP.AES256 s2k s = simpleCFB (string2key s2k s :: AES256) zeroIV
+string2sencrypt OpenPGP.Blowfish s2k s = simpleCFB (string2key s2k s :: Blowfish128) zeroIV
+string2sencrypt algo _ _ = error $ "Unsupported symmetric algorithm : " ++ show algo ++ " in Data.OpenPGP.CryptoAPI.string2sencrypt"
+
+string2decrypt :: OpenPGP.SymmetricAlgorithm -> OpenPGP.S2K -> LZ.ByteString -> Decrypt
+string2decrypt OpenPGP.AES128 s2k s = pgpUnCFB (string2key s2k s :: AES128)
+string2decrypt OpenPGP.AES192 s2k s = pgpUnCFB (string2key s2k s :: AES192)
+string2decrypt OpenPGP.AES256 s2k s = pgpUnCFB (string2key s2k s :: AES256)
+string2decrypt OpenPGP.Blowfish s2k s = pgpUnCFB (string2key s2k s :: Blowfish128)
+string2decrypt algo _ _ = error $ "Unsupported symmetric algorithm : " ++ show algo ++ " in Data.OpenPGP.CryptoAPI.string2decrypt"
+
+string2sdecrypt :: OpenPGP.SymmetricAlgorithm -> OpenPGP.S2K -> LZ.ByteString -> Enciphered -> LZ.ByteString
+string2sdecrypt OpenPGP.AES128 s2k s = withIV $ simpleUnCFB (string2key s2k s :: AES128)
+string2sdecrypt OpenPGP.AES192 s2k s = withIV $ simpleUnCFB (string2key s2k s :: AES192)
+string2sdecrypt OpenPGP.AES256 s2k s = withIV $ simpleUnCFB (string2key s2k s :: AES256)
+string2sdecrypt OpenPGP.Blowfish s2k s = withIV $ simpleUnCFB (string2key s2k s :: Blowfish128)
+string2sdecrypt algo _ _ = error $ "Unsupported symmetric algorithm : " ++ show algo ++ " in Data.OpenPGP.CryptoAPI.string2sdecrypt"
+
+data Enciphered = EncipheredWithIV !LZ.ByteString | EncipheredZeroIV !LZ.ByteString
+
+withIV :: (BlockCipher k) => (IV k -> LZ.ByteString -> LZ.ByteString) -> Enciphered -> LZ.ByteString
+withIV f (EncipheredWithIV s) = f iv $ LZ.drop (fromIntegral $ BS.length $ Serialize.encode iv) s
+	where
+	iv = let Right x = Serialize.decode (toStrictBS s) in x
+withIV f (EncipheredZeroIV s) = f zeroIV s
+
+string2key :: (BlockCipher k) => OpenPGP.S2K -> LZ.ByteString -> k
+string2key s2k s = k
+	where
+	Just k = buildKey $ toStrictBS $
+		LZ.take ksize $ OpenPGP.string2key (fst `oo` hash) s2k s
+	ksize = fromIntegral (keyLength `for` k) `div` 8
diff --git a/Data/OpenPGP/CryptoAPI/Blowfish128.hs b/Data/OpenPGP/CryptoAPI/Blowfish128.hs
new file mode 100644
--- /dev/null
+++ b/Data/OpenPGP/CryptoAPI/Blowfish128.hs
@@ -0,0 +1,20 @@
+module Data.OpenPGP.CryptoAPI.Blowfish128 (Blowfish128) where
+
+import Crypto.Classes (BlockCipher(..))
+import Crypto.Types (BitLength)
+import Crypto.Cipher.Blowfish (Blowfish)
+import Data.Tagged (retag, Tagged(..))
+import qualified Data.Serialize as Serialize
+
+newtype Blowfish128 = Blowfish128 Blowfish
+
+instance Serialize.Serialize Blowfish128 where
+	put (Blowfish128 b) = Serialize.put b
+	get = fmap Blowfish128 Serialize.get
+
+instance BlockCipher Blowfish128 where
+	blockSize = retag (blockSize :: Tagged Blowfish BitLength)
+	encryptBlock (Blowfish128 k) = encryptBlock k
+	decryptBlock (Blowfish128 k) = decryptBlock k
+	buildKey = fmap Blowfish128 . buildKey
+	keyLength = Tagged 128
diff --git a/Data/OpenPGP/CryptoAPI/Util.hs b/Data/OpenPGP/CryptoAPI/Util.hs
new file mode 100644
--- /dev/null
+++ b/Data/OpenPGP/CryptoAPI/Util.hs
@@ -0,0 +1,80 @@
+module Data.OpenPGP.CryptoAPI.Util where
+
+import Numeric
+import Control.Applicative
+import Data.Binary
+import Data.Binary.Get (runGetOrFail)
+import qualified Data.Serialize as Serialize
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Lazy as LZ
+import qualified Data.OpenPGP as OpenPGP
+
+swing :: (((a -> b) -> b) -> c -> d) -> c -> a -> d
+swing f c a = f ($ a) c
+
+hush :: Either a b -> Maybe b
+hush (Left _) = Nothing
+hush (Right x) = Just x
+
+toStrictBS :: LZ.ByteString -> BS.ByteString
+toStrictBS = BS.concat . LZ.toChunks
+
+toLazyBS :: BS.ByteString -> LZ.ByteString
+toLazyBS = LZ.fromChunks . (:[])
+
+oo :: (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c
+oo = (.) . (.)
+
+hexString :: [Word8] -> String
+hexString = foldr (pad `oo` showHex) ""
+	where
+	pad s | odd $ length s = '0':s
+	      | otherwise = s
+
+maybeDecode :: (Binary a) => LZ.ByteString -> Maybe a
+maybeDecode = maybeGet get
+
+maybeGet :: (Binary a) => Get a -> LZ.ByteString -> Maybe a
+maybeGet g bs = (\(_,_,x) -> x) <$> hush (runGetOrFail g bs)
+
+sDecode :: (Serialize.Serialize a) => BS.ByteString -> Maybe a
+sDecode = hush . Serialize.decode
+
+keyIdMatch :: String -> String -> Bool
+keyIdMatch x y = all (uncurry (==)) $ uncurry (flip zip) $
+	unzip $ zip (reverse x) (reverse y)
+
+fromJustMPI :: Maybe OpenPGP.MPI -> Integer
+fromJustMPI (Just (OpenPGP.MPI x)) = x
+fromJustMPI _ = error "Not a Just MPI, Data.OpenPGP.CryptoAPI"
+
+isSecretKey :: OpenPGP.Packet -> Bool
+isSecretKey (OpenPGP.SecretKeyPacket {}) = True
+isSecretKey                            _ = False
+
+isAsymmetricSessionKey :: OpenPGP.Packet -> Bool
+isAsymmetricSessionKey (OpenPGP.AsymmetricSessionKeyPacket {}) = True
+isAsymmetricSessionKey                                       _ = False
+
+isSymmetricSessionKey :: OpenPGP.Packet -> Bool
+isSymmetricSessionKey (OpenPGP.SymmetricSessionKeyPacket {}) = True
+isSymmetricSessionKey                                      _ = False
+
+isEncryptedData :: OpenPGP.Packet -> Bool
+isEncryptedData (OpenPGP.EncryptedDataPacket {}) = True
+isEncryptedData                                _ = False
+
+isUserID :: OpenPGP.Packet -> Bool
+isUserID (OpenPGP.UserIDPacket {})        = True
+isUserID _                                = False
+
+isSignable :: OpenPGP.Packet -> Bool
+isSignable (OpenPGP.LiteralDataPacket {}) = True
+isSignable (OpenPGP.PublicKeyPacket {})   = True
+isSignable (OpenPGP.SecretKeyPacket {})   = True
+isSignable _                              = False
+
+isKey :: OpenPGP.Packet -> Bool
+isKey (OpenPGP.SecretKeyPacket {}) = True
+isKey (OpenPGP.PublicKeyPacket {}) = True
+isKey                            _ = False
diff --git a/README b/README
--- a/README
+++ b/README
@@ -1,6 +1,7 @@
 This is a wrapper around <http://hackage.haskell.org/package/crypto-api>
 and related libraries that currently does fingerprint generation, signature
-generation, and signature verification (for RSA and DSA keys).
+generation, signature verification, and both asymmetric and symmetric
+encryption and decryption.
 
 It is indended to be used with <http://hackage.haskell.org/package/openpgp>
 
diff --git a/openpgp-crypto-api.cabal b/openpgp-crypto-api.cabal
--- a/openpgp-crypto-api.cabal
+++ b/openpgp-crypto-api.cabal
@@ -1,5 +1,5 @@
 name:            openpgp-crypto-api
-version:         0.4
+version:         0.6
 cabal-version:   >= 1.8
 license:         OtherLicense
 license-file:    COPYING
@@ -11,12 +11,13 @@
 tested-with:     GHC == 7.0.3
 synopsis:        Implement cryptography for OpenPGP using crypto-api compatible libraries
 homepage:        http://github.com/singpolyma/OpenPGP-CryptoAPI
-bug-reports:     http://github.com/singpolyma/OpenPGP-Haskell/issues
+bug-reports:     http://github.com/singpolyma/OpenPGP-CryptoAPI/issues
 build-type:      Simple
 description:
         This is a wrapper around <http://hackage.haskell.org/package/crypto-api>
         and related libraries that currently does fingerprint generation, signature
-        generation, and signature verification (for RSA and DSA keys).
+        generation, signature verification, and both asymmetric and symmetric
+        encryption and decryption.
         .
         It is indended to be used with <http://hackage.haskell.org/package/openpgp>
         .
@@ -31,25 +32,39 @@
         tests/data/000016-006.public_key,
         tests/data/000027-006.public_key,
         tests/data/000035-006.public_key,
-        tests/data/uncompressed-ops-dsa.gpg
-        tests/data/uncompressed-ops-dsa-sha384.txt.gpg
+        tests/data/compressedsig-bzip2.gpg,
+        tests/data/compressedsig.gpg,
+        tests/data/compressedsig-zlib.gpg,
+        tests/data/encryptedSecretKey.gpg,
+        tests/data/hello.gpg,
+        tests/data/helloKey.gpg,
+        tests/data/pubring.gpg,
+        tests/data/secring.gpg,
+        tests/data/symmetric-aes.gpg,
+        tests/data/symmetric-blowfish.gpg,
+        tests/data/symmetric-no-mdc.gpg,
+        tests/data/symmetric-with-session-key.gpg,
+        tests/data/uncompressed-ops-dsa.gpg,
+        tests/data/uncompressed-ops-dsa-sha384.txt.gpg,
         tests/data/uncompressed-ops-rsa.gpg
-        tests/data/compressedsig.gpg
-        tests/data/compressedsig-zlib.gpg
-        tests/data/compressedsig-bzip2.gpg
 
 library
         exposed-modules:
                 Data.OpenPGP.CryptoAPI
 
+        other-modules:
+                Data.OpenPGP.CryptoAPI.Util
+                Data.OpenPGP.CryptoAPI.Blowfish128
+
         build-depends:
                 base == 4.*,
+                transformers,
                 bytestring,
-                utf8-string,
-                binary,
-                openpgp,
-                crypto-api,
-                cryptocipher >= 0.3.7,
+                binary >= 0.6.4.0,
+                openpgp >= 0.6,
+                crypto-api >= 0.9,
+                tagged,
+                cryptocipher >= 0.3.6,
                 cryptohash,
                 cereal
 
@@ -59,15 +74,19 @@
 
         build-depends:
                 base == 4.*,
+                transformers,
                 bytestring,
+                binary >= 0.6.4.0,
                 utf8-string,
-                binary,
-                openpgp,
+                openpgp >= 0.6,
                 crypto-api >= 0.9,
-                cryptocipher >= 0.3.7,
+                tagged,
+                cryptocipher >= 0.3.6,
                 cryptohash,
+                cereal,
                 HUnit,
                 QuickCheck >= 2.4.1.1,
+                quickcheck-instances,
                 test-framework,
                 test-framework-hunit,
                 test-framework-quickcheck2
diff --git a/tests/data/encryptedSecretKey.gpg b/tests/data/encryptedSecretKey.gpg
new file mode 100644
Binary files /dev/null and b/tests/data/encryptedSecretKey.gpg differ
diff --git a/tests/data/hello.gpg b/tests/data/hello.gpg
new file mode 100644
Binary files /dev/null and b/tests/data/hello.gpg differ
diff --git a/tests/data/helloKey.gpg b/tests/data/helloKey.gpg
new file mode 100644
Binary files /dev/null and b/tests/data/helloKey.gpg differ
diff --git a/tests/data/pubring.gpg b/tests/data/pubring.gpg
new file mode 100644
Binary files /dev/null and b/tests/data/pubring.gpg differ
diff --git a/tests/data/secring.gpg b/tests/data/secring.gpg
new file mode 100644
Binary files /dev/null and b/tests/data/secring.gpg differ
diff --git a/tests/data/symmetric-aes.gpg b/tests/data/symmetric-aes.gpg
new file mode 100644
--- /dev/null
+++ b/tests/data/symmetric-aes.gpg
@@ -0,0 +1,1 @@
+	»FÝyDã`Ò>ì~iXaMé©MÂÛ7*W£ÛK1®IºÉ&¢·UÞ]-axósön´ìíÖì©ó§jUÄûÇ%ÀøÔ©eÚ
diff --git a/tests/data/symmetric-blowfish.gpg b/tests/data/symmetric-blowfish.gpg
new file mode 100644
--- /dev/null
+++ b/tests/data/symmetric-blowfish.gpg
@@ -0,0 +1,1 @@
+B|LÉ©<îâ`Ò2ÆïG5ðÌzì÷g¶çóý¬î%dõíú`~JXêMÂÄÄ¾mË¦4Ü
diff --git a/tests/data/symmetric-no-mdc.gpg b/tests/data/symmetric-no-mdc.gpg
new file mode 100644
--- /dev/null
+++ b/tests/data/symmetric-no-mdc.gpg
@@ -0,0 +1,2 @@
+[¨¡JH×BÉ`É#¥{Õ%íû|©6buâC»	gÝ+
+HÞÄè
diff --git a/tests/data/symmetric-with-session-key.gpg b/tests/data/symmetric-with-session-key.gpg
new file mode 100644
Binary files /dev/null and b/tests/data/symmetric-with-session-key.gpg differ
diff --git a/tests/suite.hs b/tests/suite.hs
--- a/tests/suite.hs
+++ b/tests/suite.hs
@@ -2,18 +2,31 @@
 import Test.Framework.Providers.HUnit
 import Test.Framework.Providers.QuickCheck2
 import Test.QuickCheck
+import Test.QuickCheck.Instances ()
 import Test.HUnit hiding (Test)
 
+import Data.Maybe
+import Data.Monoid
+import Data.List (find)
 import Crypto.Random
 import Data.Binary
 import qualified Data.OpenPGP as OpenPGP
 import qualified Data.OpenPGP.CryptoAPI as OpenPGP
 import qualified Data.ByteString.Lazy as LZ
-import qualified Data.ByteString.Lazy.UTF8 as LZ (fromString)
+import qualified Data.ByteString.Lazy.UTF8 as LZ (fromString, toString)
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.UTF8 as BS (fromString)
 
 instance Arbitrary OpenPGP.HashAlgorithm where
 	arbitrary = elements [OpenPGP.MD5, OpenPGP.SHA1, OpenPGP.RIPEMD160, OpenPGP.SHA256, OpenPGP.SHA384, OpenPGP.SHA512, OpenPGP.SHA224]
 
+instance Arbitrary OpenPGP.SymmetricAlgorithm where
+	arbitrary = elements [OpenPGP.AES128, OpenPGP.AES192, OpenPGP.AES256, OpenPGP.Blowfish]
+
+isLiteral :: OpenPGP.Packet -> Bool
+isLiteral (OpenPGP.LiteralDataPacket {}) = True
+isLiteral                              _ = False
+
 testFingerprint :: FilePath -> String -> Assertion
 testFingerprint fp kf = do
 	bs <- LZ.readFile $ "tests/data/" ++ fp
@@ -24,9 +37,41 @@
 testVerifyMessage keyring message = do
 	keys <- fmap decode $ LZ.readFile $ "tests/data/" ++ keyring
 	m <- fmap decode $ LZ.readFile $ "tests/data/" ++ message
-	let verification = OpenPGP.verify keys m 0
-	assertEqual (keyring ++ " for " ++ message) True verification
+	let OpenPGP.DataSignature _ ss =
+		OpenPGP.verify keys (head $ OpenPGP.signatures m)
+	assertEqual (keyring ++ " for " ++ message) 1 (length ss)
 
+testVerifyKey :: FilePath -> Int -> Assertion
+testVerifyKey keyring count = do
+	keys <- fmap decode $ LZ.readFile $ "tests/data/" ++ keyring
+	let out = OpenPGP.verify keys (OpenPGP.signatures keys !! 1)
+	assertEqual keyring count (length $ OpenPGP.signatures_over out)
+
+testDecryptHello :: Assertion
+testDecryptHello = do
+	keys <- fmap decode $ LZ.readFile "tests/data/helloKey.gpg"
+	m <- fmap decode $ LZ.readFile "tests/data/hello.gpg"
+	let Just (OpenPGP.Message [OpenPGP.CompressedDataPacket {
+			OpenPGP.message = OpenPGP.Message msg
+		}]) = OpenPGP.decryptAsymmetric keys m
+	let content = fmap (LZ.toString . OpenPGP.content) (find isLiteral msg)
+	assertEqual "Decrypt hello" (Just "hello\n") content
+
+testDecryptSymmetric :: String -> String -> FilePath -> Assertion
+testDecryptSymmetric pass cnt file = do
+	m <- fmap decode $ LZ.readFile $ "tests/data/" ++ file
+	let Just (OpenPGP.Message [OpenPGP.CompressedDataPacket {
+			OpenPGP.message = OpenPGP.Message msg
+		}]) = OpenPGP.decryptSymmetric [BS.fromString pass] m
+	let content = fmap (LZ.toString . OpenPGP.content) (find isLiteral msg)
+	assertEqual "Decrypt symmetric" (Just cnt) content
+
+testDecryptSecretKey :: String -> FilePath -> Assertion
+testDecryptSecretKey pass file = do
+	m <- fmap decode $ LZ.readFile $ "tests/data/" ++ file
+	let d = OpenPGP.decryptSecretKey (BS.fromString pass) m
+	assertEqual "Decrypt secret key" True (isJust d)
+
 prop_sign_and_verify :: (CryptoRandomGen g) => OpenPGP.Message -> g -> OpenPGP.HashAlgorithm -> String -> String -> Gen Bool
 prop_sign_and_verify secring g halgo filename msg = do
 	keyid <- elements ["FEF8AFA0F661C3EE","7F69FA376B020509"]
@@ -36,12 +81,29 @@
 			OpenPGP.timestamp = 12341234,
 			OpenPGP.content = LZ.fromString msg
 		}
-	let sig = OpenPGP.sign secring (OpenPGP.Message [m])
+	let (sig,_) = OpenPGP.sign secring (OpenPGP.DataSignature m [])
 			halgo keyid 12341234 g
-	return $ OpenPGP.verify secring (OpenPGP.Message [sig,m]) 0
+	let OpenPGP.DataSignature _ ss = OpenPGP.verify secring sig
+	return (length ss == 1)
 
-tests :: (CryptoRandomGen g) => OpenPGP.Message -> g -> [Test]
-tests secring rng =
+prop_encrypt_and_decrypt :: (CryptoRandomGen g) => OpenPGP.Message -> g -> BS.ByteString -> OpenPGP.SymmetricAlgorithm -> String -> String -> Bool
+prop_encrypt_and_decrypt secring g pass algo filename msg =
+	case (OpenPGP.encrypt [] secring algo m g, OpenPGP.encrypt [pass] mempty algo m g) of
+		(Left _, _) -> False
+		(_, Left _) -> False
+		(Right (encA, _), Right (encB, _)) ->
+			(OpenPGP.decryptAsymmetric secring encA == Just m) &&
+			(OpenPGP.decryptSymmetric [pass] encB == Just m)
+	where
+	m = OpenPGP.Message [OpenPGP.LiteralDataPacket {
+			OpenPGP.format = 'u',
+			OpenPGP.filename = filename,
+			OpenPGP.timestamp = 12341234,
+			OpenPGP.content = LZ.fromString msg
+		}]
+
+tests :: (CryptoRandomGen g) => OpenPGP.Message -> OpenPGP.Message -> g -> [Test]
+tests secring oneKey rng =
 	[
 		testGroup "Fingerprint" [
 			testCase "000001-006.public_key" (testFingerprint "000001-006.public_key" "421F28FEAAD222F856C8FFD5D4D54EA16F87040E"),
@@ -57,8 +119,22 @@
 			testCase "compressedsig-zlib" (testVerifyMessage "pubring.gpg" "compressedsig-zlib.gpg"),
 			testCase "compressedsig-bzip2" (testVerifyMessage "pubring.gpg" "compressedsig-bzip2.gpg")
 		],
+		testGroup "Key verification" [
+			testCase "helloKey" (testVerifyKey "helloKey.gpg" 1)
+		],
 		testGroup "Signing" [
-			testProperty "Crypto signatures verify" (prop_sign_and_verify secring rng)
+			testProperty "Signatures verify" (prop_sign_and_verify secring rng)
+		],
+		testGroup "Decryption" [
+			testCase "decrypt hello" testDecryptHello,
+			testCase "decrypt AES" (testDecryptSymmetric "hello" "PGP\n" "symmetric-aes.gpg"),
+			testCase "decrypt session key" (testDecryptSymmetric "hello" "PGP\n" "symmetric-with-session-key.gpg"),
+			testCase "decrypt Blowfish" (testDecryptSymmetric "hello" "PGP\n" "symmetric-blowfish.gpg"),
+			testCase "decrypt no MDC" (testDecryptSymmetric "hello" "PGP\n" "symmetric-no-mdc.gpg"),
+			testCase "decrypt secret key" (testDecryptSecretKey "hello" "encryptedSecretKey.gpg")
+		],
+		testGroup "Encryption" [
+			testProperty "Encrypted messages decrypt" (prop_encrypt_and_decrypt oneKey rng)
 		]
 	]
 
@@ -66,4 +142,5 @@
 main = do
 	rng <- newGenIO :: IO SystemRandom
 	secring <- fmap decode $ LZ.readFile "tests/data/secring.gpg"
-	defaultMain (tests secring rng)
+	oneKey <- fmap decode $ LZ.readFile "tests/data/helloKey.gpg"
+	defaultMain (tests secring oneKey rng)
