diff --git a/Cargo.toml b/Cargo.toml index 31333d2..e86fb82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,11 @@ license-file = "LICENSE" repository = "https://github.com/acw/simple_crypto" [dependencies] +digest = "^0.7.1" num = "^0.1.42" rand = "^0.3" +sha-1 = "^0.7.0" +sha2 = "^0.7.0" [dev-dependencies] quickcheck = "^0.4.1" diff --git a/src/cryptonum/mod.rs b/src/cryptonum/mod.rs index 06b4bc9..98ff167 100644 --- a/src/cryptonum/mod.rs +++ b/src/cryptonum/mod.rs @@ -9,5 +9,5 @@ mod unsigned; mod gold_tests; pub use self::signed::SCN; -pub use self::unsigned::UCN; +pub use self::unsigned::{BarrettUCN,UCN}; pub use self::primes::*; diff --git a/src/cryptonum/primes.rs b/src/cryptonum/primes.rs index 826fbc8..a932751 100644 --- a/src/cryptonum/primes.rs +++ b/src/cryptonum/primes.rs @@ -53,24 +53,26 @@ impl UCN { let candidate = base | &one; if let Some(proposed) = check_value(candidate) { - if proposed.probably_prime(rng, iterations) { + if proposed.probably_prime(rng, bitlen, iterations) { return proposed; } } } } - pub fn probably_prime(&self, g: &mut G, iters: usize) -> bool { + fn probably_prime(&self, g: &mut G, size: usize, iters: usize) + -> bool + { for tester in SMALL_PRIMES.iter() { if (self % UCN::from(*tester)).is_zero() { return false; } } - miller_rabin(g, &self, iters) + miller_rabin(g, &self, size, iters) } } -fn miller_rabin(g: &mut G, n: &UCN, iters: usize) -> bool { +fn miller_rabin(g: &mut G, n: &UCN, size: usize, iters: usize) -> bool { let one = UCN::from(1 as u8); let two = UCN::from(2 as u8); let nm1 = n - &one; @@ -86,7 +88,7 @@ fn miller_rabin(g: &mut G, n: &UCN, iters: usize) -> bool { // WitnessLoop: repeat k times 'WitnessLoop: for _k in 0..iters { // pick a random integer a in the range [2, n - 2] - let a = random_in_range(g, &two, &nm1); + let a = random_in_range(g, size, &two, &nm1); // x <- a^d mod n let mut x = a.modexp(&d, &n); // if x = 1 or x = n - 1 then @@ -116,8 +118,9 @@ fn miller_rabin(g: &mut G, n: &UCN, iters: usize) -> bool { true } -fn random_in_range(rng: &mut G, min: &UCN, max: &UCN) -> UCN { - let bitlen = ((max.bits() + 63) / 64) * 64; +fn random_in_range(rng: &mut G, bitlen: usize, min: &UCN, max: &UCN) + -> UCN +{ loop { let candidate = random_number(rng, bitlen); diff --git a/src/cryptonum/unsigned.rs b/src/cryptonum/unsigned.rs index f1642d3..992baec 100644 --- a/src/cryptonum/unsigned.rs +++ b/src/cryptonum/unsigned.rs @@ -11,6 +11,7 @@ pub struct UCN { pub(crate) contents: Vec } +#[derive(Clone,Debug,PartialEq,Eq)] pub struct BarrettUCN { pub(crate) u: UCN, pub(crate) k: usize, @@ -185,7 +186,6 @@ impl UCN { let mylen = self.contents.len() * 8; let mut res = Vec::with_capacity(len); - println!("mylen: {} len: {}", mylen, len); assert!( (len % 8) == 0 ); assert!( mylen <= len ); for _ in 0..(len - mylen) { diff --git a/src/lib.rs b/src/lib.rs index 385e7c6..ec602e8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,15 +10,22 @@ //! when they should use it, and examples. For now, it mostly just fowards //! off to more detailed modules. Help requested! +extern crate digest; extern crate num; #[cfg(test)] #[macro_use] extern crate quickcheck; extern crate rand; +extern crate sha1; +extern crate sha2; /// The cryptonum module provides support for large numbers for use in various /// cryptographically-relevant algorithms. pub mod cryptonum; +/// The rsa module provides support for RSA-related core algorithms, including +/// signing / verification and encryption / decryption. You can also generate +/// key material there. +pub mod rsa; #[cfg(test)] mod test { diff --git a/src/rsa/core.rs b/src/rsa/core.rs new file mode 100644 index 0000000..4348278 --- /dev/null +++ b/src/rsa/core.rs @@ -0,0 +1,159 @@ +use cryptonum::{BarrettUCN,UCN}; +use rand::Rng; + +pub static ACCEPTABLE_KEY_SIZES: [(usize,usize); 8] = + [(512, 7), + (1024, 7), + (2048, 4), + (3072, 3), + (4096, 3), + (7680, 3), + (8192, 3), + (15360, 3)]; + +fn iterations_for_size(l: usize) -> usize { + for &(m, i) in ACCEPTABLE_KEY_SIZES.iter() { + if m == l { + return i; + } + } + panic!("Bad key size, can't get M-R iterations") +} + +pub fn generate_pq(rng: &mut G, e: &UCN, bitlen: usize) -> (UCN, UCN) { + let iterations = iterations_for_size(bitlen); + let sqrt2 = UCN::from(6074001000 as u64); + let topbit = UCN::from(1 as u64) << ((bitlen / 2) - 1); + let minval = sqrt2 << ((bitlen / 2) - 33); + let mindiff = UCN::from(1 as u64) << ((bitlen / 2) - 101); + let validate = |inval| { + let x = &inval | &topbit; + if x < minval { + return None + } + if !gcd_is_one(&e, &x) { + return None + } + Some(x) + }; + + let p = UCN::generate_prime(rng, bitlen / 2, iterations, validate); + loop { + let q = UCN::generate_prime(rng, bitlen / 2, iterations, validate); + + if diff(&p, &q) >= mindiff { + return (p, q); + } + } +} + +fn diff(a: &UCN, b: &UCN) -> UCN { + if a > b { + a - b + } else { + b - a + } +} + +fn gcd_is_one(a: &UCN, b: &UCN) -> bool { + let mut u = a.clone(); + let mut v = b.clone(); + + if u.is_zero() { + return v == UCN::from(1 as u8); + } + + if v.is_zero() { + return u == UCN::from(1 as u8); + } + + if u.is_even() && v.is_even() { + return false; + } + + while u.is_even() { + u >>= 1; + } + + loop { + while v.is_even() { + v >>= 1; + } + // u and v guaranteed to be odd right now. + if u > v { + // make sure that v > u, so that our subtraction works + // out. + let t = u; + u = v; + v = t; + } + v = v - &u; + + if v.is_zero() { + return u == UCN::from(1 as u64); + } + } +} + + +// the RSA encryption function +pub fn ep(nu: &BarrettUCN, e: &UCN, m: &UCN) -> UCN { + m.fastmodexp(e, nu) +} + +// the RSA decryption function +pub fn dp(nu: &BarrettUCN, d: &UCN, c: &UCN) -> UCN { + c.fastmodexp(d, nu) +} + +// the RSA signature generation function +pub fn sp1(nu: &BarrettUCN, d: &UCN, m: &UCN) -> UCN { + m.fastmodexp(d, nu) +} + +// the RSA signature verification function +pub fn vp1(nu: &BarrettUCN, e: &UCN, s: &UCN) -> UCN { + s.fastmodexp(e, nu) +} + +// encoding PKCS1 stuff +pub fn pkcs1_pad(ident: &[u8], hash: &[u8], keylen: usize) -> Vec { + let mut idhash = Vec::new(); + idhash.extend_from_slice(ident); + idhash.extend_from_slice(hash); + let tlen = idhash.len(); + assert!(keylen > (tlen + 3)); + let mut padding = Vec::new(); + padding.resize(keylen - tlen - 3, 0xFF); + let mut result = vec![0x00, 0x01]; + result.append(&mut padding); + result.push(0x00); + result.append(&mut idhash); + result +} + + +#[cfg(test)] +mod tests { + use rand::OsRng; + use super::*; + + #[test] + fn can_get_p_and_q() { + let mut rng = OsRng::new().unwrap(); + let e = UCN::from(65537 as u64); + + for &(size, _) in ACCEPTABLE_KEY_SIZES.iter().take(3) { + let (p,q) = generate_pq(&mut rng, &e, size); + let minval = UCN::from(1 as u8) << ((size / 2) - 1); + assert!(p > minval); + assert!(q > minval); + assert!(p != q); + assert!(p.is_odd()); + assert!(q.is_odd()); + let phi = (p - UCN::from(1 as u64)) * (q - UCN::from(1 as u64)); + let d = e.modinv(&phi); + assert_eq!( (&e * &d) % phi, UCN::from(1 as u64) ); + } + } +} diff --git a/src/rsa/errors.rs b/src/rsa/errors.rs new file mode 100644 index 0000000..29beae0 --- /dev/null +++ b/src/rsa/errors.rs @@ -0,0 +1,14 @@ +use std::io; + +#[derive(Debug)] +pub enum RSAKeyGenError { + InvalidKeySize(usize), RngFailure(io::Error) +} + +impl From for RSAKeyGenError { + fn from(e: io::Error) -> RSAKeyGenError { + RSAKeyGenError::RngFailure(e) + } +} + + diff --git a/src/rsa/mod.rs b/src/rsa/mod.rs new file mode 100644 index 0000000..b262d5c --- /dev/null +++ b/src/rsa/mod.rs @@ -0,0 +1,104 @@ +mod core; +mod errors; +mod public; +mod private; +mod signing_hashes; + +pub use self::public::RSAPublic; +pub use self::private::RSAPrivate; +pub use self::signing_hashes::{SigningHash, + SIGNING_HASH_NULL, SIGNING_HASH_SHA1, + SIGNING_HASH_SHA224, SIGNING_HASH_SHA256, + SIGNING_HASH_SHA384, SIGNING_HASH_SHA512}; + +use cryptonum::UCN; +use rand::{OsRng,Rng}; +use self::core::{ACCEPTABLE_KEY_SIZES,generate_pq}; +use self::errors::*; + +#[derive(Clone,Debug)] +pub struct RSAKeyPair { + pub public: RSAPublic, + pub private: RSAPrivate +} + +impl RSAKeyPair { + /// Generates a fresh RSA key pair of the given bit size. Valid bit sizes + /// are 512, 1024, 2048, 3072, 4096, 7680, 8192, and 15360. If you + /// actually want to protect data, use a value greater than or equal to + /// 2048. If you don't want to spend all day waiting for RSA computations + /// to finish, choose a value less than or equal to 4096. + /// + /// This routine will use `OsRng` for entropy. If you want to use your + /// own random number generator, use `generate_w_rng`. + pub fn generate(len_bits: usize) -> Result { + let mut rng = OsRng::new()?; + RSAKeyPair::generate_w_rng(&mut rng, len_bits) + } + + /// Generates a fresh RSA key pair of the given bit size. Valid bit sizes + /// are 512, 1024, 2048, 3072, 4096, 7680, 8192, and 15360. If you + /// actually want to protect data, use a value greater than or equal to + /// 2048. If you don't want to spend all day waiting for RSA computations + /// to finish, choose a value less than or equal to 4096. + /// + /// If you provide your own random number generator that is not `OsRng`, + /// you should know what you're doing, and be using a cryptographically- + /// strong RNG of your own choosing. We've warned you. Use a good one. + /// So now it's on you. + pub fn generate_w_rng(rng: &mut G, len_bits: usize) + -> Result + { + let e = UCN::from(65537 as u32); + + for &(length, _) in ACCEPTABLE_KEY_SIZES.iter() { + if length == len_bits { + let (p, q) = generate_pq(rng, &e, len_bits); + let n = &p * &q; + let one = UCN::from(1 as u8); + let phi = (p - &one) * (q - &one); + let d = e.modinv(&phi); + let public_key = RSAPublic::new(n.clone(), e); + let private_key = RSAPrivate::new(n, d); + return Ok(RSAKeyPair{ + private: private_key, + public: public_key + }) + } + } + + Err(RSAKeyGenError::InvalidKeySize(len_bits)) + } + +} + +#[cfg(test)] +mod tests { + use quickcheck::{Arbitrary,Gen}; + use rsa::core::{ep,dp,sp1,vp1}; + use super::*; + + const TEST_KEY_SIZES: [usize; 2] = [512, 1024]; + + impl Arbitrary for RSAKeyPair { + fn arbitrary(g: &mut G) -> RSAKeyPair { + let size = g.choose(&TEST_KEY_SIZES).unwrap(); + RSAKeyPair::generate_w_rng(g, *size).unwrap() + } + } + + quickcheck! { + fn rsa_ep_dp_inversion(kp: RSAKeyPair, n: UCN) -> bool { + let m = n.reduce(&kp.public.nu); + let ciphertext = ep(&kp.public.nu, &kp.public.e, &m); + let mprime = dp(&kp.private.nu, &kp.private.d, &ciphertext); + mprime == m + } + fn rsa_sp_vp_inversion(kp: RSAKeyPair, n: UCN) -> bool { + let m = n.reduce(&kp.public.nu); + let sig = sp1(&kp.private.nu, &kp.private.d, &m); + let mprime = vp1(&kp.public.nu, &kp.public.e, &sig); + mprime == m + } + } +} diff --git a/src/rsa/private.rs b/src/rsa/private.rs new file mode 100644 index 0000000..85e61c7 --- /dev/null +++ b/src/rsa/private.rs @@ -0,0 +1,64 @@ +use cryptonum::{BarrettUCN,UCN}; +use rsa::core::{ACCEPTABLE_KEY_SIZES,pkcs1_pad,sp1}; +use rsa::signing_hashes::SigningHash; +use std::fmt; + +#[derive(Clone)] +pub struct RSAPrivate { + pub(crate) byte_len: usize, + pub(crate) n: UCN, + pub(crate) nu: BarrettUCN, + pub(crate) d: UCN +} + +#[cfg(test)] +impl fmt::Debug for RSAPrivate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("RSAPrivate") + .field("byte_len", &self.byte_len) + .field("n", &self.n) + .field("nu", &self.nu) + .field("d", &self.d) + .finish() + } +} + +#[cfg(test)] +impl PartialEq for RSAPrivate { + fn eq(&self, rhs: &RSAPrivate) -> bool { + (self.byte_len == rhs.byte_len) && + (self.n == rhs.n) && + (self.nu == rhs.nu) && + (self.d == rhs.d) + } +} + +#[cfg(test)] +impl Eq for RSAPrivate {} + +#[cfg(not(test))] +impl fmt::Debug for RSAPrivate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("RSAPrivateKey") + } +} + +impl RSAPrivate { + /// Create a new RSA public key from the given components, which you found + /// via some other mechanism. + pub fn new(n: UCN, d: UCN) -> RSAPrivate { + let len = n.bits(); + + for &(valid_bits, _) in ACCEPTABLE_KEY_SIZES.iter() { + if valid_bits > len { + return RSAPrivate { + byte_len: valid_bits / 8, + n: n.clone(), + nu: n.barrett_u(), + d: d.clone() + }; + } + } + panic!("Invalid RSA key size in new()") + } +} diff --git a/src/rsa/public.rs b/src/rsa/public.rs new file mode 100644 index 0000000..7599e15 --- /dev/null +++ b/src/rsa/public.rs @@ -0,0 +1,30 @@ +use cryptonum::{BarrettUCN,UCN}; +use rsa::core::ACCEPTABLE_KEY_SIZES; + +#[derive(Clone,Debug,PartialEq,Eq)] +pub struct RSAPublic { + pub(crate) byte_len: usize, + pub(crate) n: UCN, + pub(crate) nu: BarrettUCN, + pub(crate) e: UCN +} + +impl RSAPublic { + /// Create a new RSA public key from the given components, which you found + /// via some other mechanism. + pub fn new(n: UCN, e: UCN) -> RSAPublic { + let len = n.bits(); + + for &(valid_bits, _) in ACCEPTABLE_KEY_SIZES.iter() { + if valid_bits > len { + return RSAPublic{ + byte_len: valid_bits / 8, + n: n.clone(), + nu: n.barrett_u(), + e: e.clone() + }; + } + } + panic!("Invalid RSA key size in new()") + } +} diff --git a/src/rsa/signing_hashes.rs b/src/rsa/signing_hashes.rs new file mode 100644 index 0000000..0b40955 --- /dev/null +++ b/src/rsa/signing_hashes.rs @@ -0,0 +1,119 @@ +use digest::{FixedOutput,Input}; +use sha1::Sha1; +use sha2::{Sha224,Sha256,Sha384,Sha512}; +use std::fmt; + +/// A hash that can be used to sign a message. +#[derive(Clone)] +pub struct SigningHash { + /// The name of this hash (only used for display purposes) + pub name: &'static str, + /// The approved identity string for the hash. + pub ident: &'static [u8], + /// The hash + pub run: fn(&[u8]) -> Vec +} + +impl fmt::Debug for SigningHash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.name) + } +} + +/// The "null" signing hash. This signing hash has no identity, and will +/// simply pass the data through unhashed. You really should know what +/// you're doing if you use this, and probably using a somewhat strange +/// signing protocol. There's no good reason to use this in new code +/// for a new protocol or system. +pub static SIGNING_HASH_NULL: SigningHash = SigningHash { + name: "NULL", + ident: &[], + run: nohash +}; + +fn nohash(i: &[u8]) -> Vec { + i.to_vec() +} + +/// Sign a hash based on SHA1. You shouldn't use this unless you're using +/// very small keys, and this is the only one available to you. Even then, +/// why are you using such small keys?! +pub static SIGNING_HASH_SHA1: SigningHash = SigningHash { + name: "SHA1", + ident: &[0x30,0x21,0x30,0x09,0x06,0x05,0x2b,0x0e,0x03, + 0x02,0x1a,0x05,0x00,0x04,0x14], + run: runsha1 +}; + +fn runsha1(i: &[u8]) -> Vec { + let mut d = Sha1::default(); + d.process(i); + d.fixed_result().as_slice().to_vec() +} + +/// Sign a hash based on SHA2-224. This is the first reasonable choice +/// we've come across, and is useful when you have smaller RSA key sizes. +/// I wouldn't recommend it, though. +pub static SIGNING_HASH_SHA224: SigningHash = SigningHash { + name: "SHA224", + ident: &[0x30,0x2d,0x30,0x0d,0x06,0x09,0x60,0x86,0x48, + 0x01,0x65,0x03,0x04,0x02,0x04,0x05,0x00,0x04, + 0x1c], + run: runsha224 +}; + +fn runsha224(i: &[u8]) -> Vec { + let mut d = Sha224::default(); + d.process(i); + d.fixed_result().as_slice().to_vec() +} + +/// Sign a hash based on SHA2-256. The first one I'd recommend! +pub static SIGNING_HASH_SHA256: SigningHash = SigningHash { + name: "SHA256", + ident: &[0x30,0x31,0x30,0x0d,0x06,0x09,0x60,0x86,0x48, + 0x01,0x65,0x03,0x04,0x02,0x01,0x05,0x00,0x04, + 0x20], + run: runsha256 +}; + +fn runsha256(i: &[u8]) -> Vec { + let mut d = Sha256::default(); + d.process(i); + d.fixed_result().as_slice().to_vec() +} + +/// Sign a hash based on SHA2-384. Approximately 50% better than +/// SHA-256. +pub static SIGNING_HASH_SHA384: SigningHash = SigningHash { + name: "SHA384", + ident: &[0x30,0x41,0x30,0x0d,0x06,0x09,0x60,0x86,0x48, + 0x01,0x65,0x03,0x04,0x02,0x02,0x05,0x00,0x04, + 0x30], + run: runsha384 +}; + +fn runsha384(i: &[u8]) -> Vec { + let mut d = Sha384::default(); + d.process(i); + d.fixed_result().as_slice().to_vec() +} + +/// Sign a hash based on SHA2-512. At this point, you're getting a bit +/// silly. But if you want to through 8kbit RSA keys with a 512 bit SHA2 +/// signing hash, we're totally behind you. +pub static SIGNING_HASH_SHA512: SigningHash = SigningHash { + name: "SHA512", + ident: &[0x30,0x51,0x30,0x0d,0x06,0x09,0x60,0x86,0x48, + 0x01,0x65,0x03,0x04,0x02,0x03,0x05,0x00,0x04, + 0x40], + run: runsha512 +}; + +fn runsha512(i: &[u8]) -> Vec { + let mut d = Sha512::default(); + d.process(i); + d.fixed_result().as_slice().to_vec() +} + +