Skip to main content

rsa/
oaep.rs

1//! Encryption and Decryption using [OAEP padding](https://datatracker.ietf.org/doc/html/rfc8017#section-7.1).
2//!
3//! # Usage
4//!
5//! See [code example in the toplevel rustdoc](../index.html#oaep-encryption).
6
7#[cfg(feature = "alloc")]
8mod decrypting_key;
9mod encrypting_key;
10#[cfg(not(feature = "alloc"))]
11mod label;
12
13#[cfg(feature = "alloc")]
14pub use self::decrypting_key::DecryptingKey;
15#[cfg(feature = "alloc")]
16pub use self::encrypting_key::EncryptingKey;
17pub use self::encrypting_key::GenericEncryptingKey;
18#[cfg(not(feature = "alloc"))]
19pub use self::label::{Label, MAX_LABEL_LEN};
20
21#[cfg(feature = "alloc")]
22use alloc::boxed::Box;
23#[cfg(feature = "alloc")]
24use alloc::{vec, vec::Vec};
25use core::fmt;
26#[cfg(feature = "alloc")]
27use crypto_bigint::BoxedUint;
28
29use digest::{Digest, FixedOutputReset};
30use rand_core::TryCryptoRng;
31
32use crate::algorithms::oaep::*;
33#[cfg(feature = "alloc")]
34use crate::algorithms::pad::{uint_to_be_pad, uint_to_be_pad_into, uint_to_zeroizing_be_pad};
35#[cfg(feature = "alloc")]
36use crate::algorithms::rsa::rsa_decrypt_and_check;
37#[cfg(feature = "alloc")]
38use crate::algorithms::rsa::rsa_encrypt;
39use crate::errors::{Error, Result};
40#[cfg(feature = "alloc")]
41use crate::key::RsaPrivateKey;
42#[cfg(feature = "alloc")]
43use crate::key::{self, RsaPublicKey};
44use crate::traits::{PaddingScheme, PublicKeyParts, UnsignedModularInt};
45
46/// Encryption and Decryption using [OAEP padding](https://datatracker.ietf.org/doc/html/rfc8017#section-7.1).
47///
48/// - `digest` is used to hash the label. The maximum possible plaintext length is `m = k - 2 * h_len - 2`,
49///   where `k` is the size of the RSA modulus.
50/// - `mgf_digest` specifies the hash function that is used in the [MGF1](https://datatracker.ietf.org/doc/html/rfc8017#appendix-B.2).
51/// - `label` is optional data that can be associated with the message.
52///
53/// The two hash functions can, but don't need to be the same.
54///
55/// A prominent example is the [`AndroidKeyStore`](https://developer.android.com/guide/topics/security/cryptography#oaep-mgf1-digest).
56/// It uses SHA-1 for `mgf_digest` and a user-chosen SHA flavour for `digest`.
57#[cfg(feature = "alloc")]
58pub struct Oaep<D, MGD = D> {
59    /// Digest type to use.
60    pub digest: D,
61
62    /// Digest to use for Mask Generation Function (MGF).
63    pub mgf_digest: MGD,
64
65    /// Optional label.
66    pub label: Option<Box<[u8]>>,
67}
68
69#[cfg(feature = "alloc")]
70impl<D> Default for Oaep<D>
71where
72    D: Digest + FixedOutputReset,
73{
74    fn default() -> Self {
75        Self::new()
76    }
77}
78
79#[cfg(feature = "alloc")]
80impl<D> Oaep<D>
81where
82    D: Digest + FixedOutputReset,
83{
84    /// Create a new OAEP `PaddingScheme`, using `T` as the hash function for both the default (empty) label and for MGF1.
85    ///
86    /// # Example
87    /// ```
88    /// use sha1::Sha1;
89    /// use sha2::Sha256;
90    /// use rsa::{RsaPublicKey, Oaep};
91    /// use base64ct::{Base64, Encoding};
92    /// use crypto_bigint::BoxedUint;
93    ///
94    /// let n_bytes = Base64::decode_vec("seAOhmYFAjH6NOaB54dboqw86uPXV/oK9ayJGV4mVClbvsDBJmF3bVkOaVMp9ogcFJTFFSy5g2HsTZIfHyuQVUJADb+BeRnkYrYhRvNJOKj2pcDbkxYe9XGMx8pIvxkDFnIpusb3gUsuzMUAU5qIstjwQKzuD51c6uJi0HAtQkr6Wmlt34SX7xkD/MfRuTu9uqmHmkiiJaCDHB2reYTPguetSWfuvp1qBJDNgSsp7BjwYANWldyrmZ8cLXEXYMUG5vtsWMxUzl8ertEr6kbnGM0OJghNuEtittW/dfTPvk683R1jj0hNaMzvHK8xYldUlLuwmWCYIIvpHBaA/w+FwQ==").unwrap();
95    /// let e_bytes = Base64::decode_vec("AQAB").unwrap();
96    /// let n = BoxedUint::from_be_slice(&n_bytes, 2048).unwrap();
97    /// let e = BoxedUint::from_be_slice(&e_bytes, 32).unwrap();
98    ///
99    /// let mut rng = rand::rng();
100    /// let key = RsaPublicKey::new(n, e).unwrap();
101    /// let padding = Oaep::<Sha256>::new();
102    /// let encrypted_data = key.encrypt(&mut rng, padding, b"secret").unwrap();
103    /// ```
104    pub fn new() -> Self {
105        Self {
106            digest: D::new(),
107            mgf_digest: D::new(),
108            label: None,
109        }
110    }
111
112    /// Create a new OAEP `PaddingScheme` with an associated `label`, using `T` as the hash function for both the label and for MGF1.
113    pub fn new_with_label<S: Into<Box<[u8]>>>(label: S) -> Self {
114        Self {
115            digest: D::new(),
116            mgf_digest: D::new(),
117            label: Some(label.into()),
118        }
119    }
120}
121
122#[cfg(feature = "alloc")]
123impl<D, MGD> Oaep<D, MGD>
124where
125    D: Digest + FixedOutputReset,
126    MGD: Digest + FixedOutputReset,
127{
128    /// Create a new OAEP `PaddingScheme`, using `T` as the hash function for the default (empty) label, and `U` as the hash function for MGF1.
129    /// If a label is needed use `PaddingScheme::new_oaep_with_label` or `PaddingScheme::new_oaep_with_mgf_hash_with_label`.
130    ///
131    /// # Example
132    /// ```
133    /// use sha1::Sha1;
134    /// use sha2::Sha256;
135    /// use rsa::{RsaPublicKey, Oaep};
136    /// use base64ct::{Base64, Encoding};
137    /// use crypto_bigint::BoxedUint;
138    ///
139    /// let n_bytes = Base64::decode_vec("seAOhmYFAjH6NOaB54dboqw86uPXV/oK9ayJGV4mVClbvsDBJmF3bVkOaVMp9ogcFJTFFSy5g2HsTZIfHyuQVUJADb+BeRnkYrYhRvNJOKj2pcDbkxYe9XGMx8pIvxkDFnIpusb3gUsuzMUAU5qIstjwQKzuD51c6uJi0HAtQkr6Wmlt34SX7xkD/MfRuTu9uqmHmkiiJaCDHB2reYTPguetSWfuvp1qBJDNgSsp7BjwYANWldyrmZ8cLXEXYMUG5vtsWMxUzl8ertEr6kbnGM0OJghNuEtittW/dfTPvk683R1jj0hNaMzvHK8xYldUlLuwmWCYIIvpHBaA/w+FwQ==").unwrap();
140    /// let e_bytes = Base64::decode_vec("AQAB").unwrap();
141    /// let n = BoxedUint::from_be_slice(&n_bytes, 2048).unwrap();
142    /// let e = BoxedUint::from_be_slice(&e_bytes, 32).unwrap();
143    ///
144    /// let mut rng = rand::rng();
145    /// let key = RsaPublicKey::new(n, e).unwrap();
146    /// let padding = Oaep::<Sha256, Sha1>::new_with_mgf_hash();
147    /// let encrypted_data = key.encrypt(&mut rng, padding, b"secret").unwrap();
148    /// ```
149    pub fn new_with_mgf_hash() -> Self {
150        Self {
151            digest: D::new(),
152            mgf_digest: MGD::new(),
153            label: None,
154        }
155    }
156
157    /// Create a new OAEP `PaddingScheme` with an associated `label`, using `T` as the hash function for the label, and `U` as the hash function for MGF1.
158    pub fn new_with_mgf_hash_and_label<S: Into<Box<[u8]>>>(label: S) -> Self {
159        Self {
160            digest: D::new(),
161            mgf_digest: MGD::new(),
162            label: Some(label.into()),
163        }
164    }
165}
166
167#[cfg(feature = "alloc")]
168impl<D, MGD> PaddingScheme for Oaep<D, MGD>
169where
170    D: Digest + FixedOutputReset,
171    MGD: Digest + FixedOutputReset,
172{
173    #[cfg(feature = "alloc")]
174    fn decrypt<Rng: TryCryptoRng + ?Sized>(
175        mut self,
176        rng: Option<&mut Rng>,
177        priv_key: &RsaPrivateKey,
178        ciphertext: &[u8],
179    ) -> Result<Vec<u8>> {
180        decrypt(
181            rng,
182            priv_key,
183            ciphertext,
184            &mut self.digest,
185            &mut self.mgf_digest,
186            self.label,
187        )
188    }
189
190    fn encrypt<Rng, K, T>(mut self, rng: &mut Rng, pub_key: &K, msg: &[u8]) -> Result<Vec<u8>>
191    where
192        Rng: TryCryptoRng + ?Sized,
193        T: UnsignedModularInt,
194        K: PublicKeyParts<T>,
195        K::MontyParams: crate::traits::modular::CtModulusParams,
196    {
197        let em = oaep_encrypt(
198            rng,
199            msg,
200            &mut self.digest,
201            &mut self.mgf_digest,
202            self.label,
203            pub_key.size(),
204        )?;
205        let int = T::try_from_be_bytes_vartime(&em)?;
206        let mut storage = vec![0u8; pub_key.size()];
207        let ciphertext =
208            uint_to_be_pad_into(rsa_encrypt(pub_key, &int)?, pub_key.size(), &mut storage)?;
209        Ok(ciphertext.to_vec())
210    }
211}
212
213#[cfg(feature = "alloc")]
214impl<D, MGD> fmt::Debug for Oaep<D, MGD> {
215    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216        f.debug_struct("OAEP")
217            .field("digest", &"...")
218            .field("mgf_digest", &"...")
219            .field("label", &self.label)
220            .finish()
221    }
222}
223
224/// Encrypts the given message with RSA and the padding scheme from
225/// [PKCS#1 OAEP].
226///
227/// The message must be no longer than the length of the public modulus minus
228/// `2 + (2 * hash.size())`.
229///
230/// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1
231#[cfg(feature = "alloc")]
232#[inline]
233#[allow(dead_code)]
234fn encrypt<R, D, MGD>(
235    rng: &mut R,
236    pub_key: &RsaPublicKey,
237    msg: &[u8],
238    digest: &mut D,
239    mgf_digest: &mut MGD,
240    label: Option<Box<[u8]>>,
241) -> Result<Vec<u8>>
242where
243    R: TryCryptoRng + ?Sized,
244    D: Digest + FixedOutputReset,
245    MGD: Digest + FixedOutputReset,
246{
247    key::check_public(pub_key)?;
248
249    let em = oaep_encrypt(rng, msg, digest, mgf_digest, label, pub_key.size())?;
250
251    let int = BoxedUint::from_be_slice(&em, pub_key.n_bits_precision())?;
252    uint_to_be_pad(rsa_encrypt(pub_key, &int)?, pub_key.size())
253}
254
255/// Encrypts the given message with RSA and the padding scheme from
256/// [PKCS#1 OAEP].
257///
258/// The message must be no longer than the length of the public modulus minus
259/// `2 + (2 * hash.size())`.
260///
261/// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1
262#[cfg(feature = "alloc")]
263#[allow(dead_code)]
264fn encrypt_digest<R, D, MGD>(
265    rng: &mut R,
266    pub_key: &RsaPublicKey,
267    msg: &[u8],
268    label: Option<Box<[u8]>>,
269) -> Result<Vec<u8>>
270where
271    R: TryCryptoRng + ?Sized,
272    D: Digest,
273    MGD: Digest + FixedOutputReset,
274{
275    key::check_public(pub_key)?;
276
277    let em = oaep_encrypt_digest::<_, D, MGD>(rng, msg, label, pub_key.size())?;
278
279    let int = BoxedUint::from_be_slice(&em, pub_key.n_bits_precision())?;
280    uint_to_be_pad(rsa_encrypt(pub_key, &int)?, pub_key.size())
281}
282
283/// Does not call `key::check_public` — that validator is `alloc`-only.
284pub fn encrypt_digest_into<'a, R, D, MGD, K, T>(
285    rng: &mut R,
286    pub_key: &K,
287    msg: &[u8],
288    label: Option<&[u8]>,
289    storage: &'a mut [u8],
290) -> crate::Result<&'a [u8]>
291where
292    R: rand_core::TryCryptoRng + ?Sized,
293    D: digest::Digest,
294    MGD: digest::Digest + digest::FixedOutputReset,
295    K: crate::traits::PublicKeyParts<T>,
296    K::MontyParams: crate::traits::modular::CtModulusParams,
297    T: crate::traits::UnsignedModularInt,
298{
299    let padded_len = pub_key.size();
300    let em = crate::algorithms::oaep::oaep_encrypt_digest_into::<_, D, MGD>(
301        rng, msg, label, padded_len, storage,
302    )?;
303    let int = T::try_from_be_bytes_vartime(em)?;
304    crate::algorithms::pad::uint_to_be_pad_into(
305        crate::algorithms::rsa::rsa_encrypt(pub_key, &int)?,
306        padded_len,
307        storage,
308    )
309}
310
311/// Decrypts a plaintext using RSA and the padding scheme from [PKCS#1 OAEP].
312///
313/// If an `rng` is passed, it uses RSA blinding to avoid timing side-channel attacks.
314///
315/// Note that whether this function returns an error or not discloses secret
316/// information. If an attacker can cause this function to run repeatedly and
317/// learn whether each instance returned an error then they can decrypt and
318/// forge signatures as if they had the private key.
319///
320/// See `decrypt_session_key` for a way of solving this problem.
321///
322/// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1
323#[cfg(feature = "alloc")]
324#[inline]
325fn decrypt<R, D, MGD>(
326    rng: Option<&mut R>,
327    priv_key: &RsaPrivateKey,
328    ciphertext: &[u8],
329    digest: &mut D,
330    mgf_digest: &mut MGD,
331    label: Option<Box<[u8]>>,
332) -> Result<Vec<u8>>
333where
334    R: TryCryptoRng + ?Sized,
335    D: Digest + FixedOutputReset,
336    MGD: Digest + FixedOutputReset,
337{
338    if ciphertext.len() != priv_key.size() {
339        return Err(Error::Decryption);
340    }
341
342    let ciphertext = BoxedUint::from_be_slice(ciphertext, priv_key.n_bits_precision())?;
343
344    let em = rsa_decrypt_and_check(priv_key, rng, &ciphertext)?;
345    let mut em = uint_to_zeroizing_be_pad(em, priv_key.size())?;
346
347    oaep_decrypt(&mut em, digest, mgf_digest, label, priv_key.size())
348}
349
350/// Decrypts a plaintext using RSA and the padding scheme from [PKCS#1 OAEP].
351///
352/// If an `rng` is passed, it uses RSA blinding to avoid timing side-channel attacks.
353///
354/// Note that whether this function returns an error or not discloses secret
355/// information. If an attacker can cause this function to run repeatedly and
356/// learn whether each instance returned an error then they can decrypt and
357/// forge signatures as if they had the private key.
358///
359/// See `decrypt_session_key` for a way of solving this problem.
360///
361/// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1
362#[cfg(feature = "alloc")]
363#[inline]
364fn decrypt_digest<R, D, MGD>(
365    rng: Option<&mut R>,
366    priv_key: &RsaPrivateKey,
367    ciphertext: &[u8],
368    label: Option<Box<[u8]>>,
369) -> Result<Vec<u8>>
370where
371    R: TryCryptoRng + ?Sized,
372    D: Digest,
373    MGD: Digest + FixedOutputReset,
374{
375    key::check_public(priv_key)?;
376
377    if ciphertext.len() != priv_key.size() {
378        return Err(Error::Decryption);
379    }
380
381    let ciphertext = BoxedUint::from_be_slice(ciphertext, priv_key.n_bits_precision())?;
382    let em = rsa_decrypt_and_check(priv_key, rng, &ciphertext)?;
383    let mut em = uint_to_zeroizing_be_pad(em, priv_key.size())?;
384
385    oaep_decrypt_digest::<D, MGD>(&mut em, label, priv_key.size())
386}
387
388#[cfg(test)]
389#[cfg(feature = "alloc")]
390mod tests {
391    use crate::key::{RsaPrivateKey, RsaPublicKey};
392    use crate::oaep::{DecryptingKey, EncryptingKey, Oaep};
393    use crate::traits::PublicKeyParts;
394    use crate::traits::{Decryptor, RandomizedDecryptor, RandomizedEncryptor};
395
396    use crypto_bigint::BoxedUint;
397    use digest::{Digest, FixedOutputReset};
398    use rand::rngs::ChaCha8Rng;
399    use rand_core::{Rng, SeedableRng};
400    use sha1::Sha1;
401    use sha2::{Sha224, Sha256, Sha384, Sha512};
402    use sha3::{Sha3_256, Sha3_384, Sha3_512};
403
404    fn get_private_key() -> RsaPrivateKey {
405        // -----BEGIN RSA PRIVATE KEY-----
406        // MIIEpAIBAAKCAQEA05e4TZikwmE47RtpWoEG6tkdVTvwYEG2LT/cUKBB4iK49FKW
407        // icG4LF5xVU9d1p+i9LYVjPDb61eBGg/DJ+HyjnT+dNO8Fmweq9wbi1e5NMqL5bAL
408        // TymXW8yZrK9BW1m7KKZ4K7QaLDwpdrPBjbre9i8AxrsiZkAJUJbAzGDSL+fvmH11
409        // xqgbENlr8pICivEQ3HzBu8Q9Iq2rN5oM1dgHjMeA/1zWIJ3qNMkiz3hPdxfkKNdb
410        // WuyP8w5fAUFRB2bi4KuNRzyE6HELK5gifD2wlTN600UvGeK5v7zN2BSKv2d2+lUn
411        // debnWVbkUimuWpxGlJurHmIvDkj1ZSSoTtNIOwIDAQABAoIBAQDE5wxokWLJTGYI
412        // KBkbUrTYOSEV30hqmtvoMeRY1zlYMg3Bt1VFbpNwHpcC12+wuS+Q4B0f4kgVMoH+
413        // eaqXY6kvrmnY1+zRRN4p+hNb0U+Vc+NJ5FAx47dpgvWDADgmxVLomjl8Gga9IWNI
414        // hjDZLowrtkPXq+9wDaldaFyUFImkb1S1MW9itdLDp/G70TTLNzU6RGg/3J2V02RY
415        // 3iL2xEBX/nSgpDbEMI9z9NpC81xHrBanE41IOvyR5B3DoRJzguDA9RGbAiG0/GOd
416        // a5w4F3pt6bUm69iMONeYLAf5ig79h31Qiq4nW5RpFcAuLhEG0XXXTsZ3f16A0SwF
417        // PZx74eNBAoGBAPgnu/OkGHfHzFmuv0LtSynDLe/LjtloY9WwkKBaiTDdYkohydz5
418        // g4Vo/foN9luEYqXyrJE9bFb5dVMr2OePsHvUBcqZpIS89Z8Bm73cs5M/K85wYwC0
419        // 97EQEgxd+QGBWQZ8NdowYaVshjWlK1QnOzEnG0MR8Hld9gIeY1XhpC5hAoGBANpI
420        // F84Aid028q3mo/9BDHPsNL8bT2vaOEMb/t4RzvH39u+nDl+AY6Ox9uFylv+xX+76
421        // CRKgMluNH9ZaVZ5xe1uWHsNFBy4OxSA9A0QdKa9NZAVKBFB0EM8dp457YRnZCexm
422        // 5q1iW/mVsnmks8W+fYlc18W5xMSX/ecwkW/NtOQbAoGAHabpz4AhKFbodSLrWbzv
423        // CUt4NroVFKdjnoodjfujfwJFF2SYMV5jN9LG3lVCxca43ulzc1tqka33Nfv8TBcg
424        // WHuKQZ5ASVgm5VwU1wgDMSoQOve07MWy/yZTccTc1zA0ihDXgn3bfR/NnaVh2wlh
425        // CkuI92eyW1494hztc7qlmqECgYEA1zenyOQ9ChDIW/ABGIahaZamNxsNRrDFMl3j
426        // AD+cxHSRU59qC32CQH8ShRy/huHzTaPX2DZ9EEln76fnrS4Ey7uLH0rrFl1XvT6K
427        // /timJgLvMEvXTx/xBtUdRN2fUqXtI9odbSyCtOYFL+zVl44HJq2UzY4pVRDrNcxs
428        // SUkQJqsCgYBSaNfPBzR5rrstLtTdZrjImRW1LRQeDEky9WsMDtCTYUGJTsTSfVO8
429        // hkU82MpbRVBFIYx+GWIJwcZRcC7OCQoV48vMJllxMAAjqG/p00rVJ+nvA7et/nNu
430        // BoB0er/UmDm4Ly/97EO9A0PKMOE5YbMq9s3t3RlWcsdrU7dvw+p2+A==
431        // -----END RSA PRIVATE KEY-----
432
433        RsaPrivateKey::from_components(
434            BoxedUint::from_be_hex("d397b84d98a4c26138ed1b695a8106ead91d553bf06041b62d3fdc50a041e222b8f4529689c1b82c5e71554f5dd69fa2f4b6158cf0dbeb57811a0fc327e1f28e74fe74d3bc166c1eabdc1b8b57b934ca8be5b00b4f29975bcc99acaf415b59bb28a6782bb41a2c3c2976b3c18dbadef62f00c6bb226640095096c0cc60d22fe7ef987d75c6a81b10d96bf292028af110dc7cc1bbc43d22adab379a0cd5d8078cc780ff5cd6209dea34c922cf784f7717e428d75b5aec8ff30e5f0141510766e2e0ab8d473c84e8710b2b98227c3db095337ad3452f19e2b9bfbccdd8148abf6776fa552775e6e75956e45229ae5a9c46949bab1e622f0e48f56524a84ed3483b", 2048).unwrap(),
435            BoxedUint::from(65_537u64),
436            BoxedUint::from_be_hex("c4e70c689162c94c660828191b52b4d8392115df486a9adbe831e458d73958320dc1b755456e93701e9702d76fb0b92f90e01d1fe248153281fe79aa9763a92fae69d8d7ecd144de29fa135bd14f9573e349e45031e3b76982f583003826c552e89a397c1a06bd2163488630d92e8c2bb643d7abef700da95d685c941489a46f54b5316f62b5d2c3a7f1bbd134cb37353a44683fdc9d95d36458de22f6c44057fe74a0a436c4308f73f4da42f35c47ac16a7138d483afc91e41dc3a1127382e0c0f5119b0221b4fc639d6b9c38177a6de9b526ebd88c38d7982c07f98a0efd877d508aae275b946915c02e2e1106d175d74ec6777f5e80d12c053d9c7be1e341", 2048).unwrap(),
437            vec![
438                BoxedUint::from_be_hex("f827bbf3a41877c7cc59aebf42ed4b29c32defcb8ed96863d5b090a05a8930dd624a21c9dcf9838568fdfa0df65b8462a5f2ac913d6c56f975532bd8e78fb07bd405ca99a484bcf59f019bbddcb3933f2bce706300b4f7b110120c5df9018159067c35da3061a56c8635a52b54273b31271b4311f0795df6021e6355e1a42e61", 1024).unwrap(),
439                BoxedUint::from_be_hex("da4817ce0089dd36f2ade6a3ff410c73ec34bf1b4f6bda38431bfede11cef1f7f6efa70e5f8063a3b1f6e17296ffb15feefa0912a0325b8d1fd65a559e717b5b961ec345072e0ec5203d03441d29af4d64054a04507410cf1da78e7b6119d909ec66e6ad625bf995b279a4b3c5be7d895cd7c5b9c4c497fde730916fcdb4e41b", 1024).unwrap()
440            ],
441        ).unwrap()
442    }
443
444    #[test]
445    fn test_encrypt_decrypt_oaep() {
446        let priv_key = get_private_key();
447        do_test_encrypt_decrypt_oaep::<Sha1>(&priv_key);
448        do_test_encrypt_decrypt_oaep::<Sha224>(&priv_key);
449        do_test_encrypt_decrypt_oaep::<Sha256>(&priv_key);
450        do_test_encrypt_decrypt_oaep::<Sha384>(&priv_key);
451        do_test_encrypt_decrypt_oaep::<Sha512>(&priv_key);
452        do_test_encrypt_decrypt_oaep::<Sha3_256>(&priv_key);
453        do_test_encrypt_decrypt_oaep::<Sha3_384>(&priv_key);
454        do_test_encrypt_decrypt_oaep::<Sha3_512>(&priv_key);
455
456        do_test_oaep_with_different_hashes::<Sha1, Sha1>(&priv_key);
457        do_test_oaep_with_different_hashes::<Sha224, Sha1>(&priv_key);
458        do_test_oaep_with_different_hashes::<Sha256, Sha1>(&priv_key);
459        do_test_oaep_with_different_hashes::<Sha384, Sha1>(&priv_key);
460        do_test_oaep_with_different_hashes::<Sha512, Sha1>(&priv_key);
461        do_test_oaep_with_different_hashes::<Sha3_256, Sha1>(&priv_key);
462        do_test_oaep_with_different_hashes::<Sha3_384, Sha1>(&priv_key);
463        do_test_oaep_with_different_hashes::<Sha3_512, Sha1>(&priv_key);
464    }
465
466    fn get_label(rng: &mut ChaCha8Rng) -> Option<Box<[u8]>> {
467        let mut buf = [0u8; 32];
468        rng.fill_bytes(&mut buf);
469
470        if rng.next_u32() % 2 == 0 {
471            Some(buf.into())
472        } else {
473            None
474        }
475    }
476
477    fn do_test_encrypt_decrypt_oaep<D: Digest + FixedOutputReset>(prk: &RsaPrivateKey) {
478        let mut rng = ChaCha8Rng::from_seed([42; 32]);
479
480        let k = prk.size();
481
482        for i in 1..8 {
483            let mut input = vec![0u8; i * 8];
484            rng.fill_bytes(&mut input);
485
486            if input.len() > k - 11 {
487                input = input[0..k - 11].to_vec();
488            }
489            let label = get_label(&mut rng);
490
491            let pub_key: RsaPublicKey = prk.into();
492
493            let ciphertext = if let Some(ref label) = label {
494                let padding = Oaep::<D>::new_with_label(label.clone());
495                pub_key.encrypt(&mut rng, padding, &input).unwrap()
496            } else {
497                let padding = Oaep::<D>::new();
498                pub_key.encrypt(&mut rng, padding, &input).unwrap()
499            };
500
501            assert_ne!(input, ciphertext);
502            let blind: bool = rng.next_u32() < (1 << 31);
503
504            let padding = if let Some(label) = label {
505                Oaep::<D>::new_with_label::<Box<[u8]>>(label)
506            } else {
507                Oaep::<D>::new()
508            };
509
510            let plaintext = if blind {
511                prk.decrypt(padding, &ciphertext).unwrap()
512            } else {
513                prk.decrypt_blinded(&mut rng, padding, &ciphertext).unwrap()
514            };
515
516            assert_eq!(input, plaintext);
517        }
518    }
519
520    fn do_test_oaep_with_different_hashes<
521        D: Digest + FixedOutputReset,
522        U: Digest + FixedOutputReset,
523    >(
524        prk: &RsaPrivateKey,
525    ) {
526        let mut rng = ChaCha8Rng::from_seed([42; 32]);
527
528        let k = prk.size();
529
530        for i in 1..8 {
531            let mut input = vec![0u8; i * 8];
532            rng.fill_bytes(&mut input);
533
534            if input.len() > k - 11 {
535                input = input[0..k - 11].to_vec();
536            }
537            let label = get_label(&mut rng);
538
539            let pub_key: RsaPublicKey = prk.into();
540
541            let ciphertext = if let Some(ref label) = label {
542                let padding = Oaep::<D, U>::new_with_mgf_hash_and_label::<_>(label.clone());
543                pub_key.encrypt(&mut rng, padding, &input).unwrap()
544            } else {
545                let padding = Oaep::<D, U>::new_with_mgf_hash();
546                pub_key.encrypt(&mut rng, padding, &input).unwrap()
547            };
548
549            assert_ne!(input, ciphertext);
550            let blind: bool = rng.next_u32() < (1 << 31);
551
552            let padding = if let Some(label) = label {
553                Oaep::<D, U>::new_with_mgf_hash_and_label::<_>(label)
554            } else {
555                Oaep::<D, U>::new_with_mgf_hash()
556            };
557
558            let plaintext = if blind {
559                prk.decrypt(padding, &ciphertext).unwrap()
560            } else {
561                prk.decrypt_blinded(&mut rng, padding, &ciphertext).unwrap()
562            };
563
564            assert_eq!(input, plaintext);
565        }
566    }
567
568    #[test]
569    fn test_decrypt_oaep_invalid_hash() {
570        let mut rng = ChaCha8Rng::from_seed([42; 32]);
571        let priv_key = get_private_key();
572        let pub_key: RsaPublicKey = (&priv_key).into();
573        let ciphertext = pub_key
574            .encrypt(&mut rng, Oaep::<Sha1>::new(), "a_plain_text".as_bytes())
575            .unwrap();
576        assert!(
577            priv_key
578                .decrypt_blinded(
579                    &mut rng,
580                    Oaep::<Sha1>::new_with_label::<_>("label".as_bytes()),
581                    &ciphertext,
582                )
583                .is_err(),
584            "decrypt should have failed on hash verification"
585        );
586    }
587
588    #[test]
589    fn test_encrypt_decrypt_oaep_traits() {
590        let priv_key = get_private_key();
591        do_test_encrypt_decrypt_oaep_traits::<Sha1>(&priv_key);
592        do_test_encrypt_decrypt_oaep_traits::<Sha224>(&priv_key);
593        do_test_encrypt_decrypt_oaep_traits::<Sha256>(&priv_key);
594        do_test_encrypt_decrypt_oaep_traits::<Sha384>(&priv_key);
595        do_test_encrypt_decrypt_oaep_traits::<Sha512>(&priv_key);
596        do_test_encrypt_decrypt_oaep_traits::<Sha3_256>(&priv_key);
597        do_test_encrypt_decrypt_oaep_traits::<Sha3_384>(&priv_key);
598        do_test_encrypt_decrypt_oaep_traits::<Sha3_512>(&priv_key);
599
600        do_test_oaep_with_different_hashes_traits::<Sha1, Sha1>(&priv_key);
601        do_test_oaep_with_different_hashes_traits::<Sha224, Sha1>(&priv_key);
602        do_test_oaep_with_different_hashes_traits::<Sha256, Sha1>(&priv_key);
603        do_test_oaep_with_different_hashes_traits::<Sha384, Sha1>(&priv_key);
604        do_test_oaep_with_different_hashes_traits::<Sha512, Sha1>(&priv_key);
605        do_test_oaep_with_different_hashes_traits::<Sha3_256, Sha1>(&priv_key);
606        do_test_oaep_with_different_hashes_traits::<Sha3_384, Sha1>(&priv_key);
607        do_test_oaep_with_different_hashes_traits::<Sha3_512, Sha1>(&priv_key);
608    }
609
610    fn do_test_encrypt_decrypt_oaep_traits<D: Digest + FixedOutputReset>(prk: &RsaPrivateKey) {
611        do_test_oaep_with_different_hashes_traits::<D, D>(prk);
612    }
613
614    fn do_test_oaep_with_different_hashes_traits<D: Digest, MGD: Digest + FixedOutputReset>(
615        prk: &RsaPrivateKey,
616    ) {
617        let mut rng = ChaCha8Rng::from_seed([42; 32]);
618
619        let k = prk.size();
620
621        for i in 1..8 {
622            let mut input = vec![0u8; i * 8];
623            rng.fill_bytes(&mut input);
624
625            if input.len() > k - 11 {
626                input = input[0..k - 11].to_vec();
627            }
628            let label = get_label(&mut rng);
629
630            let pub_key: RsaPublicKey = prk.into();
631
632            let ciphertext = if let Some(ref label) = label {
633                let encrypting_key =
634                    EncryptingKey::<D, MGD>::new_with_label(pub_key, label.clone());
635                encrypting_key.encrypt_with_rng(&mut rng, &input).unwrap()
636            } else {
637                let encrypting_key = EncryptingKey::<D, MGD>::new(pub_key);
638                encrypting_key.encrypt_with_rng(&mut rng, &input).unwrap()
639            };
640
641            assert_ne!(input, ciphertext);
642            let blind: bool = rng.next_u32() < (1 << 31);
643
644            let decrypting_key = if let Some(ref label) = label {
645                DecryptingKey::<D, MGD>::new_with_label(prk.clone(), label.clone())
646            } else {
647                DecryptingKey::<D, MGD>::new(prk.clone())
648            };
649
650            let plaintext = if blind {
651                decrypting_key.decrypt(&ciphertext).unwrap()
652            } else {
653                decrypting_key
654                    .decrypt_with_rng(&mut rng, &ciphertext)
655                    .unwrap()
656            };
657
658            assert_eq!(input, plaintext);
659        }
660    }
661
662    #[test]
663    fn test_decrypt_oaep_invalid_hash_traits() {
664        let mut rng = ChaCha8Rng::from_seed([42; 32]);
665        let priv_key = get_private_key();
666        let pub_key: RsaPublicKey = (&priv_key).into();
667        let encrypting_key = EncryptingKey::<Sha1>::new(pub_key);
668        let decrypting_key = DecryptingKey::<Sha1>::new_with_label(priv_key, "label".as_bytes());
669        let ciphertext = encrypting_key
670            .encrypt_with_rng(&mut rng, "a_plain_text".as_bytes())
671            .unwrap();
672        assert!(
673            decrypting_key
674                .decrypt_with_rng(&mut rng, &ciphertext)
675                .is_err(),
676            "decrypt should have failed on hash verification"
677        );
678    }
679}