cryptostore-0.5.0.0: src/Crypto/Store/PubKey/RSA/KEM.hs
-- |
-- Module : Crypto.Store.PubKey.RSA.KEM
-- License : BSD-style
-- Maintainer : Olivier Chéron <olivier.cheron@gmail.com>
-- Stability : experimental
-- Portability : unknown
--
-- RSA as a Key-Encapsulation Mechanism (KEM).
module Crypto.Store.PubKey.RSA.KEM
( KDF(..), kdf3
-- * Operations
, encapsulate, encapsulateWith, decapsulate
) where
import Data.ByteArray (ByteArray, ByteArrayAccess, Bytes)
import qualified Data.ByteArray as B
import Data.ByteString (ByteString, empty)
import Crypto.Hash
import Crypto.Number.Generate
import Crypto.Number.Serialize (os2ip, i2ospOf_)
import qualified Crypto.PubKey.RSA.Prim as RSA
import qualified Crypto.PubKey.RSA.Types as RSA
import Crypto.Random
-- | Key derivation used by RSA-KEM.
newtype KDF bIn bOut = KDF (ByteString -> bIn -> bOut)
-- | KDF3 from ANSI X9.44-2007 (R2017)
kdf3 :: (HashAlgorithm a, ByteArrayAccess bIn, ByteArray bOut)
=> a -> Int -> KDF bIn bOut
kdf3 hashAlg outLen = KDF (doKDF3 hashAlg outLen)
doKDF3 :: (HashAlgorithm a, ByteArrayAccess bIn, ByteArray bOut)
=> a -> Int -> ByteString -> bIn -> bOut
doKDF3 hashAlg outLen otherInfo input
| r == 0 = B.concat $ map doChunk [ 1 .. k ]
| otherwise = B.take outLen $ B.concat $ map doChunk [ 1 .. k + 1 ]
where
(k, r) = outLen `divMod` blk
blk = hashDigestSize hashAlg
doChunk i =
let ctx0 = hashInitWith hashAlg
ctx1 = hashUpdate ctx0 (i2ospOf_ 4 (toInteger i) :: Bytes)
ctx2 = hashUpdate ctx1 input
ctx3 = hashUpdate ctx2 otherInfo
in hashFinalize ctx3
-- | Generate a shared secret key and an associated ciphertext using randomness.
encapsulate :: (MonadRandom m, ByteArray ciphertext)
=> KDF ciphertext sharedSecret
-> RSA.PublicKey
-> m (sharedSecret, ciphertext)
encapsulate kdf pub = encap kdf pub <$> generateMax (RSA.public_n pub)
-- | Generate a shared secret key and an associated ciphertext using a
-- specified random input. This input must be an integer in range [0, n) and
-- not repeated with other encapsulations. For testing purposes.
encapsulateWith :: ByteArray ciphertext
=> KDF ciphertext sharedSecret
-> RSA.PublicKey
-> Integer
-> Maybe (sharedSecret, ciphertext)
encapsulateWith kdf pub z
| z < 0 || z >= RSA.public_n pub = Nothing
| otherwise = Just $ encap kdf pub z
encap :: ByteArray ciphertext
=> KDF ciphertext sharedSecret
-> RSA.PublicKey
-> Integer
-> (sharedSecret, ciphertext)
encap (KDF kdf) pub z = (ss, ct)
where
zz = i2ospOf_ (RSA.public_size pub) z
ct = RSA.ep pub zz
ss = kdf empty zz
-- | Return the shared secret for a given ciphertext.
decapsulate :: ByteArray ciphertext
=> KDF ciphertext sharedSecret
-> RSA.PrivateKey
-> ciphertext
-> Maybe sharedSecret
decapsulate (KDF kdf) priv ct
| B.length ct < RSA.public_size pub = Nothing
| c >= RSA.public_n pub = Nothing
| otherwise = Just ss
where
pub = RSA.private_pub priv
c = os2ip ct
zz = RSA.dp Nothing priv ct
ss = kdf empty zz