packages feed

crypton-2.0.0: Crypto/PubKey/ElGamal.hs

{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

-- |
-- Module      : Crypto.PubKey.ElGamal
-- License     : BSD-style
-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
-- Stability   : experimental
-- Portability : Good
--
-- ElGamal encryption and signature over the multiplicative group of integers
-- modulo a prime, reusing the parameters of "Crypto.PubKey.DH".
--
-- /These are raw primitives, not a scheme./  The encryption here is textbook
-- ElGamal: it applies no padding, so it is malleable by construction --
-- multiplying a ciphertext's second component by @t@ multiplies the plaintext
-- by @t@ -- and it is not IND-CCA secure.  A message is an 'Integer' below the
-- modulus rather than a byte string, and nothing here maps one to the other.
-- Use it to build a scheme that adds those, or prefer
-- "Crypto.PubKey.RSA.OAEP" or "Crypto.PubKey.ECIES" where a scheme is what is
-- wanted.
--
-- The signature primitive is likewise raw, and an ephemeral value must never
-- be reused between signatures: two signatures under the same @k@ reveal the
-- private key.
--
-- == What is kept from the clock, and what is not
--
-- Every exponentiation with a secret exponent is
-- 'Crypto.Number.ModArithmetic.expSafe'.  Decryption inverts the shared
-- secret by Fermat's little theorem rather than by the extended Euclidean
-- algorithm, whose steps follow the bits it is given.  'sign' cannot do that
-- -- @k@ is inverted modulo @p-1@, which is even -- so it blinds instead: the
-- algorithm is handed @k@ times a fresh random unit, and the blinder is
-- divided out afterwards.  'signWith', having no randomness of its own, hands
-- it @k@.
--
-- What is left is the 'Integer' arithmetic around all of that, whose cost
-- follows the size of the numbers.  See "Crypto.PubKey.DSA" for the same note
-- at more length.
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 Crypto.Error
import Crypto.Hash
import Crypto.Internal.ByteArray (ByteArrayAccess)
import Crypto.Internal.Imports
import Crypto.Number.Basic (gcde)
import Crypto.Number.Generate (generateBetween, generateMax)
import Crypto.Number.ModArithmetic (expFast, expSafe, inverseSafe)
import Crypto.Number.Serialize (os2ip)
import Crypto.PubKey.DH (
    Params (..),
    PrivateNumber (..),
    PublicNumber (..),
    SharedKey (..),
 )
import Crypto.Random.Types
import Data.Data

-- | ElGamal Signature
data Signature = Signature
    { sign_r :: Integer
    -- ^ ElGamal r
    , sign_s :: Integer
    -- ^ ElGamal s
    }
    deriving (Show, Read, Eq, Data)

instance NFData Signature where
    rnf (Signature r s) = r `seq` s `seq` ()

-- | ElGamal Ephemeral key. also called Temporary key.
newtype EphemeralKey = EphemeralKey Integer
    deriving (NFData)

-- | generate a private number, in @[1, q-1]@ where @q@ is the order of the
-- group.  Zero is excluded: it would make the public number 1 and the shared
-- value constant.
generatePrivate :: MonadRandom m => Integer -> m PrivateNumber
generatePrivate q = PrivateNumber <$> generateBetween 1 (q - 1)

-- | 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

-- | Is the other party's public number usable?
--
-- @1@ and @p-1@ generate a group of one or two elements, so the value they
-- mask the message with is one of a handful of constants.
validPublic :: Integer -> Integer -> Bool
validPublic p h = h > 1 && h < p - 1

-- | encrypt with a specified ephemeral key
--
-- The ephemeral key must lie in @[1, p-2]@ and must never be reused: zero
-- would leave the message unmasked, and a repeat lets anyone who learns one
-- plaintext recover the other.  A message must be below the modulus, or
-- decryption would return it reduced.
encryptWith
    :: EphemeralKey
    -> Params
    -> PublicNumber
    -> Integer
    -> CryptoFailable (Integer, Integer)
encryptWith (EphemeralKey b) (Params p g _) (PublicNumber h) m
    | b < 1 || b > p - 2 = CryptoFailed CryptoError_ParameterInvalid
    | not (validPublic p h) = CryptoFailed CryptoError_ParameterInvalid
    | m < 0 || m >= p = CryptoFailed CryptoError_ParameterInvalid
    | otherwise = CryptoPassed (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 (CryptoFailable (Integer, Integer))
encrypt params@(Params p _ _) public m
    | p < 5 = return (CryptoFailed CryptoError_ParameterInvalid)
    | otherwise = do
        b <- generateBetween 1 (p - 2)
        return $ encryptWith (EphemeralKey b) params public m

-- | decrypt message
--
-- @c1@ must be a unit modulo @p@; a ciphertext whose first component is zero
-- or out of range is rejected rather than raising.
decrypt
    :: Params -> PrivateNumber -> (Integer, Integer) -> CryptoFailable Integer
decrypt (Params p _ _) (PrivateNumber a) (c1, c2)
    | c1 <= 0 || c1 >= p = CryptoFailed CryptoError_ParameterInvalid
    | c2 < 0 || c2 >= p = CryptoFailed CryptoError_ParameterInvalid
    | otherwise = case inverseSafe s p of
        Nothing -> CryptoFailed CryptoError_ParameterInvalid
        Just sm1 -> CryptoPassed ((c2 * sm1) `mod` p)
  where
    -- the shared secret, which the extended Euclidean algorithm would take
    -- apart: its steps follow the bits of what it is given, and this one is
    -- worth the private number.  p is prime, so Fermat gives the inverse
    -- without reading it
    s = expSafe c1 a p

-- | sign a message with an explicit ephemeral value
--
-- @k@ has to lie in @[1, p-2]@ and be coprime with @p-1@.  'Nothing' says the
-- value handed in cannot be used: either it fails one of those two conditions,
-- or it is one of the few that produce a second component of zero.  Either way
-- the answer is to draw another @k@, which is what 'sign' does.
--
-- @k@ is an ephemeral private key.  It has to be drawn uniformly at random,
-- kept secret, and used for one signature only: the private number follows
-- from a signature and its @k@, and equally from two signatures made with the
-- same @k@.  None of that is visible to this function, which is why it takes
-- @k@ from the caller and checks only what it can.
signWith
    :: (ByteArrayAccess msg, HashAlgorithm hash)
    => Integer
    -- ^ ephemeral value k, in [1, p-2] and coprime with p-1
    -> Params
    -- ^ DH params (p,g)
    -> PrivateNumber
    -- ^ DH private key
    -> hash
    -- ^ collision resistant hash algorithm
    -> msg
    -- ^ message to sign
    -> Maybe Signature
signWith = signWithBlinder 1

-- | The same with a blinder for the inversion of @k@.
--
-- @k@ is inverted modulo @p-1@, which is even, so Fermat's little theorem
-- does not reach it the way it reaches DSA's @k@ modulo a prime order: the
-- extended Euclidean algorithm is the only way there, and its steps follow
-- the bits of what it is given.  What can be done instead is to hand it
-- something else: for a unit @b@, the inverse of @k*b@ times @b@ is the
-- inverse of @k@, and the steps then follow @k*b@, which is a fresh random
-- number.  A blinder of 1 is no blinding, which is what the exported
-- 'signWith' has to do, having no randomness of its own.
--
-- When @b@ shares a factor with @p-1@ the algorithm reports it the same way
-- it reports one in @k@, and the answer is the same: draw again.
signWithBlinder
    :: (ByteArrayAccess msg, HashAlgorithm hash)
    => Integer -> Integer -> Params -> PrivateNumber -> hash -> msg -> Maybe Signature
signWithBlinder b k (Params p g _) (PrivateNumber x) hashAlg msg
    | k <= 0 || k >= p - 1 || b <= 0 || d > 1 = Nothing
    | s == 0 = Nothing
    | otherwise = Just $ Signature r s
  where
    r = expSafe g k p
    h = os2ip $ hashWith hashAlg msg
    s = ((h - x * r) * kInv) `mod` (p - 1)
    kInv = (kbInv * b) `mod` (p - 1)
    (kbInv, _, d) = gcde ((k * b) `mod` (p - 1)) (p - 1)

-- | sign message
--
-- This function draws the ephemeral value itself, and draws a fresh one on
-- each attempt until 'signWith' accepts it, so a caller who has no particular
-- @k@ in mind should use this rather than 'signWith'.
sign
    :: (ByteArrayAccess msg, HashAlgorithm hash, MonadRandom m)
    => Params
    -- ^ DH params (p,g)
    -> PrivateNumber
    -- ^ DH private key
    -> hash
    -- ^ collision resistant hash algorithm
    -> msg
    -- ^ message to sign
    -> m Signature
sign params@(Params p _ _) priv hashAlg msg = do
    k <- generateMax (p - 1)
    -- and a blinder for the inversion of k, which is the one step here that
    -- the extended Euclidean algorithm has to do
    b <- generateMax (p - 1)
    case signWithBlinder b k params priv hashAlg msg of
        Nothing -> sign params priv hashAlg msg
        Just sig -> return sig

-- | verify a signature
verify
    :: (ByteArrayAccess msg, HashAlgorithm hash)
    => Params
    -> PublicNumber
    -> hash
    -> msg
    -> Signature
    -> Bool
verify (Params p g _) (PublicNumber y) hashAlg msg (Signature r s)
    | or [r <= 0, r >= p, s <= 0, s >= (p - 1)] = False
    | otherwise = lhs == rhs
  where
    h = os2ip $ hashWith hashAlg msg
    lhs = expFast g h p
    rhs = (expFast y r p * expFast r s p) `mod` p