cryptonite/Crypto/PubKey/ElGamal.hs
Vincent Hanquez b1e222cf3d merge crypto-pubkey-types and crypto-pubkey
adapt CPRG to MonadRandom
2015-04-03 07:33:16 +01:00

144 lines
5.1 KiB
Haskell

-- |
-- Module : Crypto.PubKey.ElGamal
-- License : BSD-style
-- Maintainer : Vincent Hanquez <vincent@snarc.org>
-- Stability : experimental
-- Portability : Good
--
-- This module is a work in progress. do not use:
-- it might eat your dog, your data or even both.
--
-- TODO: provide a mapping between integer and ciphertext
-- generate numbers correctly
--
module Crypto.PubKey.ElGamal
( Params
, PublicNumber
, PrivateNumber
, EphemeralKey(..)
, SharedKey
, Signature
-- * generation
, generatePrivate
, generatePublic
-- * encryption and decryption with no scheme
, encryptWith
, encrypt
, decrypt
-- * signature primitives
, signWith
, sign
-- * verification primitives
, verify
) where
import Control.Applicative
import Data.Maybe (fromJust)
import Data.ByteString (ByteString)
import Crypto.Number.ModArithmetic (expSafe, expFast, inverse)
import Crypto.Number.Generate (generateMax)
import Crypto.Number.Serialize (os2ip)
import Crypto.Number.Basic (gcde_binary)
import Crypto.Random.Types
import Crypto.PubKey.HashDescr (HashFunction)
import Crypto.PubKey.DH (PrivateNumber(..), PublicNumber(..), Params(..), SharedKey(..))
-- | ElGamal Signature
data Signature = Signature (Integer, Integer)
-- | ElGamal Ephemeral key. also called Temporary key.
newtype EphemeralKey = EphemeralKey Integer
-- | generate a private number with no specific property
-- this number is usually called a and need to be between
-- 0 and q (order of the group G).
--
generatePrivate :: MonadRandom m => Integer -> m PrivateNumber
generatePrivate q = PrivateNumber <$> generateMax q
-- | generate an ephemeral key which is a number with no specific property,
-- and need to be between 0 and q (order of the group G).
--
generateEphemeral :: MonadRandom m => Integer -> m EphemeralKey
generateEphemeral q = toEphemeral <$> generatePrivate q
where toEphemeral (PrivateNumber n) = EphemeralKey n
-- | generate a public number that is for the other party benefits.
-- this number is usually called h=g^a
generatePublic :: Params -> PrivateNumber -> PublicNumber
generatePublic (Params p g) (PrivateNumber a) = PublicNumber $ expSafe g a p
-- | encrypt with a specified ephemeral key
-- do not reuse ephemeral key.
encryptWith :: EphemeralKey -> Params -> PublicNumber -> Integer -> (Integer,Integer)
encryptWith (EphemeralKey b) (Params p g) (PublicNumber h) m = (c1,c2)
where s = expSafe h b p
c1 = expSafe g b p
c2 = (s * m) `mod` p
-- | encrypt a message using params and public keys
-- will generate b (called the ephemeral key)
encrypt :: MonadRandom m => Params -> PublicNumber -> Integer -> m (Integer,Integer)
encrypt params@(Params p _) public m = (\b -> encryptWith b params public m) <$> generateEphemeral q
where q = p-1 -- p is prime, hence order of the group is p-1
-- | decrypt message
decrypt :: Params -> PrivateNumber -> (Integer, Integer) -> Integer
decrypt (Params p _) (PrivateNumber a) (c1,c2) = (c2 * sm1) `mod` p
where s = expSafe c1 a p
sm1 = fromJust $ inverse s p -- always inversible in Zp
-- | sign a message with an explicit k number
--
-- if k is not appropriate, then no signature is returned.
--
-- with some appropriate value of k, the signature generation can fail,
-- and no signature is returned. User of this function need to retry
-- with a different k value.
signWith :: Integer -- ^ random number k, between 0 and p-1 and gcd(k,p-1)=1
-> Params -- ^ DH params (p,g)
-> PrivateNumber -- ^ DH private key
-> HashFunction -- ^ collision resistant hash function
-> ByteString -- ^ message to sign
-> Maybe Signature
signWith k (Params p g) (PrivateNumber x) hashF msg
| k >= p-1 || d > 1 = Nothing -- gcd(k,p-1) is not 1
| s == 0 = Nothing
| otherwise = Just $ Signature (r,s)
where r = expSafe g k p
h = os2ip $ hashF msg
s = ((h - x*r) * kInv) `mod` (p-1)
(kInv,_,d) = gcde_binary k (p-1)
-- | sign message
--
-- This function will generate a random number, however
-- as the signature might fail, the function will automatically retry
-- until a proper signature has been created.
--
sign :: MonadRandom m
=> Params -- ^ DH params (p,g)
-> PrivateNumber -- ^ DH private key
-> HashFunction -- ^ collision resistant hash function
-> ByteString -- ^ message to sign
-> m Signature
sign params@(Params p _) priv hashF msg = do
k <- generateMax (p-1)
case signWith k params priv hashF msg of
Nothing -> sign params priv hashF msg
Just sig -> return sig
-- | verify a signature
verify :: Params
-> PublicNumber
-> HashFunction
-> ByteString
-> Signature
-> Bool
verify (Params p g) (PublicNumber y) hashF msg (Signature (r,s))
| or [r <= 0,r >= p,s <= 0,s >= (p-1)] = False
| otherwise = lhs == rhs
where h = os2ip $ hashF msg
lhs = expFast g h p
rhs = (expFast y r p * expFast r s p) `mod` p