diff --git a/src/key.rs b/src/key.rs index 031b1e1c..6a2f7998 100644 --- a/src/key.rs +++ b/src/key.rs @@ -504,19 +504,10 @@ fn check_public_with_max_size(public_key: &impl PublicKeyParts, max_size: usize) mod tests { use super::*; use crate::internals; - use crate::oaep::Oaep; - use alloc::string::String; - use digest::{Digest, DynDigest}; use hex_literal::hex; use num_traits::{FromPrimitive, ToPrimitive}; - use rand_chacha::{ - rand_core::{RngCore, SeedableRng}, - ChaCha8Rng, - }; - use sha1::Sha1; - use sha2::{Sha224, Sha256, Sha384, Sha512}; - use sha3::{Sha3_256, Sha3_384, Sha3_512}; + use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng}; #[test] fn test_from_into() { @@ -780,195 +771,4 @@ mod tests { Error::ModulusTooLarge ); } - - fn get_private_key() -> RsaPrivateKey { - // -----BEGIN RSA PRIVATE KEY----- - // MIIEpAIBAAKCAQEA05e4TZikwmE47RtpWoEG6tkdVTvwYEG2LT/cUKBB4iK49FKW - // icG4LF5xVU9d1p+i9LYVjPDb61eBGg/DJ+HyjnT+dNO8Fmweq9wbi1e5NMqL5bAL - // TymXW8yZrK9BW1m7KKZ4K7QaLDwpdrPBjbre9i8AxrsiZkAJUJbAzGDSL+fvmH11 - // xqgbENlr8pICivEQ3HzBu8Q9Iq2rN5oM1dgHjMeA/1zWIJ3qNMkiz3hPdxfkKNdb - // WuyP8w5fAUFRB2bi4KuNRzyE6HELK5gifD2wlTN600UvGeK5v7zN2BSKv2d2+lUn - // debnWVbkUimuWpxGlJurHmIvDkj1ZSSoTtNIOwIDAQABAoIBAQDE5wxokWLJTGYI - // KBkbUrTYOSEV30hqmtvoMeRY1zlYMg3Bt1VFbpNwHpcC12+wuS+Q4B0f4kgVMoH+ - // eaqXY6kvrmnY1+zRRN4p+hNb0U+Vc+NJ5FAx47dpgvWDADgmxVLomjl8Gga9IWNI - // hjDZLowrtkPXq+9wDaldaFyUFImkb1S1MW9itdLDp/G70TTLNzU6RGg/3J2V02RY - // 3iL2xEBX/nSgpDbEMI9z9NpC81xHrBanE41IOvyR5B3DoRJzguDA9RGbAiG0/GOd - // a5w4F3pt6bUm69iMONeYLAf5ig79h31Qiq4nW5RpFcAuLhEG0XXXTsZ3f16A0SwF - // PZx74eNBAoGBAPgnu/OkGHfHzFmuv0LtSynDLe/LjtloY9WwkKBaiTDdYkohydz5 - // g4Vo/foN9luEYqXyrJE9bFb5dVMr2OePsHvUBcqZpIS89Z8Bm73cs5M/K85wYwC0 - // 97EQEgxd+QGBWQZ8NdowYaVshjWlK1QnOzEnG0MR8Hld9gIeY1XhpC5hAoGBANpI - // F84Aid028q3mo/9BDHPsNL8bT2vaOEMb/t4RzvH39u+nDl+AY6Ox9uFylv+xX+76 - // CRKgMluNH9ZaVZ5xe1uWHsNFBy4OxSA9A0QdKa9NZAVKBFB0EM8dp457YRnZCexm - // 5q1iW/mVsnmks8W+fYlc18W5xMSX/ecwkW/NtOQbAoGAHabpz4AhKFbodSLrWbzv - // CUt4NroVFKdjnoodjfujfwJFF2SYMV5jN9LG3lVCxca43ulzc1tqka33Nfv8TBcg - // WHuKQZ5ASVgm5VwU1wgDMSoQOve07MWy/yZTccTc1zA0ihDXgn3bfR/NnaVh2wlh - // CkuI92eyW1494hztc7qlmqECgYEA1zenyOQ9ChDIW/ABGIahaZamNxsNRrDFMl3j - // AD+cxHSRU59qC32CQH8ShRy/huHzTaPX2DZ9EEln76fnrS4Ey7uLH0rrFl1XvT6K - // /timJgLvMEvXTx/xBtUdRN2fUqXtI9odbSyCtOYFL+zVl44HJq2UzY4pVRDrNcxs - // SUkQJqsCgYBSaNfPBzR5rrstLtTdZrjImRW1LRQeDEky9WsMDtCTYUGJTsTSfVO8 - // hkU82MpbRVBFIYx+GWIJwcZRcC7OCQoV48vMJllxMAAjqG/p00rVJ+nvA7et/nNu - // BoB0er/UmDm4Ly/97EO9A0PKMOE5YbMq9s3t3RlWcsdrU7dvw+p2+A== - // -----END RSA PRIVATE KEY----- - - RsaPrivateKey::from_components( - BigUint::parse_bytes(b"00d397b84d98a4c26138ed1b695a8106ead91d553bf06041b62d3fdc50a041e222b8f4529689c1b82c5e71554f5dd69fa2f4b6158cf0dbeb57811a0fc327e1f28e74fe74d3bc166c1eabdc1b8b57b934ca8be5b00b4f29975bcc99acaf415b59bb28a6782bb41a2c3c2976b3c18dbadef62f00c6bb226640095096c0cc60d22fe7ef987d75c6a81b10d96bf292028af110dc7cc1bbc43d22adab379a0cd5d8078cc780ff5cd6209dea34c922cf784f7717e428d75b5aec8ff30e5f0141510766e2e0ab8d473c84e8710b2b98227c3db095337ad3452f19e2b9bfbccdd8148abf6776fa552775e6e75956e45229ae5a9c46949bab1e622f0e48f56524a84ed3483b", 16).unwrap(), - BigUint::from_u64(65537).unwrap(), - BigUint::parse_bytes(b"00c4e70c689162c94c660828191b52b4d8392115df486a9adbe831e458d73958320dc1b755456e93701e9702d76fb0b92f90e01d1fe248153281fe79aa9763a92fae69d8d7ecd144de29fa135bd14f9573e349e45031e3b76982f583003826c552e89a397c1a06bd2163488630d92e8c2bb643d7abef700da95d685c941489a46f54b5316f62b5d2c3a7f1bbd134cb37353a44683fdc9d95d36458de22f6c44057fe74a0a436c4308f73f4da42f35c47ac16a7138d483afc91e41dc3a1127382e0c0f5119b0221b4fc639d6b9c38177a6de9b526ebd88c38d7982c07f98a0efd877d508aae275b946915c02e2e1106d175d74ec6777f5e80d12c053d9c7be1e341", 16).unwrap(), - vec![ - BigUint::parse_bytes(b"00f827bbf3a41877c7cc59aebf42ed4b29c32defcb8ed96863d5b090a05a8930dd624a21c9dcf9838568fdfa0df65b8462a5f2ac913d6c56f975532bd8e78fb07bd405ca99a484bcf59f019bbddcb3933f2bce706300b4f7b110120c5df9018159067c35da3061a56c8635a52b54273b31271b4311f0795df6021e6355e1a42e61",16).unwrap(), - BigUint::parse_bytes(b"00da4817ce0089dd36f2ade6a3ff410c73ec34bf1b4f6bda38431bfede11cef1f7f6efa70e5f8063a3b1f6e17296ffb15feefa0912a0325b8d1fd65a559e717b5b961ec345072e0ec5203d03441d29af4d64054a04507410cf1da78e7b6119d909ec66e6ad625bf995b279a4b3c5be7d895cd7c5b9c4c497fde730916fcdb4e41b", 16).unwrap() - ], - ).unwrap() - } - - #[test] - fn test_encrypt_decrypt_oaep() { - let priv_key = get_private_key(); - do_test_encrypt_decrypt_oaep::(&priv_key); - do_test_encrypt_decrypt_oaep::(&priv_key); - do_test_encrypt_decrypt_oaep::(&priv_key); - do_test_encrypt_decrypt_oaep::(&priv_key); - do_test_encrypt_decrypt_oaep::(&priv_key); - do_test_encrypt_decrypt_oaep::(&priv_key); - do_test_encrypt_decrypt_oaep::(&priv_key); - do_test_encrypt_decrypt_oaep::(&priv_key); - - do_test_oaep_with_different_hashes::(&priv_key); - do_test_oaep_with_different_hashes::(&priv_key); - do_test_oaep_with_different_hashes::(&priv_key); - do_test_oaep_with_different_hashes::(&priv_key); - do_test_oaep_with_different_hashes::(&priv_key); - do_test_oaep_with_different_hashes::(&priv_key); - do_test_oaep_with_different_hashes::(&priv_key); - do_test_oaep_with_different_hashes::(&priv_key); - } - - fn get_label(rng: &mut ChaCha8Rng) -> Option { - const GEN_ASCII_STR_CHARSET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\ - abcdefghijklmnopqrstuvwxyz\ - 0123456789=+"; - - let mut buf = [0u8; 32]; - rng.fill_bytes(&mut buf); - if buf[0] < (1 << 7) { - for v in buf.iter_mut() { - *v = GEN_ASCII_STR_CHARSET[(*v >> 2) as usize]; - } - Some(core::str::from_utf8(&buf).unwrap().to_string()) - } else { - None - } - } - - fn do_test_encrypt_decrypt_oaep( - prk: &RsaPrivateKey, - ) { - let mut rng = ChaCha8Rng::from_seed([42; 32]); - - let k = prk.size(); - - for i in 1..8 { - let mut input = vec![0u8; i * 8]; - rng.fill_bytes(&mut input); - - if input.len() > k - 11 { - input = input[0..k - 11].to_vec(); - } - let label = get_label(&mut rng); - - let pub_key: RsaPublicKey = prk.into(); - - let ciphertext = if let Some(ref label) = label { - let padding = Oaep::new_with_label::(label); - pub_key.encrypt(&mut rng, padding, &input).unwrap() - } else { - let padding = Oaep::new::(); - pub_key.encrypt(&mut rng, padding, &input).unwrap() - }; - - assert_ne!(input, ciphertext); - let blind: bool = rng.next_u32() < (1 << 31); - - let padding = if let Some(ref label) = label { - Oaep::new_with_label::(label) - } else { - Oaep::new::() - }; - - let plaintext = if blind { - prk.decrypt(padding, &ciphertext).unwrap() - } else { - prk.decrypt_blinded(&mut rng, padding, &ciphertext).unwrap() - }; - - assert_eq!(input, plaintext); - } - } - - fn do_test_oaep_with_different_hashes< - D: 'static + Digest + DynDigest + Send + Sync, - U: 'static + Digest + DynDigest + Send + Sync, - >( - prk: &RsaPrivateKey, - ) { - let mut rng = ChaCha8Rng::from_seed([42; 32]); - - let k = prk.size(); - - for i in 1..8 { - let mut input = vec![0u8; i * 8]; - rng.fill_bytes(&mut input); - - if input.len() > k - 11 { - input = input[0..k - 11].to_vec(); - } - let label = get_label(&mut rng); - - let pub_key: RsaPublicKey = prk.into(); - - let ciphertext = if let Some(ref label) = label { - let padding = Oaep::new_with_mgf_hash_and_label::(label); - pub_key.encrypt(&mut rng, padding, &input).unwrap() - } else { - let padding = Oaep::new_with_mgf_hash::(); - pub_key.encrypt(&mut rng, padding, &input).unwrap() - }; - - assert_ne!(input, ciphertext); - let blind: bool = rng.next_u32() < (1 << 31); - - let padding = if let Some(ref label) = label { - Oaep::new_with_mgf_hash_and_label::(label) - } else { - Oaep::new_with_mgf_hash::() - }; - - let plaintext = if blind { - prk.decrypt(padding, &ciphertext).unwrap() - } else { - prk.decrypt_blinded(&mut rng, padding, &ciphertext).unwrap() - }; - - assert_eq!(input, plaintext); - } - } - #[test] - fn test_decrypt_oaep_invalid_hash() { - let mut rng = ChaCha8Rng::from_seed([42; 32]); - let priv_key = get_private_key(); - let pub_key: RsaPublicKey = (&priv_key).into(); - let ciphertext = pub_key - .encrypt(&mut rng, Oaep::new::(), "a_plain_text".as_bytes()) - .unwrap(); - assert!( - priv_key - .decrypt_blinded( - &mut rng, - Oaep::new_with_label::("label"), - &ciphertext, - ) - .is_err(), - "decrypt should have failed on hash verification" - ); - } } diff --git a/src/lib.rs b/src/lib.rs index c11771a0..bf90672d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -220,13 +220,14 @@ pub use signature; pub mod algorithms; pub mod errors; +pub mod oaep; pub mod pkcs1v15; pub mod pss; +pub mod traits; mod dummy_rng; mod encoding; mod key; -mod oaep; mod padding; mod raw; diff --git a/src/oaep.rs b/src/oaep.rs index aab4d945..f792a422 100644 --- a/src/oaep.rs +++ b/src/oaep.rs @@ -1,18 +1,26 @@ +//! Encryption and Decryption using [OAEP padding](https://datatracker.ietf.org/doc/html/rfc8017#section-7.1). +//! +//! # Usage +//! +//! See [code example in the toplevel rustdoc](../index.html#oaep-encryption). use alloc::boxed::Box; use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; use core::fmt; +use core::marker::PhantomData; use rand_core::CryptoRngCore; -use digest::{Digest, DynDigest}; +use digest::{Digest, DynDigest, FixedOutputReset}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; use zeroize::Zeroizing; -use crate::algorithms::mgf1_xor; +use crate::algorithms::{mgf1_xor, mgf1_xor_digest}; +use crate::dummy_rng::DummyRng; use crate::errors::{Error, Result}; -use crate::key::{self, PrivateKey, PublicKey}; +use crate::key::{self, PrivateKey, PublicKey, RsaPrivateKey, RsaPublicKey}; use crate::padding::PaddingScheme; +use crate::traits::{Decryptor, RandomizedDecryptor, RandomizedEncryptor}; // 2**61 -1 (pow is not const yet) // TODO: This is the maximum for SHA-1, unclear from the RFC what the values are for other hashing functions. @@ -166,6 +174,46 @@ impl fmt::Debug for Oaep { } } +#[inline] +fn encrypt_internal< + 'a, + R: CryptoRngCore + ?Sized, + K: PublicKey, + MGF: FnMut(&mut [u8], &mut [u8]) -> (), +>( + rng: &mut R, + pub_key: &K, + msg: &[u8], + p_hash: &[u8], + h_size: usize, + mut mgf: MGF, +) -> Result> { + key::check_public(pub_key)?; + + let k = pub_key.size(); + + if msg.len() + 2 * h_size + 2 > k { + return Err(Error::MessageTooLong); + } + + let mut em = Zeroizing::new(vec![0u8; k]); + + let (_, payload) = em.split_at_mut(1); + let (seed, db) = payload.split_at_mut(h_size); + rng.fill_bytes(seed); + + // Data block DB = pHash || PS || 01 || M + let db_len = k - h_size - 1; + + db[0..h_size].copy_from_slice(p_hash); + db[db_len - msg.len() - 1] = 1; + db[db_len - msg.len()..].copy_from_slice(msg); + + mgf(seed, db); + + pub_key.raw_encryption_primitive(&em, pub_key.size()) +} + /// Encrypts the given message with RSA and the padding scheme from /// [PKCS#1 OAEP]. /// @@ -174,7 +222,7 @@ impl fmt::Debug for Oaep { /// /// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1 #[inline] -pub fn encrypt( +fn encrypt( rng: &mut R, pub_key: &K, msg: &[u8], @@ -182,40 +230,109 @@ pub fn encrypt( mgf_digest: &mut dyn DynDigest, label: Option, ) -> Result> { - key::check_public(pub_key)?; - - let k = pub_key.size(); - let h_size = digest.output_size(); - if msg.len() + 2 * h_size + 2 > k { - return Err(Error::MessageTooLong); + let label = label.unwrap_or_default(); + if label.len() as u64 > MAX_LABEL_LEN { + return Err(Error::LabelTooLong); } + digest.update(label.as_bytes()); + let p_hash = digest.finalize_reset(); + + encrypt_internal(rng, pub_key, msg, &p_hash, h_size, |seed, db| { + mgf1_xor(db, mgf_digest, seed); + mgf1_xor(seed, mgf_digest, db); + }) +} + +/// Encrypts the given message with RSA and the padding scheme from +/// [PKCS#1 OAEP]. +/// +/// The message must be no longer than the length of the public modulus minus +/// `2 + (2 * hash.size())`. +/// +/// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1 +#[inline] +fn encrypt_digest< + R: CryptoRngCore + ?Sized, + K: PublicKey, + D: Digest, + MGD: Digest + FixedOutputReset, +>( + rng: &mut R, + pub_key: &K, + msg: &[u8], + label: Option, +) -> Result> { + let h_size = ::output_size(); + let label = label.unwrap_or_default(); if label.len() as u64 > MAX_LABEL_LEN { return Err(Error::LabelTooLong); } - let mut em = Zeroizing::new(vec![0u8; k]); + let p_hash = D::digest(label.as_bytes()); - let (_, payload) = em.split_at_mut(1); - let (seed, db) = payload.split_at_mut(h_size); - rng.fill_bytes(seed); + encrypt_internal(rng, pub_key, msg, &p_hash, h_size, |seed, db| { + let mut mgf_digest = MGD::new(); + mgf1_xor_digest(db, &mut mgf_digest, seed); + mgf1_xor_digest(seed, &mut mgf_digest, db); + }) +} - // Data block DB = pHash || PS || 01 || M - let db_len = k - h_size - 1; +/// Decrypts a plaintext using RSA and the padding scheme from [PKCS#1 OAEP]. +/// +/// If an `rng` is passed, it uses RSA blinding to avoid timing side-channel attacks. +/// +/// Note that whether this function returns an error or not discloses secret +/// information. If an attacker can cause this function to run repeatedly and +/// learn whether each instance returned an error then they can decrypt and +/// forge signatures as if they had the private key. +/// +/// See `decrypt_session_key` for a way of solving this problem. +/// +/// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1 +#[inline] +fn decrypt( + rng: Option<&mut R>, + priv_key: &SK, + ciphertext: &[u8], + digest: &mut dyn DynDigest, + mgf_digest: &mut dyn DynDigest, + label: Option, +) -> Result> { + key::check_public(priv_key)?; + + let h_size = digest.output_size(); + + let label = label.unwrap_or_default(); + if label.len() as u64 > MAX_LABEL_LEN { + return Err(Error::Decryption); + } digest.update(label.as_bytes()); - let p_hash = digest.finalize_reset(); - db[0..h_size].copy_from_slice(&p_hash); - db[db_len - msg.len() - 1] = 1; - db[db_len - msg.len()..].copy_from_slice(msg); - mgf1_xor(db, mgf_digest, seed); - mgf1_xor(seed, mgf_digest, db); + let expected_p_hash = digest.finalize_reset(); + + let res = decrypt_inner( + rng, + priv_key, + ciphertext, + h_size, + &expected_p_hash, + |seed, db| { + mgf1_xor(seed, mgf_digest, db); + mgf1_xor(db, mgf_digest, seed); + }, + )?; + if res.is_none().into() { + return Err(Error::Decryption); + } + + let (out, index) = res.unwrap(); - pub_key.raw_encryption_primitive(&em, pub_key.size()) + Ok(out[index as usize..].to_vec()) } /// Decrypts a plaintext using RSA and the padding scheme from [PKCS#1 OAEP]. @@ -231,17 +348,40 @@ pub fn encrypt( /// /// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1 #[inline] -pub fn decrypt( +fn decrypt_digest< + R: CryptoRngCore + ?Sized, + SK: PrivateKey, + D: Digest, + MGD: Digest + FixedOutputReset, +>( rng: Option<&mut R>, priv_key: &SK, ciphertext: &[u8], - digest: &mut dyn DynDigest, - mgf_digest: &mut dyn DynDigest, label: Option, ) -> Result> { key::check_public(priv_key)?; - let res = decrypt_inner(rng, priv_key, ciphertext, digest, mgf_digest, label)?; + let h_size = ::output_size(); + + let label = label.unwrap_or_default(); + if label.len() as u64 > MAX_LABEL_LEN { + return Err(Error::LabelTooLong); + } + + let expected_p_hash = D::digest(label.as_bytes()); + + let res = decrypt_inner( + rng, + priv_key, + ciphertext, + h_size, + &expected_p_hash, + |seed, db| { + let mut mgf_digest = MGD::new(); + mgf1_xor_digest(seed, &mut mgf_digest, db); + mgf1_xor_digest(db, &mut mgf_digest, seed); + }, + )?; if res.is_none().into() { return Err(Error::Decryption); } @@ -255,43 +395,35 @@ pub fn decrypt( /// `rng` is given. It returns one or zero in valid that indicates whether the /// plaintext was correctly structured. #[inline] -fn decrypt_inner( +fn decrypt_inner< + R: CryptoRngCore + ?Sized, + SK: PrivateKey, + MGF: FnMut(&mut [u8], &mut [u8]) -> (), +>( rng: Option<&mut R>, priv_key: &SK, ciphertext: &[u8], - digest: &mut dyn DynDigest, - mgf_digest: &mut dyn DynDigest, - label: Option, + h_size: usize, + expected_p_hash: &[u8], + mut mgf: MGF, ) -> Result, u32)>> { let k = priv_key.size(); if k < 11 { return Err(Error::Decryption); } - let h_size = digest.output_size(); - if ciphertext.len() != k || k < h_size * 2 + 2 { return Err(Error::Decryption); } let mut em = priv_key.raw_decryption_primitive(rng, ciphertext, priv_key.size())?; - let label = label.unwrap_or_default(); - if label.len() as u64 > MAX_LABEL_LEN { - return Err(Error::LabelTooLong); - } - - digest.update(label.as_bytes()); - - let expected_p_hash = &*digest.finalize_reset(); - let first_byte_is_zero = em[0].ct_eq(&0u8); let (_, payload) = em.split_at_mut(1); let (seed, db) = payload.split_at_mut(h_size); - mgf1_xor(seed, mgf_digest, db); - mgf1_xor(db, mgf_digest, seed); + mgf(seed, db); let hash_are_equal = db[0..h_size].ct_eq(expected_p_hash); @@ -316,3 +448,436 @@ fn decrypt_inner( Ok(CtOption::new((em, index + 2 + (h_size * 2) as u32), valid)) } + +/// Encryption key for PKCS#1 v1.5 encryption as described in [RFC8017 § 7.1]. +/// +/// [RFC8017 § 7.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1 +#[derive(Debug, Clone)] +pub struct EncryptingKey +where + D: Digest, + MGD: Digest + FixedOutputReset, +{ + inner: RsaPublicKey, + label: Option, + phantom: PhantomData, + mg_phantom: PhantomData, +} + +impl EncryptingKey +where + D: Digest, + MGD: Digest + FixedOutputReset, +{ + /// Create a new verifying key from an RSA public key. + pub fn new(key: RsaPublicKey) -> Self { + Self { + inner: key, + label: None, + phantom: Default::default(), + mg_phantom: Default::default(), + } + } + + /// Create a new verifying key from an RSA public key using provided label + pub fn new_with_label>(key: RsaPublicKey, label: S) -> Self { + Self { + inner: key, + label: Some(label.as_ref().to_string()), + phantom: Default::default(), + mg_phantom: Default::default(), + } + } +} + +impl RandomizedEncryptor for EncryptingKey +where + D: Digest, + MGD: Digest + FixedOutputReset, +{ + fn encrypt_with_rng( + &self, + rng: &mut R, + msg: &[u8], + ) -> Result> { + encrypt_digest::<_, _, D, MGD>(rng, &self.inner, msg, self.label.as_ref().cloned()) + } +} + +/// Decryption key for PKCS#1 v1.5 decryption as described in [RFC8017 § 7.1]. +/// +/// [RFC8017 § 7.1]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1 +#[derive(Debug, Clone)] +pub struct DecryptingKey +where + D: Digest, + MGD: Digest + FixedOutputReset, +{ + inner: RsaPrivateKey, + label: Option, + phantom: PhantomData, + mg_phantom: PhantomData, +} + +impl DecryptingKey +where + D: Digest, + MGD: Digest + FixedOutputReset, +{ + /// Create a new verifying key from an RSA public key. + pub fn new(key: RsaPrivateKey) -> Self { + Self { + inner: key, + label: None, + phantom: Default::default(), + mg_phantom: Default::default(), + } + } + + /// Create a new verifying key from an RSA public key using provided label + pub fn new_with_label>(key: RsaPrivateKey, label: S) -> Self { + Self { + inner: key, + label: Some(label.as_ref().to_string()), + phantom: Default::default(), + mg_phantom: Default::default(), + } + } +} + +impl Decryptor for DecryptingKey +where + D: Digest, + MGD: Digest + FixedOutputReset, +{ + fn decrypt(&self, ciphertext: &[u8]) -> Result> { + decrypt_digest::( + None, + &self.inner, + ciphertext, + self.label.as_ref().cloned(), + ) + } +} + +impl RandomizedDecryptor for DecryptingKey +where + D: Digest, + MGD: Digest + FixedOutputReset, +{ + fn decrypt_with_rng( + &self, + rng: &mut R, + ciphertext: &[u8], + ) -> Result> { + decrypt_digest::<_, _, D, MGD>( + Some(rng), + &self.inner, + ciphertext, + self.label.as_ref().cloned(), + ) + } +} + +#[cfg(test)] +mod tests { + use crate::key::{PublicKey, PublicKeyParts, RsaPrivateKey, RsaPublicKey}; + use crate::oaep::{DecryptingKey, EncryptingKey, Oaep}; + use crate::traits::{Decryptor, RandomizedDecryptor, RandomizedEncryptor}; + + use alloc::string::String; + use digest::{Digest, DynDigest, FixedOutputReset}; + use num_bigint::BigUint; + use num_traits::FromPrimitive; + use rand_chacha::{ + rand_core::{RngCore, SeedableRng}, + ChaCha8Rng, + }; + use sha1::Sha1; + use sha2::{Sha224, Sha256, Sha384, Sha512}; + use sha3::{Sha3_256, Sha3_384, Sha3_512}; + + fn get_private_key() -> RsaPrivateKey { + // -----BEGIN RSA PRIVATE KEY----- + // MIIEpAIBAAKCAQEA05e4TZikwmE47RtpWoEG6tkdVTvwYEG2LT/cUKBB4iK49FKW + // icG4LF5xVU9d1p+i9LYVjPDb61eBGg/DJ+HyjnT+dNO8Fmweq9wbi1e5NMqL5bAL + // TymXW8yZrK9BW1m7KKZ4K7QaLDwpdrPBjbre9i8AxrsiZkAJUJbAzGDSL+fvmH11 + // xqgbENlr8pICivEQ3HzBu8Q9Iq2rN5oM1dgHjMeA/1zWIJ3qNMkiz3hPdxfkKNdb + // WuyP8w5fAUFRB2bi4KuNRzyE6HELK5gifD2wlTN600UvGeK5v7zN2BSKv2d2+lUn + // debnWVbkUimuWpxGlJurHmIvDkj1ZSSoTtNIOwIDAQABAoIBAQDE5wxokWLJTGYI + // KBkbUrTYOSEV30hqmtvoMeRY1zlYMg3Bt1VFbpNwHpcC12+wuS+Q4B0f4kgVMoH+ + // eaqXY6kvrmnY1+zRRN4p+hNb0U+Vc+NJ5FAx47dpgvWDADgmxVLomjl8Gga9IWNI + // hjDZLowrtkPXq+9wDaldaFyUFImkb1S1MW9itdLDp/G70TTLNzU6RGg/3J2V02RY + // 3iL2xEBX/nSgpDbEMI9z9NpC81xHrBanE41IOvyR5B3DoRJzguDA9RGbAiG0/GOd + // a5w4F3pt6bUm69iMONeYLAf5ig79h31Qiq4nW5RpFcAuLhEG0XXXTsZ3f16A0SwF + // PZx74eNBAoGBAPgnu/OkGHfHzFmuv0LtSynDLe/LjtloY9WwkKBaiTDdYkohydz5 + // g4Vo/foN9luEYqXyrJE9bFb5dVMr2OePsHvUBcqZpIS89Z8Bm73cs5M/K85wYwC0 + // 97EQEgxd+QGBWQZ8NdowYaVshjWlK1QnOzEnG0MR8Hld9gIeY1XhpC5hAoGBANpI + // F84Aid028q3mo/9BDHPsNL8bT2vaOEMb/t4RzvH39u+nDl+AY6Ox9uFylv+xX+76 + // CRKgMluNH9ZaVZ5xe1uWHsNFBy4OxSA9A0QdKa9NZAVKBFB0EM8dp457YRnZCexm + // 5q1iW/mVsnmks8W+fYlc18W5xMSX/ecwkW/NtOQbAoGAHabpz4AhKFbodSLrWbzv + // CUt4NroVFKdjnoodjfujfwJFF2SYMV5jN9LG3lVCxca43ulzc1tqka33Nfv8TBcg + // WHuKQZ5ASVgm5VwU1wgDMSoQOve07MWy/yZTccTc1zA0ihDXgn3bfR/NnaVh2wlh + // CkuI92eyW1494hztc7qlmqECgYEA1zenyOQ9ChDIW/ABGIahaZamNxsNRrDFMl3j + // AD+cxHSRU59qC32CQH8ShRy/huHzTaPX2DZ9EEln76fnrS4Ey7uLH0rrFl1XvT6K + // /timJgLvMEvXTx/xBtUdRN2fUqXtI9odbSyCtOYFL+zVl44HJq2UzY4pVRDrNcxs + // SUkQJqsCgYBSaNfPBzR5rrstLtTdZrjImRW1LRQeDEky9WsMDtCTYUGJTsTSfVO8 + // hkU82MpbRVBFIYx+GWIJwcZRcC7OCQoV48vMJllxMAAjqG/p00rVJ+nvA7et/nNu + // BoB0er/UmDm4Ly/97EO9A0PKMOE5YbMq9s3t3RlWcsdrU7dvw+p2+A== + // -----END RSA PRIVATE KEY----- + + RsaPrivateKey::from_components( + BigUint::parse_bytes(b"00d397b84d98a4c26138ed1b695a8106ead91d553bf06041b62d3fdc50a041e222b8f4529689c1b82c5e71554f5dd69fa2f4b6158cf0dbeb57811a0fc327e1f28e74fe74d3bc166c1eabdc1b8b57b934ca8be5b00b4f29975bcc99acaf415b59bb28a6782bb41a2c3c2976b3c18dbadef62f00c6bb226640095096c0cc60d22fe7ef987d75c6a81b10d96bf292028af110dc7cc1bbc43d22adab379a0cd5d8078cc780ff5cd6209dea34c922cf784f7717e428d75b5aec8ff30e5f0141510766e2e0ab8d473c84e8710b2b98227c3db095337ad3452f19e2b9bfbccdd8148abf6776fa552775e6e75956e45229ae5a9c46949bab1e622f0e48f56524a84ed3483b", 16).unwrap(), + BigUint::from_u64(65537).unwrap(), + BigUint::parse_bytes(b"00c4e70c689162c94c660828191b52b4d8392115df486a9adbe831e458d73958320dc1b755456e93701e9702d76fb0b92f90e01d1fe248153281fe79aa9763a92fae69d8d7ecd144de29fa135bd14f9573e349e45031e3b76982f583003826c552e89a397c1a06bd2163488630d92e8c2bb643d7abef700da95d685c941489a46f54b5316f62b5d2c3a7f1bbd134cb37353a44683fdc9d95d36458de22f6c44057fe74a0a436c4308f73f4da42f35c47ac16a7138d483afc91e41dc3a1127382e0c0f5119b0221b4fc639d6b9c38177a6de9b526ebd88c38d7982c07f98a0efd877d508aae275b946915c02e2e1106d175d74ec6777f5e80d12c053d9c7be1e341", 16).unwrap(), + vec![ + BigUint::parse_bytes(b"00f827bbf3a41877c7cc59aebf42ed4b29c32defcb8ed96863d5b090a05a8930dd624a21c9dcf9838568fdfa0df65b8462a5f2ac913d6c56f975532bd8e78fb07bd405ca99a484bcf59f019bbddcb3933f2bce706300b4f7b110120c5df9018159067c35da3061a56c8635a52b54273b31271b4311f0795df6021e6355e1a42e61",16).unwrap(), + BigUint::parse_bytes(b"00da4817ce0089dd36f2ade6a3ff410c73ec34bf1b4f6bda38431bfede11cef1f7f6efa70e5f8063a3b1f6e17296ffb15feefa0912a0325b8d1fd65a559e717b5b961ec345072e0ec5203d03441d29af4d64054a04507410cf1da78e7b6119d909ec66e6ad625bf995b279a4b3c5be7d895cd7c5b9c4c497fde730916fcdb4e41b", 16).unwrap() + ], + ).unwrap() + } + + #[test] + fn test_encrypt_decrypt_oaep() { + let priv_key = get_private_key(); + do_test_encrypt_decrypt_oaep::(&priv_key); + do_test_encrypt_decrypt_oaep::(&priv_key); + do_test_encrypt_decrypt_oaep::(&priv_key); + do_test_encrypt_decrypt_oaep::(&priv_key); + do_test_encrypt_decrypt_oaep::(&priv_key); + do_test_encrypt_decrypt_oaep::(&priv_key); + do_test_encrypt_decrypt_oaep::(&priv_key); + do_test_encrypt_decrypt_oaep::(&priv_key); + + do_test_oaep_with_different_hashes::(&priv_key); + do_test_oaep_with_different_hashes::(&priv_key); + do_test_oaep_with_different_hashes::(&priv_key); + do_test_oaep_with_different_hashes::(&priv_key); + do_test_oaep_with_different_hashes::(&priv_key); + do_test_oaep_with_different_hashes::(&priv_key); + do_test_oaep_with_different_hashes::(&priv_key); + do_test_oaep_with_different_hashes::(&priv_key); + } + + fn get_label(rng: &mut ChaCha8Rng) -> Option { + const GEN_ASCII_STR_CHARSET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\ + abcdefghijklmnopqrstuvwxyz\ + 0123456789=+"; + + let mut buf = [0u8; 32]; + rng.fill_bytes(&mut buf); + if buf[0] < (1 << 7) { + for v in buf.iter_mut() { + *v = GEN_ASCII_STR_CHARSET[(*v >> 2) as usize]; + } + Some(core::str::from_utf8(&buf).unwrap().to_string()) + } else { + None + } + } + + fn do_test_encrypt_decrypt_oaep( + prk: &RsaPrivateKey, + ) { + let mut rng = ChaCha8Rng::from_seed([42; 32]); + + let k = prk.size(); + + for i in 1..8 { + let mut input = vec![0u8; i * 8]; + rng.fill_bytes(&mut input); + + if input.len() > k - 11 { + input = input[0..k - 11].to_vec(); + } + let label = get_label(&mut rng); + + let pub_key: RsaPublicKey = prk.into(); + + let ciphertext = if let Some(ref label) = label { + let padding = Oaep::new_with_label::(label); + pub_key.encrypt(&mut rng, padding, &input).unwrap() + } else { + let padding = Oaep::new::(); + pub_key.encrypt(&mut rng, padding, &input).unwrap() + }; + + assert_ne!(input, ciphertext); + let blind: bool = rng.next_u32() < (1 << 31); + + let padding = if let Some(ref label) = label { + Oaep::new_with_label::(label) + } else { + Oaep::new::() + }; + + let plaintext = if blind { + prk.decrypt(padding, &ciphertext).unwrap() + } else { + prk.decrypt_blinded(&mut rng, padding, &ciphertext).unwrap() + }; + + assert_eq!(input, plaintext); + } + } + + fn do_test_oaep_with_different_hashes< + D: 'static + Digest + DynDigest + Send + Sync, + U: 'static + Digest + DynDigest + Send + Sync, + >( + prk: &RsaPrivateKey, + ) { + let mut rng = ChaCha8Rng::from_seed([42; 32]); + + let k = prk.size(); + + for i in 1..8 { + let mut input = vec![0u8; i * 8]; + rng.fill_bytes(&mut input); + + if input.len() > k - 11 { + input = input[0..k - 11].to_vec(); + } + let label = get_label(&mut rng); + + let pub_key: RsaPublicKey = prk.into(); + + let ciphertext = if let Some(ref label) = label { + let padding = Oaep::new_with_mgf_hash_and_label::(label); + pub_key.encrypt(&mut rng, padding, &input).unwrap() + } else { + let padding = Oaep::new_with_mgf_hash::(); + pub_key.encrypt(&mut rng, padding, &input).unwrap() + }; + + assert_ne!(input, ciphertext); + let blind: bool = rng.next_u32() < (1 << 31); + + let padding = if let Some(ref label) = label { + Oaep::new_with_mgf_hash_and_label::(label) + } else { + Oaep::new_with_mgf_hash::() + }; + + let plaintext = if blind { + prk.decrypt(padding, &ciphertext).unwrap() + } else { + prk.decrypt_blinded(&mut rng, padding, &ciphertext).unwrap() + }; + + assert_eq!(input, plaintext); + } + } + + #[test] + fn test_decrypt_oaep_invalid_hash() { + let mut rng = ChaCha8Rng::from_seed([42; 32]); + let priv_key = get_private_key(); + let pub_key: RsaPublicKey = (&priv_key).into(); + let ciphertext = pub_key + .encrypt(&mut rng, Oaep::new::(), "a_plain_text".as_bytes()) + .unwrap(); + assert!( + priv_key + .decrypt_blinded( + &mut rng, + Oaep::new_with_label::("label"), + &ciphertext, + ) + .is_err(), + "decrypt should have failed on hash verification" + ); + } + + #[test] + fn test_encrypt_decrypt_oaep_traits() { + let priv_key = get_private_key(); + do_test_encrypt_decrypt_oaep_traits::(&priv_key); + do_test_encrypt_decrypt_oaep_traits::(&priv_key); + do_test_encrypt_decrypt_oaep_traits::(&priv_key); + do_test_encrypt_decrypt_oaep_traits::(&priv_key); + do_test_encrypt_decrypt_oaep_traits::(&priv_key); + do_test_encrypt_decrypt_oaep_traits::(&priv_key); + do_test_encrypt_decrypt_oaep_traits::(&priv_key); + do_test_encrypt_decrypt_oaep_traits::(&priv_key); + + do_test_oaep_with_different_hashes_traits::(&priv_key); + do_test_oaep_with_different_hashes_traits::(&priv_key); + do_test_oaep_with_different_hashes_traits::(&priv_key); + do_test_oaep_with_different_hashes_traits::(&priv_key); + do_test_oaep_with_different_hashes_traits::(&priv_key); + do_test_oaep_with_different_hashes_traits::(&priv_key); + do_test_oaep_with_different_hashes_traits::(&priv_key); + do_test_oaep_with_different_hashes_traits::(&priv_key); + } + + fn do_test_encrypt_decrypt_oaep_traits(prk: &RsaPrivateKey) { + do_test_oaep_with_different_hashes_traits::(prk); + } + + fn do_test_oaep_with_different_hashes_traits( + prk: &RsaPrivateKey, + ) { + let mut rng = ChaCha8Rng::from_seed([42; 32]); + + let k = prk.size(); + + for i in 1..8 { + let mut input = vec![0u8; i * 8]; + rng.fill_bytes(&mut input); + + if input.len() > k - 11 { + input = input[0..k - 11].to_vec(); + } + let label = get_label(&mut rng); + + let pub_key: RsaPublicKey = prk.into(); + + let ciphertext = if let Some(ref label) = label { + let encrypting_key = + EncryptingKey::::new_with_label(pub_key, label.clone()); + encrypting_key.encrypt_with_rng(&mut rng, &input).unwrap() + } else { + let encrypting_key = EncryptingKey::::new(pub_key); + encrypting_key.encrypt_with_rng(&mut rng, &input).unwrap() + }; + + assert_ne!(input, ciphertext); + let blind: bool = rng.next_u32() < (1 << 31); + + let decrypting_key = if let Some(ref label) = label { + DecryptingKey::::new_with_label(prk.clone(), label.clone()) + } else { + DecryptingKey::::new(prk.clone()) + }; + + let plaintext = if blind { + decrypting_key.decrypt(&ciphertext).unwrap() + } else { + decrypting_key + .decrypt_with_rng(&mut rng, &ciphertext) + .unwrap() + }; + + assert_eq!(input, plaintext); + } + } + + #[test] + fn test_decrypt_oaep_invalid_hash_traits() { + let mut rng = ChaCha8Rng::from_seed([42; 32]); + let priv_key = get_private_key(); + let pub_key: RsaPublicKey = (&priv_key).into(); + let encrypting_key = EncryptingKey::::new(pub_key); + let decrypting_key = DecryptingKey::::new_with_label(priv_key, "label"); + let ciphertext = encrypting_key + .encrypt_with_rng(&mut rng, "a_plain_text".as_bytes()) + .unwrap(); + assert!( + decrypting_key + .decrypt_with_rng(&mut rng, &ciphertext) + .is_err(), + "decrypt should have failed on hash verification" + ); + } +} diff --git a/src/pkcs1v15.rs b/src/pkcs1v15.rs index a5eb046b..8aa0a1f4 100644 --- a/src/pkcs1v15.rs +++ b/src/pkcs1v15.rs @@ -25,6 +25,7 @@ use crate::dummy_rng::DummyRng; use crate::errors::{Error, Result}; use crate::key::{self, PrivateKey, PublicKey}; use crate::padding::{PaddingScheme, SignatureScheme}; +use crate::traits::{Decryptor, EncryptingKeypair, RandomizedDecryptor, RandomizedEncryptor}; use crate::{RsaPrivateKey, RsaPublicKey}; /// Encryption using PKCS#1 v1.5 padding. @@ -188,7 +189,7 @@ impl Display for Signature { /// scheme from PKCS#1 v1.5. The message must be no longer than the /// length of the public modulus minus 11 bytes. #[inline] -pub(crate) fn encrypt( +pub(crate) fn encrypt( rng: &mut R, pub_key: &PK, msg: &[u8], @@ -220,7 +221,7 @@ pub(crate) fn encrypt( /// forge signatures as if they had the private key. See /// `decrypt_session_key` for a way of solving this problem. #[inline] -pub(crate) fn decrypt( +pub(crate) fn decrypt( rng: Option<&mut R>, priv_key: &SK, ciphertext: &[u8], @@ -337,7 +338,7 @@ where /// in order to maintain constant memory access patterns. If the plaintext was /// valid then index contains the index of the original message in em. #[inline] -fn decrypt_inner( +fn decrypt_inner( rng: Option<&mut R>, priv_key: &SK, ciphertext: &[u8], @@ -382,7 +383,7 @@ fn decrypt_inner( /// Fills the provided slice with random values, which are guaranteed /// to not be zero. #[inline] -fn non_zero_random_bytes(rng: &mut R, data: &mut [u8]) { +fn non_zero_random_bytes(rng: &mut R, data: &mut [u8]) { rng.fill_bytes(data); for el in data { @@ -708,6 +709,71 @@ where } } +/// Encryption key for PKCS#1 v1.5 encryption as described in [RFC8017 § 7.2]. +/// +/// [RFC8017 § 7.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.2 +#[derive(Debug, Clone)] +pub struct EncryptingKey { + inner: RsaPublicKey, +} + +impl EncryptingKey { + /// Create a new verifying key from an RSA public key. + pub fn new(key: RsaPublicKey) -> Self { + Self { inner: key } + } +} + +impl RandomizedEncryptor for EncryptingKey { + fn encrypt_with_rng( + &self, + rng: &mut R, + msg: &[u8], + ) -> Result> { + encrypt(rng, &self.inner, msg) + } +} + +/// Decryption key for PKCS#1 v1.5 decryption as described in [RFC8017 § 7.2]. +/// +/// [RFC8017 § 7.2]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.2 +#[derive(Debug, Clone)] +pub struct DecryptingKey { + inner: RsaPrivateKey, +} + +impl DecryptingKey { + /// Create a new verifying key from an RSA public key. + pub fn new(key: RsaPrivateKey) -> Self { + Self { inner: key } + } +} + +impl Decryptor for DecryptingKey { + fn decrypt(&self, ciphertext: &[u8]) -> Result> { + decrypt::(None, &self.inner, ciphertext) + } +} + +impl RandomizedDecryptor for DecryptingKey { + fn decrypt_with_rng( + &self, + rng: &mut R, + ciphertext: &[u8], + ) -> Result> { + decrypt(Some(rng), &self.inner, ciphertext) + } +} + +impl EncryptingKeypair for DecryptingKey { + type EncryptingKey = EncryptingKey; + fn encrypting_key(&self) -> EncryptingKey { + EncryptingKey { + inner: self.inner.clone().into(), + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -812,6 +878,63 @@ mod tests { } } + #[test] + fn test_decrypt_pkcs1v15_traits() { + let priv_key = get_private_key(); + let decrypting_key = DecryptingKey::new(priv_key); + + let tests = [[ + "gIcUIoVkD6ATMBk/u/nlCZCCWRKdkfjCgFdo35VpRXLduiKXhNz1XupLLzTXAybEq15juc+EgY5o0DHv/nt3yg==", + "x", + ], [ + "Y7TOCSqofGhkRb+jaVRLzK8xw2cSo1IVES19utzv6hwvx+M8kFsoWQm5DzBeJCZTCVDPkTpavUuEbgp8hnUGDw==", + "testing.", + ], [ + "arReP9DJtEVyV2Dg3dDp4c/PSk1O6lxkoJ8HcFupoRorBZG+7+1fDAwT1olNddFnQMjmkb8vxwmNMoTAT/BFjQ==", + "testing.\n", + ], [ + "WtaBXIoGC54+vH0NH0CHHE+dRDOsMc/6BrfFu2lEqcKL9+uDuWaf+Xj9mrbQCjjZcpQuX733zyok/jsnqe/Ftw==", + "01234567890123456789012345678901234567890123456789012", + ]]; + + for test in &tests { + let out = decrypting_key + .decrypt(&Base64::decode_vec(test[0]).unwrap()) + .unwrap(); + assert_eq!(out, test[1].as_bytes()); + } + } + + #[test] + fn test_encrypt_decrypt_pkcs1v15_traits() { + let mut rng = ChaCha8Rng::from_seed([42; 32]); + let priv_key = get_private_key(); + let k = priv_key.size(); + let decrypting_key = DecryptingKey::new(priv_key); + + for i in 1..100 { + let mut input = vec![0u8; i * 8]; + rng.fill_bytes(&mut input); + if input.len() > k - 11 { + input = input[0..k - 11].to_vec(); + } + + let encrypting_key = decrypting_key.encrypting_key(); + let ciphertext = encrypting_key.encrypt_with_rng(&mut rng, &input).unwrap(); + assert_ne!(input, ciphertext); + + let blind: bool = rng.next_u32() < (1u32 << 31); + let plaintext = if blind { + decrypting_key + .decrypt_with_rng(&mut rng, &ciphertext) + .unwrap() + } else { + decrypting_key.decrypt(&ciphertext).unwrap() + }; + assert_eq!(input, plaintext); + } + } + #[test] fn test_sign_pkcs1v15() { let priv_key = get_private_key(); diff --git a/src/traits.rs b/src/traits.rs new file mode 100644 index 00000000..30b67902 --- /dev/null +++ b/src/traits.rs @@ -0,0 +1,42 @@ +//! Generic traits for message encryption and decryption + +use alloc::vec::Vec; +use rand_core::CryptoRngCore; + +use crate::errors::Result; + +/// Encrypt the message using provided random source +pub trait RandomizedEncryptor { + /// Encrypt the given message. + fn encrypt_with_rng( + &self, + rng: &mut R, + msg: &[u8], + ) -> Result>; +} + +/// Decrypt the given message +pub trait Decryptor { + /// Decrypt the given message. + fn decrypt(&self, ciphertext: &[u8]) -> Result>; +} + +/// Decrypt the given message using provided random source +pub trait RandomizedDecryptor { + /// Decrypt the given message. + fn decrypt_with_rng( + &self, + rng: &mut R, + ciphertext: &[u8], + ) -> Result>; +} + +/// Encryption keypair with an associated encryption key. +pub trait EncryptingKeypair { + /// Encrypting key type for this keypair. + type EncryptingKey: Clone; + + /// Get the encrypting key which can encrypt messages to be decrypted by + /// the decryption key portion of this keypair. + fn encrypting_key(&self) -> Self::EncryptingKey; +}