packages feed

cryptocipher 0.1 → 0.2

raw patch · 4 files changed

+329/−38 lines, 4 filesdep +QuickCheckdep +crypto-apidep −haskell98dep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: QuickCheck, crypto-api

Dependencies removed: haskell98

Dependency ranges changed: base

API changes (from Hackage documentation)

+ Crypto.Cipher.RSA: KeyInternalError :: Error
+ Crypto.Cipher.RSA: MessageNotRecognized :: Error
+ Crypto.Cipher.RSA: MessageSizeIncorrect :: Error
+ Crypto.Cipher.RSA: MessageTooLong :: Error
+ Crypto.Cipher.RSA: PrivateKey :: Int -> Integer -> Integer -> Integer -> Integer -> Integer -> Integer -> Integer -> PrivateKey
+ Crypto.Cipher.RSA: PublicKey :: Int -> Integer -> Integer -> PublicKey
+ Crypto.Cipher.RSA: RandomGenFailure :: GenError -> Error
+ Crypto.Cipher.RSA: data Error
+ Crypto.Cipher.RSA: data PrivateKey
+ Crypto.Cipher.RSA: data PublicKey
+ Crypto.Cipher.RSA: decrypt :: PrivateKey -> ByteString -> Either Error ByteString
+ Crypto.Cipher.RSA: encrypt :: CryptoRandomGen g => g -> PublicKey -> ByteString -> Either Error (ByteString, g)
+ Crypto.Cipher.RSA: instance Eq Error
+ Crypto.Cipher.RSA: instance Show Error
+ Crypto.Cipher.RSA: instance Show PrivateKey
+ Crypto.Cipher.RSA: instance Show PublicKey
+ Crypto.Cipher.RSA: private_d :: PrivateKey -> Integer
+ Crypto.Cipher.RSA: private_dP :: PrivateKey -> Integer
+ Crypto.Cipher.RSA: private_dQ :: PrivateKey -> Integer
+ Crypto.Cipher.RSA: private_n :: PrivateKey -> Integer
+ Crypto.Cipher.RSA: private_p :: PrivateKey -> Integer
+ Crypto.Cipher.RSA: private_q :: PrivateKey -> Integer
+ Crypto.Cipher.RSA: private_qinv :: PrivateKey -> Integer
+ Crypto.Cipher.RSA: private_sz :: PrivateKey -> Int
+ Crypto.Cipher.RSA: public_e :: PublicKey -> Integer
+ Crypto.Cipher.RSA: public_n :: PublicKey -> Integer
+ Crypto.Cipher.RSA: public_sz :: PublicKey -> Int

Files

