Add Encryption-related traits (#259)
* feat: relax Sized requirement for random source parameters * oaep: move OAEP test cases to src/oaep.rs There is little point in having only OAEP test cases in src/key.rs. Move them to proper module, oaep.rs. * oaep: mark two functions as private Currently the crate doesn't mark the oaep module as public. Thus it makes little sense to mark top-level functions as public. Drop the modifier. * feat: traits: add traits for encryption and decryption Add traits following the signature design for encryption and decryption. * oaep: add support for new encryption API Add new EncryptingKey and DecryptingKey structs implementing Encryptor / Decryptor traits. Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
This commit is contained in:
parent
6f8d112cea
commit
dacabfc5ff
202
src/key.rs
202
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::<Sha1>(&priv_key);
|
||||
do_test_encrypt_decrypt_oaep::<Sha224>(&priv_key);
|
||||
do_test_encrypt_decrypt_oaep::<Sha256>(&priv_key);
|
||||
do_test_encrypt_decrypt_oaep::<Sha384>(&priv_key);
|
||||
do_test_encrypt_decrypt_oaep::<Sha512>(&priv_key);
|
||||
do_test_encrypt_decrypt_oaep::<Sha3_256>(&priv_key);
|
||||
do_test_encrypt_decrypt_oaep::<Sha3_384>(&priv_key);
|
||||
do_test_encrypt_decrypt_oaep::<Sha3_512>(&priv_key);
|
||||
|
||||
do_test_oaep_with_different_hashes::<Sha1, Sha1>(&priv_key);
|
||||
do_test_oaep_with_different_hashes::<Sha224, Sha1>(&priv_key);
|
||||
do_test_oaep_with_different_hashes::<Sha256, Sha1>(&priv_key);
|
||||
do_test_oaep_with_different_hashes::<Sha384, Sha1>(&priv_key);
|
||||
do_test_oaep_with_different_hashes::<Sha512, Sha1>(&priv_key);
|
||||
do_test_oaep_with_different_hashes::<Sha3_256, Sha1>(&priv_key);
|
||||
do_test_oaep_with_different_hashes::<Sha3_384, Sha1>(&priv_key);
|
||||
do_test_oaep_with_different_hashes::<Sha3_512, Sha1>(&priv_key);
|
||||
}
|
||||
|
||||
fn get_label(rng: &mut ChaCha8Rng) -> Option<String> {
|
||||
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<D: '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_label::<D, _>(label);
|
||||
pub_key.encrypt(&mut rng, padding, &input).unwrap()
|
||||
} else {
|
||||
let padding = Oaep::new::<D>();
|
||||
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::<D, _>(label)
|
||||
} else {
|
||||
Oaep::new::<D>()
|
||||
};
|
||||
|
||||
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::<D, U, _>(label);
|
||||
pub_key.encrypt(&mut rng, padding, &input).unwrap()
|
||||
} else {
|
||||
let padding = Oaep::new_with_mgf_hash::<D, U>();
|
||||
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::<D, U, _>(label)
|
||||
} else {
|
||||
Oaep::new_with_mgf_hash::<D, U>()
|
||||
};
|
||||
|
||||
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::<Sha1>(), "a_plain_text".as_bytes())
|
||||
.unwrap();
|
||||
assert!(
|
||||
priv_key
|
||||
.decrypt_blinded(
|
||||
&mut rng,
|
||||
Oaep::new_with_label::<Sha1, _>("label"),
|
||||
&ciphertext,
|
||||
)
|
||||
.is_err(),
|
||||
"decrypt should have failed on hash verification"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
655
src/oaep.rs
655
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,37 +174,28 @@ impl fmt::Debug for Oaep {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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]
|
||||
pub fn encrypt<R: CryptoRngCore, K: PublicKey>(
|
||||
fn encrypt_internal<
|
||||
'a,
|
||||
R: CryptoRngCore + ?Sized,
|
||||
K: PublicKey,
|
||||
MGF: FnMut(&mut [u8], &mut [u8]) -> (),
|
||||
>(
|
||||
rng: &mut R,
|
||||
pub_key: &K,
|
||||
msg: &[u8],
|
||||
digest: &mut dyn DynDigest,
|
||||
mgf_digest: &mut dyn DynDigest,
|
||||
label: Option<String>,
|
||||
p_hash: &[u8],
|
||||
h_size: usize,
|
||||
mut mgf: MGF,
|
||||
) -> Result<Vec<u8>> {
|
||||
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);
|
||||
}
|
||||
|
||||
let mut em = Zeroizing::new(vec![0u8; k]);
|
||||
|
||||
let (_, payload) = em.split_at_mut(1);
|
||||
@ -206,18 +205,82 @@ pub fn encrypt<R: CryptoRngCore, K: PublicKey>(
|
||||
// Data block DB = pHash || PS || 01 || M
|
||||
let db_len = k - h_size - 1;
|
||||
|
||||
digest.update(label.as_bytes());
|
||||
let p_hash = digest.finalize_reset();
|
||||
db[0..h_size].copy_from_slice(&p_hash);
|
||||
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);
|
||||
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].
|
||||
///
|
||||
/// 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<R: CryptoRngCore + ?Sized, K: PublicKey>(
|
||||
rng: &mut R,
|
||||
pub_key: &K,
|
||||
msg: &[u8],
|
||||
digest: &mut dyn DynDigest,
|
||||
mgf_digest: &mut dyn DynDigest,
|
||||
label: Option<String>,
|
||||
) -> Result<Vec<u8>> {
|
||||
let h_size = digest.output_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 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<String>,
|
||||
) -> Result<Vec<u8>> {
|
||||
let h_size = <D as Digest>::output_size();
|
||||
|
||||
let label = label.unwrap_or_default();
|
||||
if label.len() as u64 > MAX_LABEL_LEN {
|
||||
return Err(Error::LabelTooLong);
|
||||
}
|
||||
|
||||
let p_hash = D::digest(label.as_bytes());
|
||||
|
||||
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);
|
||||
})
|
||||
}
|
||||
|
||||
/// 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.
|
||||
@ -231,7 +294,7 @@ pub fn encrypt<R: CryptoRngCore, K: PublicKey>(
|
||||
///
|
||||
/// [PKCS#1 OAEP]: https://datatracker.ietf.org/doc/html/rfc8017#section-7.1
|
||||
#[inline]
|
||||
pub fn decrypt<R: CryptoRngCore, SK: PrivateKey>(
|
||||
fn decrypt<R: CryptoRngCore + ?Sized, SK: PrivateKey>(
|
||||
rng: Option<&mut R>,
|
||||
priv_key: &SK,
|
||||
ciphertext: &[u8],
|
||||
@ -241,7 +304,84 @@ pub fn decrypt<R: CryptoRngCore, SK: PrivateKey>(
|
||||
) -> Result<Vec<u8>> {
|
||||
key::check_public(priv_key)?;
|
||||
|
||||
let res = decrypt_inner(rng, priv_key, ciphertext, digest, mgf_digest, label)?;
|
||||
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 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();
|
||||
|
||||
Ok(out[index as usize..].to_vec())
|
||||
}
|
||||
|
||||
/// 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_digest<
|
||||
R: CryptoRngCore + ?Sized,
|
||||
SK: PrivateKey,
|
||||
D: Digest,
|
||||
MGD: Digest + FixedOutputReset,
|
||||
>(
|
||||
rng: Option<&mut R>,
|
||||
priv_key: &SK,
|
||||
ciphertext: &[u8],
|
||||
label: Option<String>,
|
||||
) -> Result<Vec<u8>> {
|
||||
key::check_public(priv_key)?;
|
||||
|
||||
let h_size = <D as Digest>::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<R: CryptoRngCore, SK: PrivateKey>(
|
||||
/// `rng` is given. It returns one or zero in valid that indicates whether the
|
||||
/// plaintext was correctly structured.
|
||||
#[inline]
|
||||
fn decrypt_inner<R: CryptoRngCore, SK: PrivateKey>(
|
||||
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<String>,
|
||||
h_size: usize,
|
||||
expected_p_hash: &[u8],
|
||||
mut mgf: MGF,
|
||||
) -> Result<CtOption<(Vec<u8>, 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<R: CryptoRngCore, SK: PrivateKey>(
|
||||
|
||||
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<D, MGD = D>
|
||||
where
|
||||
D: Digest,
|
||||
MGD: Digest + FixedOutputReset,
|
||||
{
|
||||
inner: RsaPublicKey,
|
||||
label: Option<String>,
|
||||
phantom: PhantomData<D>,
|
||||
mg_phantom: PhantomData<MGD>,
|
||||
}
|
||||
|
||||
impl<D, MGD> EncryptingKey<D, MGD>
|
||||
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<S: AsRef<str>>(key: RsaPublicKey, label: S) -> Self {
|
||||
Self {
|
||||
inner: key,
|
||||
label: Some(label.as_ref().to_string()),
|
||||
phantom: Default::default(),
|
||||
mg_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D, MGD> RandomizedEncryptor for EncryptingKey<D, MGD>
|
||||
where
|
||||
D: Digest,
|
||||
MGD: Digest + FixedOutputReset,
|
||||
{
|
||||
fn encrypt_with_rng<R: CryptoRngCore + ?Sized>(
|
||||
&self,
|
||||
rng: &mut R,
|
||||
msg: &[u8],
|
||||
) -> Result<Vec<u8>> {
|
||||
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<D, MGD = D>
|
||||
where
|
||||
D: Digest,
|
||||
MGD: Digest + FixedOutputReset,
|
||||
{
|
||||
inner: RsaPrivateKey,
|
||||
label: Option<String>,
|
||||
phantom: PhantomData<D>,
|
||||
mg_phantom: PhantomData<MGD>,
|
||||
}
|
||||
|
||||
impl<D, MGD> DecryptingKey<D, MGD>
|
||||
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<S: AsRef<str>>(key: RsaPrivateKey, label: S) -> Self {
|
||||
Self {
|
||||
inner: key,
|
||||
label: Some(label.as_ref().to_string()),
|
||||
phantom: Default::default(),
|
||||
mg_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D, MGD> Decryptor for DecryptingKey<D, MGD>
|
||||
where
|
||||
D: Digest,
|
||||
MGD: Digest + FixedOutputReset,
|
||||
{
|
||||
fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
|
||||
decrypt_digest::<DummyRng, _, D, MGD>(
|
||||
None,
|
||||
&self.inner,
|
||||
ciphertext,
|
||||
self.label.as_ref().cloned(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D, MGD> RandomizedDecryptor for DecryptingKey<D, MGD>
|
||||
where
|
||||
D: Digest,
|
||||
MGD: Digest + FixedOutputReset,
|
||||
{
|
||||
fn decrypt_with_rng<R: CryptoRngCore + ?Sized>(
|
||||
&self,
|
||||
rng: &mut R,
|
||||
ciphertext: &[u8],
|
||||
) -> Result<Vec<u8>> {
|
||||
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::<Sha1>(&priv_key);
|
||||
do_test_encrypt_decrypt_oaep::<Sha224>(&priv_key);
|
||||
do_test_encrypt_decrypt_oaep::<Sha256>(&priv_key);
|
||||
do_test_encrypt_decrypt_oaep::<Sha384>(&priv_key);
|
||||
do_test_encrypt_decrypt_oaep::<Sha512>(&priv_key);
|
||||
do_test_encrypt_decrypt_oaep::<Sha3_256>(&priv_key);
|
||||
do_test_encrypt_decrypt_oaep::<Sha3_384>(&priv_key);
|
||||
do_test_encrypt_decrypt_oaep::<Sha3_512>(&priv_key);
|
||||
|
||||
do_test_oaep_with_different_hashes::<Sha1, Sha1>(&priv_key);
|
||||
do_test_oaep_with_different_hashes::<Sha224, Sha1>(&priv_key);
|
||||
do_test_oaep_with_different_hashes::<Sha256, Sha1>(&priv_key);
|
||||
do_test_oaep_with_different_hashes::<Sha384, Sha1>(&priv_key);
|
||||
do_test_oaep_with_different_hashes::<Sha512, Sha1>(&priv_key);
|
||||
do_test_oaep_with_different_hashes::<Sha3_256, Sha1>(&priv_key);
|
||||
do_test_oaep_with_different_hashes::<Sha3_384, Sha1>(&priv_key);
|
||||
do_test_oaep_with_different_hashes::<Sha3_512, Sha1>(&priv_key);
|
||||
}
|
||||
|
||||
fn get_label(rng: &mut ChaCha8Rng) -> Option<String> {
|
||||
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<D: '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_label::<D, _>(label);
|
||||
pub_key.encrypt(&mut rng, padding, &input).unwrap()
|
||||
} else {
|
||||
let padding = Oaep::new::<D>();
|
||||
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::<D, _>(label)
|
||||
} else {
|
||||
Oaep::new::<D>()
|
||||
};
|
||||
|
||||
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::<D, U, _>(label);
|
||||
pub_key.encrypt(&mut rng, padding, &input).unwrap()
|
||||
} else {
|
||||
let padding = Oaep::new_with_mgf_hash::<D, U>();
|
||||
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::<D, U, _>(label)
|
||||
} else {
|
||||
Oaep::new_with_mgf_hash::<D, U>()
|
||||
};
|
||||
|
||||
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::<Sha1>(), "a_plain_text".as_bytes())
|
||||
.unwrap();
|
||||
assert!(
|
||||
priv_key
|
||||
.decrypt_blinded(
|
||||
&mut rng,
|
||||
Oaep::new_with_label::<Sha1, _>("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::<Sha1>(&priv_key);
|
||||
do_test_encrypt_decrypt_oaep_traits::<Sha224>(&priv_key);
|
||||
do_test_encrypt_decrypt_oaep_traits::<Sha256>(&priv_key);
|
||||
do_test_encrypt_decrypt_oaep_traits::<Sha384>(&priv_key);
|
||||
do_test_encrypt_decrypt_oaep_traits::<Sha512>(&priv_key);
|
||||
do_test_encrypt_decrypt_oaep_traits::<Sha3_256>(&priv_key);
|
||||
do_test_encrypt_decrypt_oaep_traits::<Sha3_384>(&priv_key);
|
||||
do_test_encrypt_decrypt_oaep_traits::<Sha3_512>(&priv_key);
|
||||
|
||||
do_test_oaep_with_different_hashes_traits::<Sha1, Sha1>(&priv_key);
|
||||
do_test_oaep_with_different_hashes_traits::<Sha224, Sha1>(&priv_key);
|
||||
do_test_oaep_with_different_hashes_traits::<Sha256, Sha1>(&priv_key);
|
||||
do_test_oaep_with_different_hashes_traits::<Sha384, Sha1>(&priv_key);
|
||||
do_test_oaep_with_different_hashes_traits::<Sha512, Sha1>(&priv_key);
|
||||
do_test_oaep_with_different_hashes_traits::<Sha3_256, Sha1>(&priv_key);
|
||||
do_test_oaep_with_different_hashes_traits::<Sha3_384, Sha1>(&priv_key);
|
||||
do_test_oaep_with_different_hashes_traits::<Sha3_512, Sha1>(&priv_key);
|
||||
}
|
||||
|
||||
fn do_test_encrypt_decrypt_oaep_traits<D: Digest + FixedOutputReset>(prk: &RsaPrivateKey) {
|
||||
do_test_oaep_with_different_hashes_traits::<D, D>(prk);
|
||||
}
|
||||
|
||||
fn do_test_oaep_with_different_hashes_traits<D: Digest, MGD: Digest + FixedOutputReset>(
|
||||
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::<D, MGD>::new_with_label(pub_key, label.clone());
|
||||
encrypting_key.encrypt_with_rng(&mut rng, &input).unwrap()
|
||||
} else {
|
||||
let encrypting_key = EncryptingKey::<D, MGD>::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::<D, MGD>::new_with_label(prk.clone(), label.clone())
|
||||
} else {
|
||||
DecryptingKey::<D, MGD>::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::<Sha1>::new(pub_key);
|
||||
let decrypting_key = DecryptingKey::<Sha1>::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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
131
src/pkcs1v15.rs
131
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<R: CryptoRngCore, PK: PublicKey>(
|
||||
pub(crate) fn encrypt<R: CryptoRngCore + ?Sized, PK: PublicKey>(
|
||||
rng: &mut R,
|
||||
pub_key: &PK,
|
||||
msg: &[u8],
|
||||
@ -220,7 +221,7 @@ pub(crate) fn encrypt<R: CryptoRngCore, PK: PublicKey>(
|
||||
/// 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<R: CryptoRngCore, SK: PrivateKey>(
|
||||
pub(crate) fn decrypt<R: CryptoRngCore + ?Sized, SK: PrivateKey>(
|
||||
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<R: CryptoRngCore, SK: PrivateKey>(
|
||||
fn decrypt_inner<R: CryptoRngCore + ?Sized, SK: PrivateKey>(
|
||||
rng: Option<&mut R>,
|
||||
priv_key: &SK,
|
||||
ciphertext: &[u8],
|
||||
@ -382,7 +383,7 @@ fn decrypt_inner<R: CryptoRngCore, SK: PrivateKey>(
|
||||
/// Fills the provided slice with random values, which are guaranteed
|
||||
/// to not be zero.
|
||||
#[inline]
|
||||
fn non_zero_random_bytes<R: CryptoRngCore>(rng: &mut R, data: &mut [u8]) {
|
||||
fn non_zero_random_bytes<R: CryptoRngCore + ?Sized>(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<R: CryptoRngCore + ?Sized>(
|
||||
&self,
|
||||
rng: &mut R,
|
||||
msg: &[u8],
|
||||
) -> Result<Vec<u8>> {
|
||||
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<Vec<u8>> {
|
||||
decrypt::<DummyRng, _>(None, &self.inner, ciphertext)
|
||||
}
|
||||
}
|
||||
|
||||
impl RandomizedDecryptor for DecryptingKey {
|
||||
fn decrypt_with_rng<R: CryptoRngCore + ?Sized>(
|
||||
&self,
|
||||
rng: &mut R,
|
||||
ciphertext: &[u8],
|
||||
) -> Result<Vec<u8>> {
|
||||
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();
|
||||
|
42
src/traits.rs
Normal file
42
src/traits.rs
Normal file
@ -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<R: CryptoRngCore + ?Sized>(
|
||||
&self,
|
||||
rng: &mut R,
|
||||
msg: &[u8],
|
||||
) -> Result<Vec<u8>>;
|
||||
}
|
||||
|
||||
/// Decrypt the given message
|
||||
pub trait Decryptor {
|
||||
/// Decrypt the given message.
|
||||
fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>>;
|
||||
}
|
||||
|
||||
/// Decrypt the given message using provided random source
|
||||
pub trait RandomizedDecryptor {
|
||||
/// Decrypt the given message.
|
||||
fn decrypt_with_rng<R: CryptoRngCore + ?Sized>(
|
||||
&self,
|
||||
rng: &mut R,
|
||||
ciphertext: &[u8],
|
||||
) -> Result<Vec<u8>>;
|
||||
}
|
||||
|
||||
/// 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;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user