From fd8152022d928c9811def769c0befa547eddd09f Mon Sep 17 00:00:00 2001 From: lucdew Date: Mon, 1 Apr 2019 18:11:40 +0200 Subject: [PATCH 1/9] Add oaep encrypt and only with sha1 digest Test assertion is manual and done with openssl --- Cargo.toml | 6 +- src/hash.rs | 29 ++++++++ src/internals.rs | 11 +++ src/key.rs | 8 +- src/lib.rs | 1 + src/oaep.rs | 161 ++++++++++++++++++++++++++++++++++++++++ src/pkcs1v15.rs | 14 +--- test/data/rsa_prkey.pem | 27 +++++++ 8 files changed, 241 insertions(+), 16 deletions(-) create mode 100644 src/oaep.rs create mode 100644 test/data/rsa_prkey.pem diff --git a/Cargo.toml b/Cargo.toml index c1a0745e..3d6ce44e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,10 @@ rand = "0.7.0" byteorder = "1.3.1" failure = "0.1.5" subtle = "2.0.0" +sha-1 = "0.8.1" +sha2 = "0.8.0" +sha3 = "0.8.0" +digest = "0.8.0" [dependencies.zeroize] version = "1.1.0" @@ -34,8 +38,6 @@ features = ["std", "derive"] [dev-dependencies] base64 = "0.11.0" -sha-1 = "0.8.1" -sha2 = "0.8.0" hex = "0.4.0" serde_test = "1.0.89" rand_xorshift = "0.2.0" diff --git a/src/hash.rs b/src/hash.rs index 6af2ae5b..411f5341 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -1,3 +1,5 @@ +use sha1::{Sha1,Digest}; + /// A generic trait that exposes the information that is needed for a hash function to be /// used in `sign` and `verify.`. pub trait Hash { @@ -6,6 +8,8 @@ pub trait Hash { /// Returns the ASN1 DER prefix for the the hash function. fn asn1_prefix(&self) -> Vec; + + fn digest(&self, msg: &[u8]) -> Vec; } /// A list of provided hashes, implementing `Hash`. @@ -24,6 +28,13 @@ pub enum Hashes { RIPEMD160, } +fn sha1_digest(msg: &[u8]) -> Vec { + let mut hasher = Sha1::new(); + hasher.input(msg); + let res = hasher.result(); + res.iter().cloned().collect() +} + impl Hash for Hashes { fn size(&self) -> usize { match *self { @@ -90,4 +101,22 @@ impl Hash for Hashes { ], } } + + fn digest(&self, msg: &[u8]) -> Vec { + match *self { + + Hashes::MD5 => panic!("Not implemented"), + Hashes::SHA1 => sha1_digest(msg), + Hashes::SHA2_224 => panic!("Not implemented"), + Hashes::SHA2_256 => panic!("Not implemented"), + Hashes::SHA2_384 => panic!("Not implemented"), + Hashes::SHA2_512 => panic!("Not implemented"), + Hashes::SHA3_256 => panic!("Not implemented"), + Hashes::SHA3_384 => panic!("Not implemented"), + Hashes::SHA3_512 => panic!("Not implemented"), + Hashes::MD5SHA1 => panic!("Not implemented"), + Hashes::RIPEMD160 => panic!("Not implemented"), + + } + } } diff --git a/src/internals.rs b/src/internals.rs index 5c034f59..28b8f07b 100644 --- a/src/internals.rs +++ b/src/internals.rs @@ -179,3 +179,14 @@ pub fn left_pad(input: &[u8], size: usize) -> Vec { out[size - n..].copy_from_slice(input); out } + +#[inline] +pub fn copy_with_left_pad(dest: &mut [u8], src: &[u8]) { + // left pad with zeros + let padding_bytes = dest.len() - src.len(); + for el in dest.iter_mut().take(padding_bytes) { + *el = 0; + } + dest[padding_bytes..].copy_from_slice(src); +} + diff --git a/src/key.rs b/src/key.rs index f9e71ae2..e87c112b 100644 --- a/src/key.rs +++ b/src/key.rs @@ -9,9 +9,10 @@ use zeroize::Zeroize; use crate::algorithms::generate_multi_prime_key; use crate::errors::{Error, Result}; -use crate::hash::Hash; +use crate::hash::{Hash,Hashes}; use crate::padding::PaddingScheme; use crate::pkcs1v15; +use crate::oaep; lazy_static! { static ref MIN_PUB_EXPONENT: BigUint = BigUint::from_u64(2).unwrap(); @@ -164,7 +165,10 @@ impl PublicKey for RSAPublicKey { fn encrypt(&self, rng: &mut R, padding: PaddingScheme, msg: &[u8]) -> Result> { match padding { PaddingScheme::PKCS1v15 => pkcs1v15::encrypt(rng, self, msg), - PaddingScheme::OAEP => unimplemented!("not yet implemented"), + PaddingScheme::OAEP => oaep::encrypt(rng,self,msg,oaep::OaepOptions { + hash: Hashes::SHA1, + label: None, + }), _ => Err(Error::InvalidPaddingScheme), } } diff --git a/src/lib.rs b/src/lib.rs index a96c2a5c..4f1701d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,6 +62,7 @@ pub mod padding; mod key; mod pkcs1v15; +mod oaep; pub use self::key::{PublicKey, RSAPrivateKey, RSAPublicKey}; pub use self::padding::PaddingScheme; diff --git a/src/oaep.rs b/src/oaep.rs new file mode 100644 index 00000000..d1f188f7 --- /dev/null +++ b/src/oaep.rs @@ -0,0 +1,161 @@ +use num_bigint::BigUint; +use rand::Rng; +use zeroize::Zeroize; + +use crate::errors::{Error, Result}; +use crate::hash::Hash; +use crate::internals; +use crate::key::{self, PublicKey}; + + +pub struct OaepOptions { + pub hash:H, + pub label:Option, +} + +fn inc_counter(counter: &mut[u8]) { + + if counter[3]==u8::max_value() { + counter[3] = 0; + } else { + counter[3] += 1; + return; + } + + if counter[2]==u8::max_value() { + counter[2] = 0; + } else { + counter[2] += 1; + return; + } + + if counter[1]==u8::max_value() { + counter[1] = 0; + } else { + counter[1] += 1; + return; + } + + if counter[0] == u8::max_value() { + counter[0]=0u8; + counter[1]=0u8; + counter[2]=0u8; + counter[3]=0u8; + } else { + counter[0]+=1; + } + +} + +fn mgf1_xor(out: &mut[u8], h: &H, seed: &[u8]) { + let mut counter = vec![0u8; 4]; + let mut i = 0; + + while i < out.len() { + let mut digest_data = vec![0u8;seed.len()+4]; + digest_data[0..seed.len()].copy_from_slice(seed); + digest_data[seed.len()..].copy_from_slice(&counter); + + let digest = h.digest(digest_data.as_slice()); + let mut j = 0; + loop { + if j>= digest.len() || i >= out.len() { + break; + } + + out[i]^= digest[j]; + j+=1; + i+=1; + } + inc_counter(counter.as_mut_slice()); + } + +} + +// Encrypts the given message with RSA and the padding +// scheme from PKCS#1 v1.5. The message must be no longer than the +// length of the public modulus minus 11 bytes. +#[inline] +pub fn encrypt(rng: &mut R, pub_key: &K, msg: &[u8], oaep_options: OaepOptions) -> Result> { + key::check_public(pub_key)?; + + let h = oaep_options.hash; + let k = pub_key.size(); + + if msg.len() > k - 2*h.size() -2 { + return Err(Error::MessageTooLong); + } + + let label = match oaep_options.label { + Some(l) => l, + None => "".to_owned(), + }; + + let mut em = vec![0u8; k]; + + let (_, payload) = em.split_at_mut(1); + let (seed,db) = payload.split_at_mut(h.size()); + rng.fill(seed); + + // Data block DB = pHash || PS || 01 || M + let db_len = k - h.size() -1; + + let p_hash = h.digest(label.as_bytes()); + db[0..h.size()].copy_from_slice(p_hash.as_slice()); + db[db_len - msg.len() -1 ] = 1; + db[db_len - msg.len()..].copy_from_slice( msg); + + mgf1_xor(db,&h,seed); + mgf1_xor(seed, &h, db); + + { + let mut m = BigUint::from_bytes_be(&em); + let mut c = internals::encrypt(pub_key, &m).to_bytes_be(); + internals::copy_with_left_pad(&mut em, &c); + + // clear out tmp values + m.zeroize(); + c.zeroize(); + } + + + + Ok(em) +} + + +#[cfg(test)] +mod tests { + + use super::*; + use rand::thread_rng; + use crate::key::RSAPublicKey; + use crate::hash::Hashes; + use hex; + + #[test] + fn test_encrypt_decrypt_oaep() { + let mut rng = thread_rng(); + + let m = BigUint::parse_bytes(b"d397b84d98a4c26138ed1b695a8106ead91d553bf06041b62d3fdc50a041e222b8f4529689c1b82c5e71554f5dd69fa2f4b6158cf0dbeb57811a0fc327e1f28e74fe74d3bc166c1eabdc1b8b57b934ca8be5b00b4f29975bcc99acaf415b59bb28a6782bb41a2c3c2976b3c18dbadef62f00c6bb226640095096c0cc60d22fe7ef987d75c6a81b10d96bf292028af110dc7cc1bbc43d22adab379a0cd5d8078cc780ff5cd6209dea34c922cf784f7717e428d75b5aec8ff30e5f0141510766e2e0ab8d473c84e8710b2b98227c3db095337ad3452f19e2b9bfbccdd8148abf6776fa552775e6e75956e45229ae5a9c46949bab1e622f0e48f56524a84ed3483b",16).unwrap(); + let e = BigUint::parse_bytes(b"10001",16).unwrap(); + + println!("{:?}",e); + + let pub_key: RSAPublicKey = RSAPublicKey::new(m,e).unwrap(); + + let input = "hello world"; + let ciphertext = encrypt(&mut rng, &pub_key, input.as_bytes(), OaepOptions { + hash: Hashes::SHA1, + label: None, + }).unwrap(); + + + + // + println!("echo \"{}\" | xxd -r -p | openssl rsautl -inkey ./test/data/rsa_prkey.pem -decrypt -oaep", hex::encode(ciphertext)); + + + } + +} \ No newline at end of file diff --git a/src/pkcs1v15.rs b/src/pkcs1v15.rs index aa753384..68706dc4 100644 --- a/src/pkcs1v15.rs +++ b/src/pkcs1v15.rs @@ -30,7 +30,7 @@ pub fn encrypt(rng: &mut R, pub_key: &K, msg: &[u8]) -> Re { let mut m = BigUint::from_bytes_be(&em); let mut c = internals::encrypt(pub_key, &m).to_bytes_be(); - copy_with_left_pad(&mut em, &c); + internals::copy_with_left_pad(&mut em, &c); // clear out tmp values m.zeroize(); @@ -104,7 +104,7 @@ pub fn sign( let mut m = BigUint::from_bytes_be(&em); let mut c = internals::decrypt_and_check(rng, priv_key, &m)?.to_bytes_be(); - copy_with_left_pad(&mut em, &c); + internals::copy_with_left_pad(&mut em, &c); // clear tmp values m.zeroize(); @@ -170,16 +170,6 @@ fn hash_info(hash: Option<&H>, digest_len: usize) -> Result<(usize, Vec } } -#[inline] -fn copy_with_left_pad(dest: &mut [u8], src: &[u8]) { - // left pad with zeros - let padding_bytes = dest.len() - src.len(); - for el in dest.iter_mut().take(padding_bytes) { - *el = 0; - } - dest[padding_bytes..].copy_from_slice(src); -} - /// Decrypts ciphertext using `priv_key` and blinds the operation if /// `rng` is given. It returns one or zero in valid that indicates whether the /// plaintext was correctly structured. In either case, the plaintext is diff --git a/test/data/rsa_prkey.pem b/test/data/rsa_prkey.pem new file mode 100644 index 00000000..9505c00e --- /dev/null +++ b/test/data/rsa_prkey.pem @@ -0,0 +1,27 @@ +-----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----- From 38d1cd20130f87eedaa02025aea3a6b06557838b Mon Sep 17 00:00:00 2001 From: lucdew Date: Mon, 1 Apr 2019 20:26:22 +0200 Subject: [PATCH 2/9] Add more hash function implementations --- Cargo.toml | 2 +- src/hash.rs | 27 ++++++++-------- src/oaep.rs | 89 ++++++++++++++++++++++++++--------------------------- 3 files changed, 58 insertions(+), 60 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3d6ce44e..f04b0e92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ failure = "0.1.5" subtle = "2.0.0" sha-1 = "0.8.1" sha2 = "0.8.0" -sha3 = "0.8.0" +sha3 = "0.8.1" digest = "0.8.0" [dependencies.zeroize] diff --git a/src/hash.rs b/src/hash.rs index 411f5341..6b979c19 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -1,4 +1,6 @@ -use sha1::{Sha1,Digest}; +use sha1::{Digest, Sha1}; +use sha2::{Sha224, Sha256, Sha384, Sha512}; +use sha3::{Sha3_256, Sha3_384, Sha3_512}; /// A generic trait that exposes the information that is needed for a hash function to be /// used in `sign` and `verify.`. @@ -28,10 +30,9 @@ pub enum Hashes { RIPEMD160, } -fn sha1_digest(msg: &[u8]) -> Vec { - let mut hasher = Sha1::new(); +fn digest(msg: &[u8], hasher: &mut H) -> Vec { hasher.input(msg); - let res = hasher.result(); + let res = hasher.result_reset(); res.iter().cloned().collect() } @@ -104,19 +105,17 @@ impl Hash for Hashes { fn digest(&self, msg: &[u8]) -> Vec { match *self { - Hashes::MD5 => panic!("Not implemented"), - Hashes::SHA1 => sha1_digest(msg), - Hashes::SHA2_224 => panic!("Not implemented"), - Hashes::SHA2_256 => panic!("Not implemented"), - Hashes::SHA2_384 => panic!("Not implemented"), - Hashes::SHA2_512 => panic!("Not implemented"), - Hashes::SHA3_256 => panic!("Not implemented"), - Hashes::SHA3_384 => panic!("Not implemented"), - Hashes::SHA3_512 => panic!("Not implemented"), + Hashes::SHA1 => digest(msg, &mut Sha1::new()), + Hashes::SHA2_224 => digest(msg, &mut Sha224::new()), + Hashes::SHA2_256 => digest(msg, &mut Sha256::new()), + Hashes::SHA2_384 => digest(msg, &mut Sha384::new()), + Hashes::SHA2_512 => digest(msg, &mut Sha512::new()), + Hashes::SHA3_256 => digest(msg, &mut Sha3_256::new()), + Hashes::SHA3_384 => digest(msg, &mut Sha3_384::new()), + Hashes::SHA3_512 => digest(msg, &mut Sha3_512::new()), Hashes::MD5SHA1 => panic!("Not implemented"), Hashes::RIPEMD160 => panic!("Not implemented"), - } } } diff --git a/src/oaep.rs b/src/oaep.rs index d1f188f7..43f04d9f 100644 --- a/src/oaep.rs +++ b/src/oaep.rs @@ -7,29 +7,27 @@ use crate::hash::Hash; use crate::internals; use crate::key::{self, PublicKey}; - -pub struct OaepOptions { - pub hash:H, - pub label:Option, +pub struct OaepOptions { + pub hash: H, + pub label: Option, } -fn inc_counter(counter: &mut[u8]) { - - if counter[3]==u8::max_value() { +fn inc_counter(counter: &mut [u8]) { + if counter[3] == u8::max_value() { counter[3] = 0; } else { counter[3] += 1; return; } - if counter[2]==u8::max_value() { + if counter[2] == u8::max_value() { counter[2] = 0; } else { counter[2] += 1; return; } - if counter[1]==u8::max_value() { + if counter[1] == u8::max_value() { counter[1] = 0; } else { counter[1] += 1; @@ -37,52 +35,56 @@ fn inc_counter(counter: &mut[u8]) { } if counter[0] == u8::max_value() { - counter[0]=0u8; - counter[1]=0u8; - counter[2]=0u8; - counter[3]=0u8; + counter[0] = 0u8; + counter[1] = 0u8; + counter[2] = 0u8; + counter[3] = 0u8; } else { - counter[0]+=1; + counter[0] += 1; } - } -fn mgf1_xor(out: &mut[u8], h: &H, seed: &[u8]) { +/// Mask generation function +fn mgf1_xor(out: &mut [u8], h: &H, seed: &[u8]) { let mut counter = vec![0u8; 4]; let mut i = 0; while i < out.len() { - let mut digest_data = vec![0u8;seed.len()+4]; + let mut digest_data = vec![0u8; seed.len() + 4]; digest_data[0..seed.len()].copy_from_slice(seed); digest_data[seed.len()..].copy_from_slice(&counter); let digest = h.digest(digest_data.as_slice()); let mut j = 0; loop { - if j>= digest.len() || i >= out.len() { + if j >= digest.len() || i >= out.len() { break; } - out[i]^= digest[j]; - j+=1; - i+=1; + out[i] ^= digest[j]; + j += 1; + i += 1; } inc_counter(counter.as_mut_slice()); } - } // Encrypts the given message with RSA and the padding // scheme from PKCS#1 v1.5. The message must be no longer than the // length of the public modulus minus 11 bytes. #[inline] -pub fn encrypt(rng: &mut R, pub_key: &K, msg: &[u8], oaep_options: OaepOptions) -> Result> { +pub fn encrypt( + rng: &mut R, + pub_key: &K, + msg: &[u8], + oaep_options: OaepOptions, +) -> Result> { key::check_public(pub_key)?; let h = oaep_options.hash; let k = pub_key.size(); - if msg.len() > k - 2*h.size() -2 { + if msg.len() > k - 2 * h.size() - 2 { return Err(Error::MessageTooLong); } @@ -94,18 +96,18 @@ pub fn encrypt(rng: &mut R, pub_key: &K, msg: &[u let mut em = vec![0u8; k]; let (_, payload) = em.split_at_mut(1); - let (seed,db) = payload.split_at_mut(h.size()); + let (seed, db) = payload.split_at_mut(h.size()); rng.fill(seed); // Data block DB = pHash || PS || 01 || M - let db_len = k - h.size() -1; + let db_len = k - h.size() - 1; let p_hash = h.digest(label.as_bytes()); db[0..h.size()].copy_from_slice(p_hash.as_slice()); - db[db_len - msg.len() -1 ] = 1; - db[db_len - msg.len()..].copy_from_slice( msg); + db[db_len - msg.len() - 1] = 1; + db[db_len - msg.len()..].copy_from_slice(msg); - mgf1_xor(db,&h,seed); + mgf1_xor(db, &h, seed); mgf1_xor(seed, &h, db); { @@ -118,44 +120,41 @@ pub fn encrypt(rng: &mut R, pub_key: &K, msg: &[u c.zeroize(); } - - Ok(em) } - #[cfg(test)] mod tests { use super::*; - use rand::thread_rng; - use crate::key::RSAPublicKey; use crate::hash::Hashes; + use crate::key::RSAPublicKey; use hex; + use rand::thread_rng; #[test] fn test_encrypt_decrypt_oaep() { let mut rng = thread_rng(); let m = BigUint::parse_bytes(b"d397b84d98a4c26138ed1b695a8106ead91d553bf06041b62d3fdc50a041e222b8f4529689c1b82c5e71554f5dd69fa2f4b6158cf0dbeb57811a0fc327e1f28e74fe74d3bc166c1eabdc1b8b57b934ca8be5b00b4f29975bcc99acaf415b59bb28a6782bb41a2c3c2976b3c18dbadef62f00c6bb226640095096c0cc60d22fe7ef987d75c6a81b10d96bf292028af110dc7cc1bbc43d22adab379a0cd5d8078cc780ff5cd6209dea34c922cf784f7717e428d75b5aec8ff30e5f0141510766e2e0ab8d473c84e8710b2b98227c3db095337ad3452f19e2b9bfbccdd8148abf6776fa552775e6e75956e45229ae5a9c46949bab1e622f0e48f56524a84ed3483b",16).unwrap(); - let e = BigUint::parse_bytes(b"10001",16).unwrap(); + let e = BigUint::parse_bytes(b"10001", 16).unwrap(); - println!("{:?}",e); - - let pub_key: RSAPublicKey = RSAPublicKey::new(m,e).unwrap(); + let pub_key: RSAPublicKey = RSAPublicKey::new(m, e).unwrap(); let input = "hello world"; - let ciphertext = encrypt(&mut rng, &pub_key, input.as_bytes(), OaepOptions { + let ciphertext = encrypt( + &mut rng, + &pub_key, + input.as_bytes(), + OaepOptions { hash: Hashes::SHA1, label: None, - }).unwrap(); - - + }, + ) + .unwrap(); // println!("echo \"{}\" | xxd -r -p | openssl rsautl -inkey ./test/data/rsa_prkey.pem -decrypt -oaep", hex::encode(ciphertext)); - - } -} \ No newline at end of file +} From f61439bf495d5d3964fa5852562bd03eec94ba7c Mon Sep 17 00:00:00 2001 From: lucdew Date: Tue, 2 Apr 2019 14:33:28 +0200 Subject: [PATCH 3/9] Add nominal decrypt (only tested with SHA1) Might change the way oaep options are passed, current version is not convenient keys not fully implemented for oaep --- src/key.rs | 5 +- src/oaep.rs | 195 +++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 181 insertions(+), 19 deletions(-) diff --git a/src/key.rs b/src/key.rs index e87c112b..5e26265e 100644 --- a/src/key.rs +++ b/src/key.rs @@ -425,7 +425,10 @@ impl RSAPrivateKey { ) -> Result> { match padding { PaddingScheme::PKCS1v15 => pkcs1v15::decrypt(Some(rng), self, ciphertext), - PaddingScheme::OAEP => unimplemented!("not yet implemented"), + PaddingScheme::OAEP => oaep::decrypt(Some(rng), self, ciphertext, oaep::OaepOptions { + hash: Hashes::SHA1, + label: None, + }), _ => Err(Error::InvalidPaddingScheme), } } diff --git a/src/oaep.rs b/src/oaep.rs index 43f04d9f..9d638b8b 100644 --- a/src/oaep.rs +++ b/src/oaep.rs @@ -5,7 +5,8 @@ use zeroize::Zeroize; use crate::errors::{Error, Result}; use crate::hash::Hash; use crate::internals; -use crate::key::{self, PublicKey}; +use crate::key::{self, PublicKey, RSAPrivateKey}; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; pub struct OaepOptions { pub hash: H, @@ -85,6 +86,9 @@ pub fn encrypt( let k = pub_key.size(); if msg.len() > k - 2 * h.size() - 2 { + println!("message len = {:?}",msg.len()); + println!("message k = {:?}", k); + println!("message h = {:?}", h.size()); return Err(Error::MessageTooLong); } @@ -123,38 +127,193 @@ pub fn encrypt( Ok(em) } +/// Decrypts a plaintext using RSA and the padding scheme from 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. +#[inline] +pub fn decrypt( + rng: Option<&mut R>, + priv_key: &RSAPrivateKey, + ciphertext: &[u8], + oaep_options: OaepOptions, +) -> Result> { + key::check_public(priv_key)?; + + let (valid, out, index) = decrypt_inner(rng, priv_key, ciphertext, oaep_options)?; + if valid == 0 { + return Err(Error::Decryption); + } + + Ok(out[index as usize..].to_vec()) +} + + +/// Decrypts ciphertext using `priv_key` and blinds the operation if +/// `rng` is given. It returns one or zero in valid that indicates whether the +/// plaintext was correctly structured. In either case, the plaintext is +/// returned in em so that it may be read independently of whether it was valid +/// 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( + rng: Option<&mut R>, + priv_key: &RSAPrivateKey, + ciphertext: &[u8], + oaep_options: OaepOptions, +) -> Result<(u8, Vec, u32)> { + + let k = priv_key.size(); + if k < 11 { + return Err(Error::Decryption); + } + + let h = oaep_options.hash; + + if ciphertext.len() > k || + k < h.size()*2+2 { + return Err(Error::Decryption); + } + + let mut em = { + let mut c = BigUint::from_bytes_be(ciphertext); + let mut m = internals::decrypt(rng, priv_key, &c)?; + let em = internals::left_pad(&m.to_bytes_be(), k); + + c.zeroize(); + m.zeroize(); + + em + }; + + let label = match oaep_options.label { + Some(l) => l, + None => "".to_owned(), + }; + + let expected_p_hash = h.digest(label.as_bytes()); + + 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, &h, db); + mgf1_xor(db, &h, seed); + + let hash_are_equal = db[0..h.size()].ct_eq(expected_p_hash.as_slice()); + + + // The remainder of the plaintext must be zero or more 0x00, followed + // by 0x01, followed by the message. + // looking_for_index: 1 if we are still looking for the 0x01 + // index: the offset of the first 0x01 byte + // zero_before_one: 1 if we saw a non-zero byte before the 1 + let mut looking_for_index = 1u8; + let mut index = 0u32; + let mut zero_before_one = 0u8; + + for (i, el) in db.iter().skip(h.size()).enumerate() { + let equals0 = el.ct_eq(&0u8); + let equals1 = el.ct_eq(&1u8); + index.conditional_assign(&(i as u32), Choice::from(looking_for_index) & equals1); + looking_for_index.conditional_assign(&0u8, equals1); + zero_before_one.conditional_assign(&1u8, Choice::from(looking_for_index) & !equals0); + } + + + let valid = + first_byte_is_zero & hash_are_equal & ! Choice::from(zero_before_one) & ! Choice::from(looking_for_index); + index = u32::conditional_select(&0, &(index + 2 + (h.size()*2) as u32), valid); + + Ok((valid.unwrap_u8(), em, index)) +} + #[cfg(test)] mod tests { use super::*; + use num_traits::FromPrimitive; use crate::hash::Hashes; use crate::key::RSAPublicKey; - use hex; use rand::thread_rng; + + 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() + ], + ) + } + #[test] fn test_encrypt_decrypt_oaep() { - let mut rng = thread_rng(); - let m = BigUint::parse_bytes(b"d397b84d98a4c26138ed1b695a8106ead91d553bf06041b62d3fdc50a041e222b8f4529689c1b82c5e71554f5dd69fa2f4b6158cf0dbeb57811a0fc327e1f28e74fe74d3bc166c1eabdc1b8b57b934ca8be5b00b4f29975bcc99acaf415b59bb28a6782bb41a2c3c2976b3c18dbadef62f00c6bb226640095096c0cc60d22fe7ef987d75c6a81b10d96bf292028af110dc7cc1bbc43d22adab379a0cd5d8078cc780ff5cd6209dea34c922cf784f7717e428d75b5aec8ff30e5f0141510766e2e0ab8d473c84e8710b2b98227c3db095337ad3452f19e2b9bfbccdd8148abf6776fa552775e6e75956e45229ae5a9c46949bab1e622f0e48f56524a84ed3483b",16).unwrap(); - let e = BigUint::parse_bytes(b"10001", 16).unwrap(); + let mut rng = thread_rng(); + let priv_key = get_private_key(); + let k = priv_key.size(); - let pub_key: RSAPublicKey = RSAPublicKey::new(m, e).unwrap(); + for i in 1..20 { + let mut input: Vec = (0..i * 8).map(|_| rng.gen()).collect(); + if input.len() > k - 11 { + input = input[0..k - 11].to_vec(); + } - let input = "hello world"; - let ciphertext = encrypt( - &mut rng, - &pub_key, - input.as_bytes(), - OaepOptions { + let pub_key: RSAPublicKey = priv_key.clone().into(); + let ciphertext = encrypt(&mut rng, &pub_key, &input, OaepOptions{ hash: Hashes::SHA1, label: None, - }, - ) - .unwrap(); - - // - println!("echo \"{}\" | xxd -r -p | openssl rsautl -inkey ./test/data/rsa_prkey.pem -decrypt -oaep", hex::encode(ciphertext)); + }).unwrap(); + assert_ne!(input, ciphertext); + let blind: bool = rng.gen(); + let blinder = if blind { Some(&mut rng) } else { None }; + let plaintext = decrypt(blinder, &priv_key, &ciphertext,OaepOptions{ + hash: Hashes::SHA1, + label: None, + }).unwrap(); + assert_eq!(input, plaintext); + } } + + // TODO add test with test vectors like https://github.com/golang/go/blob/master/src/crypto/rsa/rsa_test.go + } From 139e07772bf72a218f419d4b02c18b15df9c23bc Mon Sep 17 00:00:00 2001 From: lucdew Date: Tue, 2 Apr 2019 15:04:55 +0200 Subject: [PATCH 4/9] Remove debug messages --- src/oaep.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/oaep.rs b/src/oaep.rs index 9d638b8b..5022794d 100644 --- a/src/oaep.rs +++ b/src/oaep.rs @@ -86,9 +86,6 @@ pub fn encrypt( let k = pub_key.size(); if msg.len() > k - 2 * h.size() - 2 { - println!("message len = {:?}",msg.len()); - println!("message k = {:?}", k); - println!("message h = {:?}", h.size()); return Err(Error::MessageTooLong); } From 8adcaf73a7b74531342e093499e425a864496fa7 Mon Sep 17 00:00:00 2001 From: lucdew Date: Tue, 2 Apr 2019 15:29:01 +0200 Subject: [PATCH 5/9] Simplifies oaep options --- src/key.rs | 14 ++++---------- src/oaep.rs | 52 +++++++++++++++++++++++++++++++++++----------------- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/src/key.rs b/src/key.rs index 5e26265e..58fd9cd2 100644 --- a/src/key.rs +++ b/src/key.rs @@ -165,10 +165,7 @@ impl PublicKey for RSAPublicKey { fn encrypt(&self, rng: &mut R, padding: PaddingScheme, msg: &[u8]) -> Result> { match padding { PaddingScheme::PKCS1v15 => pkcs1v15::encrypt(rng, self, msg), - PaddingScheme::OAEP => oaep::encrypt(rng,self,msg,oaep::OaepOptions { - hash: Hashes::SHA1, - label: None, - }), + PaddingScheme::OAEP => oaep::encrypt(rng,self,msg, oaep::OaepOptions::new()), _ => Err(Error::InvalidPaddingScheme), } } @@ -234,7 +231,7 @@ impl PublicKey for RSAPrivateKey { fn encrypt(&self, rng: &mut R, padding: PaddingScheme, msg: &[u8]) -> Result> { match padding { PaddingScheme::PKCS1v15 => pkcs1v15::encrypt(rng, self, msg), - PaddingScheme::OAEP => unimplemented!("not yet implemented"), + PaddingScheme::OAEP => oaep::encrypt(rng, self, msg, oaep::OaepOptions::new()), _ => Err(Error::InvalidPaddingScheme), } } @@ -410,7 +407,7 @@ impl RSAPrivateKey { match padding { // need to pass any Rng as the type arg, so the type checker is happy, it is not actually used for anything PaddingScheme::PKCS1v15 => pkcs1v15::decrypt::(None, self, ciphertext), - PaddingScheme::OAEP => unimplemented!("not yet implemented"), + PaddingScheme::OAEP => oaep::decrypt::(None, self, ciphertext, oaep::OaepOptions::new()), _ => Err(Error::InvalidPaddingScheme), } } @@ -425,10 +422,7 @@ impl RSAPrivateKey { ) -> Result> { match padding { PaddingScheme::PKCS1v15 => pkcs1v15::decrypt(Some(rng), self, ciphertext), - PaddingScheme::OAEP => oaep::decrypt(Some(rng), self, ciphertext, oaep::OaepOptions { - hash: Hashes::SHA1, - label: None, - }), + PaddingScheme::OAEP => oaep::decrypt(Some(rng), self, ciphertext, oaep::OaepOptions::new()), _ => Err(Error::InvalidPaddingScheme), } } diff --git a/src/oaep.rs b/src/oaep.rs index 5022794d..aa5a6b2d 100644 --- a/src/oaep.rs +++ b/src/oaep.rs @@ -3,16 +3,38 @@ use rand::Rng; use zeroize::Zeroize; use crate::errors::{Error, Result}; -use crate::hash::Hash; +use crate::hash::{Hash,Hashes}; use crate::internals; use crate::key::{self, PublicKey, RSAPrivateKey}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; -pub struct OaepOptions { - pub hash: H, +#[derive(Debug, Clone)] +pub struct OaepOptions { + pub hash: Hashes, pub label: Option, } +impl OaepOptions { + pub fn new() -> OaepOptions { + OaepOptions { + hash: Hashes::SHA1, + label: None, + } + } + + pub fn hash(mut self,h:Hashes) -> Self { + self.hash = h; + self + } + + pub fn label(mut self,l:Option) -> Self { + self.label = l; + self + } + + +} + fn inc_counter(counter: &mut [u8]) { if counter[3] == u8::max_value() { counter[3] = 0; @@ -74,11 +96,11 @@ fn mgf1_xor(out: &mut [u8], h: &H, seed: &[u8]) { // scheme from PKCS#1 v1.5. The message must be no longer than the // length of the public modulus minus 11 bytes. #[inline] -pub fn encrypt( +pub fn encrypt( rng: &mut R, pub_key: &K, msg: &[u8], - oaep_options: OaepOptions, + oaep_options: OaepOptions, ) -> Result> { key::check_public(pub_key)?; @@ -133,11 +155,11 @@ pub fn encrypt( // forge signatures as if they had the private key. See // `decrypt_session_key` for a way of solving this problem. #[inline] -pub fn decrypt( +pub fn decrypt( rng: Option<&mut R>, priv_key: &RSAPrivateKey, ciphertext: &[u8], - oaep_options: OaepOptions, + oaep_options: OaepOptions, ) -> Result> { key::check_public(priv_key)?; @@ -157,11 +179,11 @@ pub fn decrypt( /// 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: &RSAPrivateKey, ciphertext: &[u8], - oaep_options: OaepOptions, + oaep_options: OaepOptions, ) -> Result<(u8, Vec, u32)> { let k = priv_key.size(); @@ -288,6 +310,8 @@ mod tests { let priv_key = get_private_key(); let k = priv_key.size(); + let oaep_options = OaepOptions::new(); + for i in 1..20 { let mut input: Vec = (0..i * 8).map(|_| rng.gen()).collect(); if input.len() > k - 11 { @@ -295,17 +319,11 @@ mod tests { } let pub_key: RSAPublicKey = priv_key.clone().into(); - let ciphertext = encrypt(&mut rng, &pub_key, &input, OaepOptions{ - hash: Hashes::SHA1, - label: None, - }).unwrap(); + let ciphertext = encrypt(&mut rng, &pub_key, &input, oaep_options.clone()).unwrap(); assert_ne!(input, ciphertext); let blind: bool = rng.gen(); let blinder = if blind { Some(&mut rng) } else { None }; - let plaintext = decrypt(blinder, &priv_key, &ciphertext,OaepOptions{ - hash: Hashes::SHA1, - label: None, - }).unwrap(); + let plaintext = decrypt(blinder, &priv_key, &ciphertext, oaep_options.clone()).unwrap(); assert_eq!(input, plaintext); } } From d92c8f6931f82efcd435a1fce80e8be896e69605 Mon Sep 17 00:00:00 2001 From: lucdew Date: Wed, 3 Apr 2019 09:20:42 +0200 Subject: [PATCH 6/9] Add dummy invalid test case and does some cleanup --- src/key.rs | 14 ++++--- src/lib.rs | 2 +- src/oaep.rs | 90 ++++++++++++++++++++++++++++------------- test/data/rsa_prkey.pem | 27 ------------- 4 files changed, 72 insertions(+), 61 deletions(-) delete mode 100644 test/data/rsa_prkey.pem diff --git a/src/key.rs b/src/key.rs index 58fd9cd2..1e0076fc 100644 --- a/src/key.rs +++ b/src/key.rs @@ -9,10 +9,10 @@ use zeroize::Zeroize; use crate::algorithms::generate_multi_prime_key; use crate::errors::{Error, Result}; -use crate::hash::{Hash,Hashes}; +use crate::hash::Hash; +use crate::oaep; use crate::padding::PaddingScheme; use crate::pkcs1v15; -use crate::oaep; lazy_static! { static ref MIN_PUB_EXPONENT: BigUint = BigUint::from_u64(2).unwrap(); @@ -165,7 +165,7 @@ impl PublicKey for RSAPublicKey { fn encrypt(&self, rng: &mut R, padding: PaddingScheme, msg: &[u8]) -> Result> { match padding { PaddingScheme::PKCS1v15 => pkcs1v15::encrypt(rng, self, msg), - PaddingScheme::OAEP => oaep::encrypt(rng,self,msg, oaep::OaepOptions::new()), + PaddingScheme::OAEP => oaep::encrypt(rng, self, msg, oaep::OaepOptions::new()), _ => Err(Error::InvalidPaddingScheme), } } @@ -407,7 +407,9 @@ impl RSAPrivateKey { match padding { // need to pass any Rng as the type arg, so the type checker is happy, it is not actually used for anything PaddingScheme::PKCS1v15 => pkcs1v15::decrypt::(None, self, ciphertext), - PaddingScheme::OAEP => oaep::decrypt::(None, self, ciphertext, oaep::OaepOptions::new()), + PaddingScheme::OAEP => { + oaep::decrypt::(None, self, ciphertext, oaep::OaepOptions::new()) + } _ => Err(Error::InvalidPaddingScheme), } } @@ -422,7 +424,9 @@ impl RSAPrivateKey { ) -> Result> { match padding { PaddingScheme::PKCS1v15 => pkcs1v15::decrypt(Some(rng), self, ciphertext), - PaddingScheme::OAEP => oaep::decrypt(Some(rng), self, ciphertext, oaep::OaepOptions::new()), + PaddingScheme::OAEP => { + oaep::decrypt(Some(rng), self, ciphertext, oaep::OaepOptions::new()) + } _ => Err(Error::InvalidPaddingScheme), } } diff --git a/src/lib.rs b/src/lib.rs index 4f1701d8..1727fbc4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,8 +61,8 @@ pub mod hash; pub mod padding; mod key; +pub mod oaep; mod pkcs1v15; -mod oaep; pub use self::key::{PublicKey, RSAPrivateKey, RSAPublicKey}; pub use self::padding::PaddingScheme; diff --git a/src/oaep.rs b/src/oaep.rs index aa5a6b2d..6533b673 100644 --- a/src/oaep.rs +++ b/src/oaep.rs @@ -1,13 +1,15 @@ -use num_bigint::BigUint; use rand::Rng; + +use num_bigint::BigUint; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; use zeroize::Zeroize; use crate::errors::{Error, Result}; -use crate::hash::{Hash,Hashes}; +use crate::hash::{Hash, Hashes}; use crate::internals; use crate::key::{self, PublicKey, RSAPrivateKey}; -use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; +/// Represents oaep cipering/deciphering options. #[derive(Debug, Clone)] pub struct OaepOptions { pub hash: Hashes, @@ -22,17 +24,15 @@ impl OaepOptions { } } - pub fn hash(mut self,h:Hashes) -> Self { + pub fn set_hash(mut self, h: Hashes) -> Self { self.hash = h; self } - pub fn label(mut self,l:Option) -> Self { + pub fn set_label(mut self, l: Option) -> Self { self.label = l; self } - - } fn inc_counter(counter: &mut [u8]) { @@ -93,8 +93,8 @@ fn mgf1_xor(out: &mut [u8], h: &H, seed: &[u8]) { } // Encrypts the given message with RSA and the padding -// scheme from PKCS#1 v1.5. The message must be no longer than the -// length of the public modulus minus 11 bytes. +// scheme from PKCS#1 OAEP. The message must be no longer than the +// length of the public modulus minus (2+ 2*hash.size()). #[inline] pub fn encrypt( rng: &mut R, @@ -146,7 +146,7 @@ pub fn encrypt( Ok(em) } -/// Decrypts a plaintext using RSA and the padding scheme from OAEP +/// Decrypts a plaintext using RSA and the padding scheme from pkcs1# 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 @@ -171,7 +171,6 @@ pub fn decrypt( Ok(out[index as usize..].to_vec()) } - /// Decrypts ciphertext using `priv_key` and blinds the operation if /// `rng` is given. It returns one or zero in valid that indicates whether the /// plaintext was correctly structured. In either case, the plaintext is @@ -185,7 +184,6 @@ fn decrypt_inner( ciphertext: &[u8], oaep_options: OaepOptions, ) -> Result<(u8, Vec, u32)> { - let k = priv_key.size(); if k < 11 { return Err(Error::Decryption); @@ -193,8 +191,7 @@ fn decrypt_inner( let h = oaep_options.hash; - if ciphertext.len() > k || - k < h.size()*2+2 { + if ciphertext.len() > k || k < h.size() * 2 + 2 { return Err(Error::Decryption); } @@ -226,7 +223,6 @@ fn decrypt_inner( let hash_are_equal = db[0..h.size()].ct_eq(expected_p_hash.as_slice()); - // The remainder of the plaintext must be zero or more 0x00, followed // by 0x01, followed by the message. // looking_for_index: 1 if we are still looking for the 0x01 @@ -244,10 +240,11 @@ fn decrypt_inner( zero_before_one.conditional_assign(&1u8, Choice::from(looking_for_index) & !equals0); } - - let valid = - first_byte_is_zero & hash_are_equal & ! Choice::from(zero_before_one) & ! Choice::from(looking_for_index); - index = u32::conditional_select(&0, &(index + 2 + (h.size()*2) as u32), valid); + let valid = first_byte_is_zero + & hash_are_equal + & !Choice::from(zero_before_one) + & !Choice::from(looking_for_index); + index = u32::conditional_select(&0, &(index + 2 + (h.size() * 2) as u32), valid); Ok((valid.unwrap_u8(), em, index)) } @@ -256,12 +253,12 @@ fn decrypt_inner( mod tests { use super::*; - use num_traits::FromPrimitive; use crate::hash::Hashes; use crate::key::RSAPublicKey; + use num_traits::FromPrimitive; + use rand::distributions::Alphanumeric; use rand::thread_rng; - fn get_private_key() -> RSAPrivateKey { // -----BEGIN RSA PRIVATE KEY----- // MIIEpAIBAAKCAQEA05e4TZikwmE47RtpWoEG6tkdVTvwYEG2LT/cUKBB4iK49FKW @@ -291,7 +288,6 @@ mod tests { // BoB0er/UmDm4Ly/97EO9A0PKMOE5YbMq9s3t3RlWcsdrU7dvw+p2+A== // -----END RSA PRIVATE KEY----- - RSAPrivateKey::from_components( BigUint::parse_bytes(b"00d397b84d98a4c26138ed1b695a8106ead91d553bf06041b62d3fdc50a041e222b8f4529689c1b82c5e71554f5dd69fa2f4b6158cf0dbeb57811a0fc327e1f28e74fe74d3bc166c1eabdc1b8b57b934ca8be5b00b4f29975bcc99acaf415b59bb28a6782bb41a2c3c2976b3c18dbadef62f00c6bb226640095096c0cc60d22fe7ef987d75c6a81b10d96bf292028af110dc7cc1bbc43d22adab379a0cd5d8078cc780ff5cd6209dea34c922cf784f7717e428d75b5aec8ff30e5f0141510766e2e0ab8d473c84e8710b2b98227c3db095337ad3452f19e2b9bfbccdd8148abf6776fa552775e6e75956e45229ae5a9c46949bab1e622f0e48f56524a84ed3483b", 16).unwrap(), BigUint::from_u64(65537).unwrap(), @@ -305,21 +301,42 @@ mod tests { #[test] fn test_encrypt_decrypt_oaep() { - let mut rng = thread_rng(); let priv_key = get_private_key(); let k = priv_key.size(); - let oaep_options = OaepOptions::new(); + let mut oaep_options = OaepOptions::new(); + + let hashers = [ + Hashes::SHA1, + Hashes::SHA2_224, + Hashes::SHA2_256, + Hashes::SHA2_384, + Hashes::SHA2_512, + Hashes::SHA3_256, + Hashes::SHA3_384, + Hashes::SHA3_512, + ]; - for i in 1..20 { + for i in 1..16 { let mut input: Vec = (0..i * 8).map(|_| rng.gen()).collect(); if input.len() > k - 11 { input = input[0..k - 11].to_vec(); } + let hasher_idx: u32 = rng.gen(); + let has_label: bool = rng.gen(); + let label = if has_label { + Some(rng.sample_iter(&Alphanumeric).take(30).collect()) + } else { + None + }; + + oaep_options = oaep_options + .set_hash(hashers[(hasher_idx as usize % hashers.len())]) + .set_label(label); let pub_key: RSAPublicKey = priv_key.clone().into(); - let ciphertext = encrypt(&mut rng, &pub_key, &input, oaep_options.clone()).unwrap(); + let ciphertext = encrypt(&mut rng, &pub_key, &input, oaep_options.clone()).unwrap(); assert_ne!(input, ciphertext); let blind: bool = rng.gen(); let blinder = if blind { Some(&mut rng) } else { None }; @@ -328,7 +345,24 @@ mod tests { } } - - // TODO add test with test vectors like https://github.com/golang/go/blob/master/src/crypto/rsa/rsa_test.go + #[test] + fn test_decrypt_oaep_invalid_hash() { + let mut rng = thread_rng(); + let priv_key = get_private_key(); + let pub_key: RSAPublicKey = priv_key.clone().into(); + let mut oaep_options = OaepOptions::new(); + let ciphertext = encrypt( + &mut rng, + &pub_key, + "a_plain_text".as_bytes(), + oaep_options.clone(), + ) + .unwrap(); + oaep_options = oaep_options.set_label(Some("a_label".to_owned())); + assert!( + decrypt(Some(&mut rng), &priv_key, &ciphertext, oaep_options.clone()).is_err(), + "decrypt should have failed on hash verification" + ); + } } diff --git a/test/data/rsa_prkey.pem b/test/data/rsa_prkey.pem deleted file mode 100644 index 9505c00e..00000000 --- a/test/data/rsa_prkey.pem +++ /dev/null @@ -1,27 +0,0 @@ ------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----- From a405cdd8d72aee60c415c742a82ce039af8d07cf Mon Sep 17 00:00:00 2001 From: lucdew Date: Thu, 4 Apr 2019 12:51:17 +0200 Subject: [PATCH 7/9] Revert changes in key and hash Modify oaep methods signatures --- Cargo.toml | 2 +- src/hash.rs | 27 --------- src/key.rs | 13 ++--- src/oaep.rs | 162 ++++++++++++++++++++++++---------------------------- 4 files changed, 79 insertions(+), 125 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f04b0e92..8a366f76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,10 +21,10 @@ rand = "0.7.0" byteorder = "1.3.1" failure = "0.1.5" subtle = "2.0.0" +digest = "0.8.0" sha-1 = "0.8.1" sha2 = "0.8.0" sha3 = "0.8.1" -digest = "0.8.0" [dependencies.zeroize] version = "1.1.0" diff --git a/src/hash.rs b/src/hash.rs index 6b979c19..6adc39b6 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -1,7 +1,3 @@ -use sha1::{Digest, Sha1}; -use sha2::{Sha224, Sha256, Sha384, Sha512}; -use sha3::{Sha3_256, Sha3_384, Sha3_512}; - /// A generic trait that exposes the information that is needed for a hash function to be /// used in `sign` and `verify.`. pub trait Hash { @@ -11,7 +7,6 @@ pub trait Hash { /// Returns the ASN1 DER prefix for the the hash function. fn asn1_prefix(&self) -> Vec; - fn digest(&self, msg: &[u8]) -> Vec; } /// A list of provided hashes, implementing `Hash`. @@ -30,12 +25,6 @@ pub enum Hashes { RIPEMD160, } -fn digest(msg: &[u8], hasher: &mut H) -> Vec { - hasher.input(msg); - let res = hasher.result_reset(); - res.iter().cloned().collect() -} - impl Hash for Hashes { fn size(&self) -> usize { match *self { @@ -102,20 +91,4 @@ impl Hash for Hashes { ], } } - - fn digest(&self, msg: &[u8]) -> Vec { - match *self { - Hashes::MD5 => panic!("Not implemented"), - Hashes::SHA1 => digest(msg, &mut Sha1::new()), - Hashes::SHA2_224 => digest(msg, &mut Sha224::new()), - Hashes::SHA2_256 => digest(msg, &mut Sha256::new()), - Hashes::SHA2_384 => digest(msg, &mut Sha384::new()), - Hashes::SHA2_512 => digest(msg, &mut Sha512::new()), - Hashes::SHA3_256 => digest(msg, &mut Sha3_256::new()), - Hashes::SHA3_384 => digest(msg, &mut Sha3_384::new()), - Hashes::SHA3_512 => digest(msg, &mut Sha3_512::new()), - Hashes::MD5SHA1 => panic!("Not implemented"), - Hashes::RIPEMD160 => panic!("Not implemented"), - } - } } diff --git a/src/key.rs b/src/key.rs index 1e0076fc..f9e71ae2 100644 --- a/src/key.rs +++ b/src/key.rs @@ -10,7 +10,6 @@ use zeroize::Zeroize; use crate::algorithms::generate_multi_prime_key; use crate::errors::{Error, Result}; use crate::hash::Hash; -use crate::oaep; use crate::padding::PaddingScheme; use crate::pkcs1v15; @@ -165,7 +164,7 @@ impl PublicKey for RSAPublicKey { fn encrypt(&self, rng: &mut R, padding: PaddingScheme, msg: &[u8]) -> Result> { match padding { PaddingScheme::PKCS1v15 => pkcs1v15::encrypt(rng, self, msg), - PaddingScheme::OAEP => oaep::encrypt(rng, self, msg, oaep::OaepOptions::new()), + PaddingScheme::OAEP => unimplemented!("not yet implemented"), _ => Err(Error::InvalidPaddingScheme), } } @@ -231,7 +230,7 @@ impl PublicKey for RSAPrivateKey { fn encrypt(&self, rng: &mut R, padding: PaddingScheme, msg: &[u8]) -> Result> { match padding { PaddingScheme::PKCS1v15 => pkcs1v15::encrypt(rng, self, msg), - PaddingScheme::OAEP => oaep::encrypt(rng, self, msg, oaep::OaepOptions::new()), + PaddingScheme::OAEP => unimplemented!("not yet implemented"), _ => Err(Error::InvalidPaddingScheme), } } @@ -407,9 +406,7 @@ impl RSAPrivateKey { match padding { // need to pass any Rng as the type arg, so the type checker is happy, it is not actually used for anything PaddingScheme::PKCS1v15 => pkcs1v15::decrypt::(None, self, ciphertext), - PaddingScheme::OAEP => { - oaep::decrypt::(None, self, ciphertext, oaep::OaepOptions::new()) - } + PaddingScheme::OAEP => unimplemented!("not yet implemented"), _ => Err(Error::InvalidPaddingScheme), } } @@ -424,9 +421,7 @@ impl RSAPrivateKey { ) -> Result> { match padding { PaddingScheme::PKCS1v15 => pkcs1v15::decrypt(Some(rng), self, ciphertext), - PaddingScheme::OAEP => { - oaep::decrypt(Some(rng), self, ciphertext, oaep::OaepOptions::new()) - } + PaddingScheme::OAEP => unimplemented!("not yet implemented"), _ => Err(Error::InvalidPaddingScheme), } } diff --git a/src/oaep.rs b/src/oaep.rs index 6533b673..00626512 100644 --- a/src/oaep.rs +++ b/src/oaep.rs @@ -1,40 +1,15 @@ use rand::Rng; +use digest::DynDigest; + use num_bigint::BigUint; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; use zeroize::Zeroize; use crate::errors::{Error, Result}; -use crate::hash::{Hash, Hashes}; use crate::internals; use crate::key::{self, PublicKey, RSAPrivateKey}; -/// Represents oaep cipering/deciphering options. -#[derive(Debug, Clone)] -pub struct OaepOptions { - pub hash: Hashes, - pub label: Option, -} - -impl OaepOptions { - pub fn new() -> OaepOptions { - OaepOptions { - hash: Hashes::SHA1, - label: None, - } - } - - pub fn set_hash(mut self, h: Hashes) -> Self { - self.hash = h; - self - } - - pub fn set_label(mut self, l: Option) -> Self { - self.label = l; - self - } -} - fn inc_counter(counter: &mut [u8]) { if counter[3] == u8::max_value() { counter[3] = 0; @@ -68,23 +43,24 @@ fn inc_counter(counter: &mut [u8]) { } /// Mask generation function -fn mgf1_xor(out: &mut [u8], h: &H, seed: &[u8]) { +fn mgf1_xor(out: &mut [u8], digest: &mut impl DynDigest, seed: &[u8]) { let mut counter = vec![0u8; 4]; let mut i = 0; while i < out.len() { - let mut digest_data = vec![0u8; seed.len() + 4]; - digest_data[0..seed.len()].copy_from_slice(seed); - digest_data[seed.len()..].copy_from_slice(&counter); + let mut digest_input = vec![0u8; seed.len() + 4]; + digest_input[0..seed.len()].copy_from_slice(seed); + digest_input[seed.len()..].copy_from_slice(&counter); - let digest = h.digest(digest_data.as_slice()); + digest.input(digest_input.as_slice()); + let digest_output = &*digest.result_reset(); let mut j = 0; loop { - if j >= digest.len() || i >= out.len() { + if j >= digest_output.len() || i >= out.len() { break; } - out[i] ^= digest[j]; + out[i] ^= digest_output[j]; j += 1; i += 1; } @@ -100,18 +76,20 @@ pub fn encrypt( rng: &mut R, pub_key: &K, msg: &[u8], - oaep_options: OaepOptions, + digest: &mut impl DynDigest, + label: Option ) -> Result> { key::check_public(pub_key)?; - let h = oaep_options.hash; let k = pub_key.size(); - if msg.len() > k - 2 * h.size() - 2 { + let h_size = digest.output_size(); + + if msg.len() > k - 2 * h_size - 2 { return Err(Error::MessageTooLong); } - let label = match oaep_options.label { + let label = match label { Some(l) => l, None => "".to_owned(), }; @@ -119,19 +97,20 @@ pub fn encrypt( let mut em = vec![0u8; k]; let (_, payload) = em.split_at_mut(1); - let (seed, db) = payload.split_at_mut(h.size()); + let (seed, db) = payload.split_at_mut(h_size); rng.fill(seed); // Data block DB = pHash || PS || 01 || M - let db_len = k - h.size() - 1; + let db_len = k -h_size - 1; - let p_hash = h.digest(label.as_bytes()); - db[0..h.size()].copy_from_slice(p_hash.as_slice()); + digest.input(label.as_bytes()); + let p_hash = digest.result_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, &h, seed); - mgf1_xor(seed, &h, db); + mgf1_xor(db, digest, seed); + mgf1_xor(seed, digest, db); { let mut m = BigUint::from_bytes_be(&em); @@ -159,11 +138,12 @@ pub fn decrypt( rng: Option<&mut R>, priv_key: &RSAPrivateKey, ciphertext: &[u8], - oaep_options: OaepOptions, + digest: &mut impl DynDigest, + label: Option ) -> Result> { key::check_public(priv_key)?; - let (valid, out, index) = decrypt_inner(rng, priv_key, ciphertext, oaep_options)?; + let (valid, out, index) = decrypt_inner(rng, priv_key, ciphertext, digest, label)?; if valid == 0 { return Err(Error::Decryption); } @@ -182,16 +162,17 @@ fn decrypt_inner( rng: Option<&mut R>, priv_key: &RSAPrivateKey, ciphertext: &[u8], - oaep_options: OaepOptions, + digest: &mut impl DynDigest, + label: Option ) -> Result<(u8, Vec, u32)> { let k = priv_key.size(); if k < 11 { return Err(Error::Decryption); } - let h = oaep_options.hash; + let h_size = digest.output_size(); - if ciphertext.len() > k || k < h.size() * 2 + 2 { + if ciphertext.len() > k || k < h_size * 2 + 2 { return Err(Error::Decryption); } @@ -206,22 +187,24 @@ fn decrypt_inner( em }; - let label = match oaep_options.label { + let label = match label { Some(l) => l, None => "".to_owned(), }; - let expected_p_hash = h.digest(label.as_bytes()); + digest.input(label.as_bytes()); + + let expected_p_hash = &*digest.result_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()); + let (seed, db) = payload.split_at_mut(h_size); - mgf1_xor(seed, &h, db); - mgf1_xor(db, &h, seed); + mgf1_xor(seed, digest, db); + mgf1_xor(db, digest, seed); - let hash_are_equal = db[0..h.size()].ct_eq(expected_p_hash.as_slice()); + let hash_are_equal = db[0..h_size].ct_eq(expected_p_hash); // The remainder of the plaintext must be zero or more 0x00, followed // by 0x01, followed by the message. @@ -232,7 +215,7 @@ fn decrypt_inner( let mut index = 0u32; let mut zero_before_one = 0u8; - for (i, el) in db.iter().skip(h.size()).enumerate() { + for (i, el) in db.iter().skip(h_size).enumerate() { let equals0 = el.ct_eq(&0u8); let equals1 = el.ct_eq(&1u8); index.conditional_assign(&(i as u32), Choice::from(looking_for_index) & equals1); @@ -244,7 +227,7 @@ fn decrypt_inner( & hash_are_equal & !Choice::from(zero_before_one) & !Choice::from(looking_for_index); - index = u32::conditional_select(&0, &(index + 2 + (h.size() * 2) as u32), valid); + index = u32::conditional_select(&0, &(index + 2 + (h_size * 2) as u32), valid); Ok((valid.unwrap_u8(), em, index)) } @@ -253,12 +236,15 @@ fn decrypt_inner( mod tests { use super::*; - use crate::hash::Hashes; use crate::key::RSAPublicKey; use num_traits::FromPrimitive; use rand::distributions::Alphanumeric; use rand::thread_rng; + 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 @@ -301,29 +287,32 @@ mod tests { #[test] fn test_encrypt_decrypt_oaep() { - let mut rng = thread_rng(); + let priv_key = get_private_key(); - let k = priv_key.size(); - - let mut oaep_options = OaepOptions::new(); - - let hashers = [ - Hashes::SHA1, - Hashes::SHA2_224, - Hashes::SHA2_256, - Hashes::SHA2_384, - Hashes::SHA2_512, - Hashes::SHA3_256, - Hashes::SHA3_384, - Hashes::SHA3_512, - ]; - - for i in 1..16 { + do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha1::default()); + do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha224::default()); + do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha256::default()); + do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha384::default()); + do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha512::default()); + do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha3_256::default()); + do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha3_384::default()); + do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha3_512::default()); + } + + fn do_test_encrypt_decrypt_oaep( + prk: &RSAPrivateKey, + digest: &mut D, + ) { + + let mut rng = thread_rng(); + + let k = prk.size(); + + for i in 1..8 { let mut input: Vec = (0..i * 8).map(|_| rng.gen()).collect(); if input.len() > k - 11 { input = input[0..k - 11].to_vec(); } - let hasher_idx: u32 = rng.gen(); let has_label: bool = rng.gen(); let label = if has_label { Some(rng.sample_iter(&Alphanumeric).take(30).collect()) @@ -331,18 +320,15 @@ mod tests { None }; - oaep_options = oaep_options - .set_hash(hashers[(hasher_idx as usize % hashers.len())]) - .set_label(label); - - let pub_key: RSAPublicKey = priv_key.clone().into(); - let ciphertext = encrypt(&mut rng, &pub_key, &input, oaep_options.clone()).unwrap(); + let pub_key: RSAPublicKey = prk.clone().into(); + let ciphertext = encrypt(&mut rng, &pub_key, &input, digest, label.clone()).unwrap(); assert_ne!(input, ciphertext); let blind: bool = rng.gen(); let blinder = if blind { Some(&mut rng) } else { None }; - let plaintext = decrypt(blinder, &priv_key, &ciphertext, oaep_options.clone()).unwrap(); + let plaintext = decrypt(blinder, &prk, &ciphertext, digest, label).unwrap(); assert_eq!(input, plaintext); - } + } + } #[test] @@ -350,17 +336,17 @@ mod tests { let mut rng = thread_rng(); let priv_key = get_private_key(); let pub_key: RSAPublicKey = priv_key.clone().into(); - let mut oaep_options = OaepOptions::new(); + let mut digest = Sha1::default(); let ciphertext = encrypt( &mut rng, &pub_key, "a_plain_text".as_bytes(), - oaep_options.clone(), + &mut digest, + None, ) .unwrap(); - oaep_options = oaep_options.set_label(Some("a_label".to_owned())); assert!( - decrypt(Some(&mut rng), &priv_key, &ciphertext, oaep_options.clone()).is_err(), + decrypt(Some(&mut rng), &priv_key, &ciphertext, &mut digest, Some("label".to_owned())).is_err(), "decrypt should have failed on hash verification" ); } From 2d1fbde84198a9752423218f93ee0f8f77eea781 Mon Sep 17 00:00:00 2001 From: lucdew Date: Sun, 26 Jan 2020 12:02:23 +0100 Subject: [PATCH 8/9] Update to new dependencies --- Cargo.toml | 2 +- src/oaep.rs | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8a366f76..a514ca34 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ rand = "0.7.0" byteorder = "1.3.1" failure = "0.1.5" subtle = "2.0.0" -digest = "0.8.0" +digest = { version = "0.8.0", features = ["std"] } sha-1 = "0.8.1" sha2 = "0.8.0" sha3 = "0.8.1" diff --git a/src/oaep.rs b/src/oaep.rs index 00626512..1e2dc558 100644 --- a/src/oaep.rs +++ b/src/oaep.rs @@ -77,7 +77,7 @@ pub fn encrypt( pub_key: &K, msg: &[u8], digest: &mut impl DynDigest, - label: Option + label: Option, ) -> Result> { key::check_public(pub_key)?; @@ -101,7 +101,7 @@ pub fn encrypt( rng.fill(seed); // Data block DB = pHash || PS || 01 || M - let db_len = k -h_size - 1; + let db_len = k - h_size - 1; digest.input(label.as_bytes()); let p_hash = digest.result_reset(); @@ -139,7 +139,7 @@ pub fn decrypt( priv_key: &RSAPrivateKey, ciphertext: &[u8], digest: &mut impl DynDigest, - label: Option + label: Option, ) -> Result> { key::check_public(priv_key)?; @@ -163,7 +163,7 @@ fn decrypt_inner( priv_key: &RSAPrivateKey, ciphertext: &[u8], digest: &mut impl DynDigest, - label: Option + label: Option, ) -> Result<(u8, Vec, u32)> { let k = priv_key.size(); if k < 11 { @@ -287,7 +287,6 @@ mod tests { #[test] fn test_encrypt_decrypt_oaep() { - let priv_key = get_private_key(); do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha1::default()); do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha224::default()); @@ -299,16 +298,12 @@ mod tests { do_test_encrypt_decrypt_oaep(&priv_key, &mut Sha3_512::default()); } - fn do_test_encrypt_decrypt_oaep( - prk: &RSAPrivateKey, - digest: &mut D, - ) { - + fn do_test_encrypt_decrypt_oaep(prk: &RSAPrivateKey, digest: &mut D) { let mut rng = thread_rng(); let k = prk.size(); - for i in 1..8 { + for i in 1..8 { let mut input: Vec = (0..i * 8).map(|_| rng.gen()).collect(); if input.len() > k - 11 { input = input[0..k - 11].to_vec(); @@ -327,8 +322,7 @@ mod tests { let blinder = if blind { Some(&mut rng) } else { None }; let plaintext = decrypt(blinder, &prk, &ciphertext, digest, label).unwrap(); assert_eq!(input, plaintext); - } - + } } #[test] @@ -346,9 +340,15 @@ mod tests { ) .unwrap(); assert!( - decrypt(Some(&mut rng), &priv_key, &ciphertext, &mut digest, Some("label".to_owned())).is_err(), + decrypt( + Some(&mut rng), + &priv_key, + &ciphertext, + &mut digest, + Some("label".to_owned()) + ) + .is_err(), "decrypt should have failed on hash verification" ); } - } From 40e64cc986f223fa8be1de1fd8dcf34622a9ce72 Mon Sep 17 00:00:00 2001 From: lucdew Date: Mon, 27 Jan 2020 21:04:36 +0100 Subject: [PATCH 9/9] Fix potential attempt to substract with overflow --- src/oaep.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/oaep.rs b/src/oaep.rs index 1e2dc558..21d2faa8 100644 --- a/src/oaep.rs +++ b/src/oaep.rs @@ -85,7 +85,7 @@ pub fn encrypt( let h_size = digest.output_size(); - if msg.len() > k - 2 * h_size - 2 { + if msg.len() + 2 * h_size + 2 > k { return Err(Error::MessageTooLong); }