+ Crypto/Cipher/RSA.hs view
@@ -0,0 +1,140 @@+{-# LANGUAGE FlexibleInstances, CPP #-}++-- |+-- Module      : Crypto.Cipher.RSA+-- License     : BSD-style+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>+-- Stability   : experimental+-- Portability : Good+--+module Crypto.Cipher.RSA+	( Error(..)+	, PublicKey(..)+	, PrivateKey(..)+	, decrypt+	, encrypt+	) where++import Control.Arrow (first)+import Crypto.Random+import Data.Bits+import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import Number.ModArithmetic (exponantiation_rtl_binary)++data Error =+	  MessageSizeIncorrect      -- ^ the message to decrypt is not of the correct size (need to be == private_size)+	| MessageTooLong            -- ^ the message to encrypt is too long (>= private_size - 11)+	| MessageNotRecognized      -- ^ the message decrypted doesn't have a PKCS15 structure (0 2 .. 0 msg)+	| RandomGenFailure GenError -- ^ the random generator returns an error. give the opportunity to reseed for example.+	| KeyInternalError          -- ^ the whole key is probably not valid, since the message is bigger than the key size+	deriving (Show,Eq)++data PublicKey = PublicKey+	{ public_sz :: Int      -- ^ size of key in bytes+	, public_n  :: Integer  -- ^ public p*q+	, public_e  :: Integer  -- ^ public exponant e+	} deriving (Show)++data PrivateKey = PrivateKey+	{ private_sz   :: Int     -- ^ size of key in bytes+	, private_n    :: Integer -- ^ private p*q+	, private_d    :: Integer -- ^ private exponant d+	, private_p    :: Integer -- ^ p prime number+	, private_q    :: Integer -- ^ q prime number+	, private_dP   :: Integer -- ^ d mod (p-1)+	, private_dQ   :: Integer -- ^ d mod (q-1)+	, private_qinv :: Integer -- ^ q^(-1) mod p+	} deriving (Show)++#if !MIN_VERSION_base(4,3,0)+instance Monad (Either Error) where+	return          = Right+	(Left x) >>= _  = Left x+	(Right x) >>= f = f x+#endif++padPKCS1 :: CryptoRandomGen g => g -> Int -> ByteString -> Either Error (ByteString, g)+padPKCS1 rng len m = do+	(padding, rng') <- getRandomBytes rng (len - B.length m - 3)+	return (B.concat [ B.singleton 0, B.singleton 2, padding, B.singleton 0, m ], rng')++unpadPKCS1 :: ByteString -> Either Error ByteString+unpadPKCS1 packed+	| signal_error = Left MessageNotRecognized+	| otherwise    = Right m+	where+		(zt, ps0m)   = B.splitAt 2 packed+		(ps, zm)     = B.span (/= 0) ps0m+		(z, m)       = B.splitAt 1 zm+		signal_error = (B.unpack zt /= [0, 2]) || (B.unpack z /= [0]) || (B.length ps < 8)++{- dpSlow computes the decrypted message not using any precomputed cache value.+   only n and d need to valid. -}+dpSlow :: PrivateKey -> ByteString -> Either Error ByteString+dpSlow pk c = i2ospOf (private_sz pk) $ expmod (os2ip c) (private_d pk) (private_n pk)++{- dpFast computes the decrypted message more efficiently if the+   precomputed private values are available. mod p and mod q are faster+   to compute than mod pq -}+dpFast :: PrivateKey -> ByteString -> Either Error ByteString+dpFast pk c = i2ospOf (private_sz pk) (m2 + h * (private_q pk))+	where+		iC = os2ip c+		m1 = expmod iC (private_dP pk) (private_p pk)+		m2 = expmod iC (private_dQ pk) (private_q pk)+		h  = ((private_qinv pk) * (m1 - m2)) `mod` (private_p pk)++{-| decrypt message using the private key. -}+decrypt :: PrivateKey -> ByteString -> Either Error ByteString+decrypt pk c+	| B.length c /= (private_sz pk) = Left MessageSizeIncorrect+	| otherwise                     = dp pk c >>= unpadPKCS1+		where dp = if private_p pk /= 0 && private_q pk /= 0 then dpFast else dpSlow++{- | encrypt a bytestring using the public key and a CryptoRandomGen random generator.+ - the message need to be smaller than the key size - 11+ -}+encrypt :: CryptoRandomGen g => g -> PublicKey -> ByteString -> Either Error (ByteString, g)+encrypt rng pk m+	| B.length m > public_sz pk - 11 = Left MessageTooLong+	| otherwise                      = do+		(em, rng') <- padPKCS1 rng (public_sz pk) m+		c          <- i2ospOf (public_sz pk) $ expmod (os2ip em) (public_e pk) (public_n pk)+		return (c, rng')++{- get random non-null bytes for encryption padding. -}+getRandomBytes :: CryptoRandomGen g => g -> Int -> Either Error (ByteString, g)+getRandomBytes rng n = do+	gend <- either (Left . RandomGenFailure) Right $ genBytes n rng+	let (bytes, rng') = first (B.pack . filter (/= 0) . B.unpack) gend+	let left          = (n - B.length bytes)+	if left == 0+		then return (bytes, rng')+		else getRandomBytes rng' left >>= return . first (B.append bytes)++{- convert a positive integer into a bytestring of specific size.+   if the number is too big, this will returns an error, otherwise it will pad+   the bytestring of 0 -}+i2ospOf :: Int -> Integer -> Either Error ByteString+i2ospOf len m +	| lenbytes < len  = Right $ B.replicate (len - lenbytes) 0 `B.append` bytes+	| lenbytes == len = Right bytes+	| otherwise       = Left KeyInternalError+	where+		lenbytes = B.length bytes+		bytes    = i2osp m++-- | os2ip converts a byte string into a positive integer+os2ip :: ByteString -> Integer+os2ip = B.foldl' (\a b -> (256 * a) .|. (fromIntegral b)) 0++-- | i2osp converts a positive integer into a byte string+i2osp :: Integer -> ByteString+i2osp m = B.reverse $ B.unfoldr divMod256 m+	where+		divMod256 0 = Nothing+		divMod256 n = Just (fromIntegral a,b) where (b,a) = n `divMod` 256++expmod :: Integer -> Integer -> Integer -> Integer+expmod = exponantiation_rtl_binary
+ Number/ModArithmetic.hs view
@@ -0,0 +1,57 @@+{-# LANGUAGE BangPatterns #-}+module Number.ModArithmetic+	( exponantiation_rtl_binary+	, inverse+	, gcde_binary+	) where++import Data.Bits++-- note on exponantiation: 0^0 is treated as 1 for mimicking the standard library;+-- the mathematic debate is still open on whether or not this is true, but pratically+-- in computer science it shouldn't be useful for anything anyway.++-- | exponantiation_rtl_binary computes modular exponantiation as b^e mod m+-- using the right-to-left binary exponentiation algorithm (HAC 14.79)+exponantiation_rtl_binary :: Integer -> Integer -> Integer -> Integer+exponantiation_rtl_binary 0 0 m = 1 `mod` m+exponantiation_rtl_binary b e m = loop e b 1+	where+		sq x          = (x * x) `mod` m+		loop !0 _  !a = a `mod` m+		loop !i !s !a = loop (i `shiftR` 1) (sq s) (if odd i then a * s else a)++-- | inverse computes the modular inverse as in g^(-1) mod m+inverse :: Integer -> Integer -> Maybe Integer+inverse g m = if d > 1 then Nothing else Just x+	where (x,_,d) = gcde_binary g m++-- | get the extended GCD of two integer using the extended binary algorithm (HAC 14.61)+-- get (x,y,d) where d = gcd(a,b) and x,y satisfying ax + by = d+gcde_binary :: Integer -> Integer -> (Integer, Integer, Integer)+gcde_binary a' b'+	| b' == 0   = (1,0,a')+	| a' >= b'  = compute a' b'+	| otherwise = (\(x,y,d) -> (y,x,d)) $ compute b' a'+	where+		getEvenMultiplier !g !x !y+			| areEven [x,y] = getEvenMultiplier (g `shiftL` 1) (x `shiftR` 1) (y `shiftR` 1)+			| otherwise     = (x,y,g)+		halfLoop !x !y !u !i !j+			| areEven [u,i,j] = halfLoop x y (u `shiftR` 1) (i `shiftR` 1) (j `shiftR` 1)+			| even u          = halfLoop x y (u `shiftR` 1) ((i + y) `shiftR` 1) ((j - x) `shiftR` 1)+			| otherwise       = (u, i, j)+		compute a b =+			let (x,y,g) = getEvenMultiplier 1 a b in+			loop g x y x y 1 0 0 1++		loop g _ _ 0  !v _  _  !c !d = (c, d, g * v)+		loop g x y !u !v !a !b !c !d =+			let (u2,a2,b2) = halfLoop x y u a b in+			let (v2,c2,d2) = halfLoop x y v c d in+			if u2 >= v2+				then loop g x y (u2 - v2) v2 (a2 - c2) (b2 - d2) c2 d2+				else loop g x y u2 (v2 - u2) a2 b2 (c2 - a2) (d2 - b2)++areEven :: [Integer] -> Bool+areEven = and . map even
Tests.hs view
@@ -1,33 +1,28 @@+{-# LANGUAGE OverloadedStrings #-}+ import Test.HUnit ((~:), (~=?)) import qualified Test.HUnit as Unit++import Test.QuickCheck+import Test.QuickCheck.Test+import System.IO (hFlush, stdout)++import Control.Monad++import Data.List (intercalate) import Data.Char import Data.Bits import Data.Word import qualified Data.ByteString as B+import qualified Data.ByteString.Char8 as BC++-- numbers+import Number.ModArithmetic+-- ciphers import qualified Crypto.Cipher.RC4 as RC4 import qualified Crypto.Cipher.Camellia as Camellia--{- CAMELLIA test vectors -}-{--   Here are test data for Camellia in hexadecimal form.--   128-bit key-       Key       : 01 23 45 67 89 ab cd ef fe dc ba 98 76 54 32 10-       Plaintext : 01 23 45 67 89 ab cd ef fe dc ba 98 76 54 32 10-       Ciphertext: 67 67 31 38 54 96 69 73 08 57 06 56 48 ea be 43--   192-bit key-       Key       : 01 23 45 67 89 ab cd ef fe dc ba 98 76 54 32 10-                 : 00 11 22 33 44 55 66 77-       Plaintext : 01 23 45 67 89 ab cd ef fe dc ba 98 76 54 32 10-       Ciphertext: b4 99 34 01 b3 e9 96 f8 4e e5 ce e7 d7 9b 09 b9--   256-bit key-       Key       : 01 23 45 67 89 ab cd ef fe dc ba 98 76 54 32 10-                 : 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff-       Plaintext : 01 23 45 67 89 ab cd ef fe dc ba 98 76 54 32 10-       Ciphertext: 9a cc 23 7d ff 16 d7 6c 20 ef 7c 91 9e 3a 75 09--}+import qualified Crypto.Cipher.RSA as RSA+import Crypto.Random  encryptStream fi fc key plaintext = B.unpack $ snd $ fc (fi key) plaintext @@ -40,25 +35,41 @@ wordify :: [Char] -> [Word8] wordify = map (toEnum . fromEnum) -packString :: [Char] -> B.ByteString-packString = B.pack . wordify- vectors_rc4 =-	[ (wordify "Key", packString "Plaintext", [ 0xBB,0xF3,0x16,0xE8,0xD9,0x40,0xAF,0x0A,0xD3 ])-	, (wordify "Wiki", packString "pedia", [ 0x10,0x21,0xBF,0x04,0x20 ])-	, (wordify "Secret", packString "Attack at dawn", [ 0x45,0xA0,0x1F,0x64,0x5F,0xC3,0x5B,0x38,0x35,0x52,0x54,0x4B,0x9B,0xF5 ])+	[ (wordify "Key", "Plaintext", [ 0xBB,0xF3,0x16,0xE8,0xD9,0x40,0xAF,0x0A,0xD3 ])+	, (wordify "Wiki", "pedia", [ 0x10,0x21,0xBF,0x04,0x20 ])+	, (wordify "Secret", "Attack at dawn", [ 0x45,0xA0,0x1F,0x64,0x5F,0xC3,0x5B,0x38,0x35,0x52,0x54,0x4B,0x9B,0xF5 ]) 	]  vectors_camellia128 = 	[ -	  ([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],-	   B.pack [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],-	   [0x3d,0x02,0x80,0x25,0xb1,0x56,0x32,0x7c,0x17,0xf7,0x62,0xc1,0xf2,0xcb,0xca,0x71]),-	  ([0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10],-	   B.pack [0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10],-           [0x67,0x67,0x31,0x38,0x54,0x96,0x69,0x73,0x08,0x57,0x06,0x56,0x48,0xea,0xbe,0x43])+	  ( [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]+	  , B.pack [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]+	  , [0x3d,0x02,0x80,0x25,0xb1,0x56,0x32,0x7c,0x17,0xf7,0x62,0xc1,0xf2,0xcb,0xca,0x71]+	  )+	, ( [0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10]+	  , B.pack [0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10]+	  , [0x67,0x67,0x31,0x38,0x54,0x96,0x69,0x73,0x08,0x57,0x06,0x56,0x48,0xea,0xbe,0x43]+	  ) 	] +vectors_camellia192 =+	[+	  ( [0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10,0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77]+	  , B.pack [0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10]+	  ,[0xb4,0x99,0x34,0x01,0xb3,0xe9,0x96,0xf8,0x4e,0xe5,0xce,0xe7,0xd7,0x9b,0x09,0xb9]+	  )+	]++vectors_camellia256 =+	[+	  ( [0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10+	    ,0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xaa,0xbb,0xcc,0xdd,0xee,0xff]+	  , B.pack [0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10]+	  , [0x9a,0xcc,0x23,0x7d,0xff,0x16,0xd7,0x6c,0x20,0xef,0x7c,0x91,0x9e,0x3a,0x75,0x09]+	  )+	]+ vectors = 	[ ("RC4",      vectors_rc4,         encryptStream RC4.initCtx RC4.encrypt) 	, ("Camellia", vectors_camellia128, encryptBlock Camellia.initKey Camellia.encrypt)@@ -67,4 +78,84 @@ utests :: [Unit.Test] utests = concatMap (\(name, v, f) -> map (\(k,p,e) -> name ~: name ~: e ~=? f k p) v) vectors -main = Unit.runTestTT (Unit.TestList utests)+{- end of units tests -}+{- start of QuickCheck verification -}++-- FIXME better to tweak the property to generate positive integer instead of this.++prop_gcde_binary_valid (a, b)+	| a > 0 && b >= 0 =+		let (x,y,v) = gcde_binary a b in+		and [a*x + b*y == v, gcd a b == v]+	| otherwise          = True++prop_modexp_rtl_valid (a, b, m)+	| m > 0 && a >= 0 && b >= 0 = exponantiation_rtl_binary a b m == ((a ^ b) `mod` m)+	| otherwise                 = True++prop_modinv_valid (a, m)+	| m > 1 && a > 0 =+		case inverse a m of+			Just ainv -> (ainv * a) `mod` m == 1+			Nothing   -> True+	| otherwise       = True++newtype RSAMessage = RSAMessage B.ByteString deriving (Show, Eq)++instance Arbitrary RSAMessage where+	arbitrary = do+		sz <- choose (0, 128 - 11)+		ws <- replicateM sz (choose (0,255) :: Gen Int)+		return $ RSAMessage $ B.pack $ map fromIntegral ws++data Rng = Rng Int++instance CryptoRandomGen Rng where+	newGen _       = Right (Rng 0)+	genSeedLength  = 0+	genBytes len g = Right (B.pack $ replicate len 0x2d, g)++rng = Rng 0++prop_rsa_fast_valid (RSAMessage msg) =+	(either Left (RSA.decrypt privatekey . fst) $ RSA.encrypt rng publickey msg) == Right msg++prop_rsa_slow_valid (RSAMessage msg) =+	(either Left (RSA.decrypt pk . fst) $ RSA.encrypt rng publickey msg) == Right msg+	where pk = privatekey { RSA.private_p = 0, RSA.private_q = 0 }++privatekey = RSA.PrivateKey+	{ RSA.private_sz   = 128+	, RSA.private_n    = 140203425894164333410594309212077886844966070748523642084363106504571537866632850620326769291612455847330220940078873180639537021888802572151020701352955762744921926221566899281852945861389488419179600933178716009889963150132778947506523961974222282461654256451508762805133855866018054403911588630700228345151+	, RSA.private_d    = 133764127300370985476360382258931504810339098611363623122953018301285450176037234703101635770582297431466449863745848961134143024057267778947569638425565153896020107107895924597628599677345887446144410702679470631826418774397895304952287674790343620803686034122942606764275835668353720152078674967983573326257+	, RSA.private_p    = 12909745499610419492560645699977670082358944785082915010582495768046269235061708286800087976003942261296869875915181420265794156699308840835123749375331319+	, RSA.private_q    = 10860278066550210927914375228722265675263011756304443428318337179619069537063135098400347475029673115805419186390580990519363257108008103841271008948795129+	, RSA.private_dP   = 5014229697614831746694710412330921341325464081424013940131184365711243776469716106024020620858146547161326009604054855316321928968077674343623831428796843+	, RSA.private_dQ   = 3095337504083058271243917403868092841421453478127022884745383831699720766632624326762288333095492075165622853999872779070009098364595318242383709601515849+	, RSA.private_qinv = 11136639099661288633118187183300604127717437440459572124866697429021958115062007251843236337586667012492941414990095176435990146486852255802952814505784196+	}++publickey = RSA.PublicKey+	{ RSA.public_sz = 128+	, RSA.public_n  = 140203425894164333410594309212077886844966070748523642084363106504571537866632850620326769291612455847330220940078873180639537021888802572151020701352955762744921926221566899281852945861389488419179600933178716009889963150132778947506523961974222282461654256451508762805133855866018054403911588630700228345151+	, RSA.public_e  = 65537+	}++args = Args+	{ replay     = Nothing+	, maxSuccess = 1000+	, maxDiscard = 4000+	, maxSize    = 1000+	}++run_test n t = putStr ("  " ++ n ++ " ... ") >> hFlush stdout >> quickCheckWith args t++main = do+	Unit.runTestTT (Unit.TestList utests)++	run_test "gcde binary valid" prop_gcde_binary_valid+	run_test "exponantiation RTL valid" prop_modexp_rtl_valid+	run_test "inverse valid" prop_modinv_valid++	run_test "RSA decrypt(slow).encrypt = id" prop_rsa_slow_valid+	run_test "RSA decrypt(fast).encrypt = id" prop_rsa_fast_valid
cryptocipher.cabal view
@@ -1,5 +1,5 @@ Name:                cryptocipher-Version:             0.1+Version:             0.2 Description:         Symmetrical Block and Stream Ciphers License:             BSD3 License-file:        LICENSE@@ -9,6 +9,7 @@ Synopsis:            Symmetrical Block and Stream Ciphers Category:            Cryptography Build-Type:          Simple+Homepage:            http://github.com/vincenthz/hs-cryptocipher Cabal-Version:       >=1.6  Flag test@@ -16,16 +17,18 @@   Default:           False  Library-  Build-Depends:     base >= 3 && < 5, bytestring, haskell98, vector+  Build-Depends:     base >= 4 && < 7, bytestring, vector, crypto-api >= 0.2   Exposed-modules:   Crypto.Cipher.RC4                      Crypto.Cipher.Camellia+                     Crypto.Cipher.RSA+  other-modules:     Number.ModArithmetic   ghc-options:       -Wall  Executable           Tests   Main-Is:           Tests.hs   if flag(test)     Buildable:       True-    Build-depends:   base >= 3 && < 5, HUnit, bytestring+    Build-depends:   base >= 4 && < 7, HUnit, bytestring, QuickCheck >= 2   else     Buildable:       False