1-- |
2-- Module      : Crypto.PubKey.RSA.OAEP
3-- License     : BSD-style
4-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
5-- Stability   : experimental
6-- Portability : Good
7--
8-- RSA OAEP mode
9-- <http://en.wikipedia.org/wiki/Optimal_asymmetric_encryption_padding>
10--
11module Crypto.PubKey.RSA.OAEP
12    (
13      OAEPParams(..)
14    , defaultOAEPParams
15    -- * OAEP encryption
16    , encryptWithSeed
17    , encrypt
18    -- * OAEP decryption
19    , decrypt
20    , decryptSafer
21    ) where
22
23import           Crypto.Hash
24import           Crypto.Random.Types
25import           Crypto.PubKey.RSA.Types
26import           Crypto.PubKey.MaskGenFunction
27import           Crypto.PubKey.RSA.Prim
28import           Crypto.PubKey.RSA (generateBlinder)
29import           Crypto.PubKey.Internal (and')
30import           Data.ByteString (ByteString)
31import qualified Data.ByteString as B
32import           Data.Bits (xor)
33
34import           Crypto.Internal.ByteArray (ByteArrayAccess, ByteArray)
35import qualified Crypto.Internal.ByteArray as B (convert)
36
37-- | Parameters for OAEP encryption/decryption
38data OAEPParams hash seed output = OAEPParams
39    { oaepHash       :: hash                         -- ^ Hash function to use.
40    , oaepMaskGenAlg :: MaskGenAlgorithm seed output -- ^ Mask Gen algorithm to use.
41    , oaepLabel      :: Maybe ByteString             -- ^ Optional label prepended to message.
42    }
43
44-- | Default Params with a specified hash function
45defaultOAEPParams :: (ByteArrayAccess seed, ByteArray output, HashAlgorithm hash)
46                  => hash
47                  -> OAEPParams hash seed output
48defaultOAEPParams hashAlg =
49    OAEPParams { oaepHash         = hashAlg
50               , oaepMaskGenAlg   = mgf1 hashAlg
51               , oaepLabel        = Nothing
52               }
53
54-- | Encrypt a message using OAEP with a predefined seed.
55encryptWithSeed :: HashAlgorithm hash
56                => ByteString      -- ^ Seed
57                -> OAEPParams hash ByteString ByteString -- ^ OAEP params to use for encryption
58                -> PublicKey       -- ^ Public key.
59                -> ByteString      -- ^ Message to encrypt
60                -> Either Error ByteString
61encryptWithSeed seed oaep pk msg
62    | k < 2*hashLen+2          = Left InvalidParameters
63    | B.length seed /= hashLen = Left InvalidParameters
64    | mLen > k - 2*hashLen-2   = Left MessageTooLong
65    | otherwise                = Right $ ep pk em
66    where -- parameters
67          k          = public_size pk
68          mLen       = B.length msg
69          mgf        = oaepMaskGenAlg oaep
70          labelHash  = hashWith (oaepHash oaep) (maybe B.empty id $ oaepLabel oaep)
71          hashLen    = hashDigestSize (oaepHash oaep)
72
73          -- put fields
74          ps         = B.replicate (k - mLen - 2*hashLen - 2) 0
75          db         = B.concat [B.convert labelHash, ps, B.singleton 0x1, msg]
76          dbmask     = mgf seed (k - hashLen - 1)
77          maskedDB   = B.pack $ B.zipWith xor db dbmask
78          seedMask   = mgf maskedDB hashLen
79          maskedSeed = B.pack $ B.zipWith xor seed seedMask
80          em         = B.concat [B.singleton 0x0,maskedSeed,maskedDB]
81
82-- | Encrypt a message using OAEP
83encrypt :: (HashAlgorithm hash, MonadRandom m)
84        => OAEPParams hash ByteString ByteString -- ^ OAEP params to use for encryption.
85        -> PublicKey       -- ^ Public key.
86        -> ByteString      -- ^ Message to encrypt
87        -> m (Either Error ByteString)
88encrypt oaep pk msg = do
89    seed <- getRandomBytes hashLen
90    return (encryptWithSeed seed oaep pk msg)
91  where
92    hashLen    = hashDigestSize (oaepHash oaep)
93
94-- | un-pad a OAEP encoded message.
95--
96-- It doesn't apply the RSA decryption primitive
97unpad :: HashAlgorithm hash
98      => OAEPParams hash ByteString ByteString -- ^ OAEP params to use
99      -> Int             -- ^ size of the key in bytes
100      -> ByteString      -- ^ encoded message (not encrypted)
101      -> Either Error ByteString
102unpad oaep k em
103    | paddingSuccess = Right msg
104    | otherwise      = Left MessageNotRecognized
105    where -- parameters
106          mgf        = oaepMaskGenAlg oaep
107          labelHash  = B.convert $ hashWith (oaepHash oaep) (maybe B.empty id $ oaepLabel oaep)
108          hashLen    = hashDigestSize (oaepHash oaep)
109          -- getting em's fields
110          (pb, em0)  = B.splitAt 1 em
111          (maskedSeed,maskedDB) = B.splitAt hashLen em0
112          seedMask   = mgf maskedDB hashLen
113          seed       = B.pack $ B.zipWith xor maskedSeed seedMask
114          dbmask     = mgf seed (k - hashLen - 1)
115          db         = B.pack $ B.zipWith xor maskedDB dbmask
116          -- getting db's fields
117          (labelHash',db1) = B.splitAt hashLen db
118          (_,db2)    = B.break (/= 0) db1
119          (ps1,msg)  = B.splitAt 1 db2
120
121          paddingSuccess = and' [ labelHash' == labelHash -- no need for constant eq
122                                , ps1        == B.replicate 1 0x1
123                                , pb         == B.replicate 1 0x0
124                                ]
125
126-- | Decrypt a ciphertext using OAEP
127--
128-- When the signature is not in a context where an attacker could gain
129-- information from the timing of the operation, the blinder can be set to None.
130--
131-- If unsure always set a blinder or use decryptSafer
132decrypt :: HashAlgorithm hash
133        => Maybe Blinder   -- ^ Optional blinder
134        -> OAEPParams hash ByteString ByteString -- ^ OAEP params to use for decryption
135        -> PrivateKey      -- ^ Private key
136        -> ByteString      -- ^ Cipher text
137        -> Either Error ByteString
138decrypt blinder oaep pk cipher
139    | B.length cipher /= k = Left MessageSizeIncorrect
140    | k < 2*hashLen+2      = Left InvalidParameters
141    | otherwise            = unpad oaep (private_size pk) $ dp blinder pk cipher
142    where -- parameters
143          k          = private_size pk
144          hashLen    = hashDigestSize (oaepHash oaep)
145
146-- | Decrypt a ciphertext using OAEP and by automatically generating a blinder.
147decryptSafer :: (HashAlgorithm hash, MonadRandom m)
148             => OAEPParams hash ByteString ByteString -- ^ OAEP params to use for decryption
149             -> PrivateKey -- ^ Private key
150             -> ByteString -- ^ Cipher text
151             -> m (Either Error ByteString)
152decryptSafer oaep pk cipher = do
153    blinder <- generateBlinder (private_n pk)
154    return (decrypt (Just blinder) oaep pk cipher)
155