diff --git a/src/main.rs b/src/main.rs index 1f8dcc9..0c7fefc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,6 +39,22 @@ use std::io; /// # Get the secret back /// /// rssss extract recovered-secret +/// +/// Choose your prime based on the size of data to be stored and your desired security level +/// +/// Implemented primes: +/// +/// M12: (127 bits) 2^127-1 +/// P384: (384 bits) 2^384-2^128-2^96+2^32-1 +/// P448: (448 bits) 2^448-2^224-1 +/// M13: (521 bits) 2^521-1 +/// M19: (4253 bits) 2^4253-1 +/// M34: (~1.2MBit) 2^1257787-1 +/// +/// M19 should only be used for *extremely* secure sharing of RSA keys; M34 +/// probably shouldn't be used at all. It will likely be extremely slow; +/// you should rather generate shares of an AES key and use that to encrypt your real secret + #[derive(StructOpt)] enum Cmdline { /// Generate a poly from a secret @@ -93,6 +109,10 @@ struct ExtractSecret { /// This is equivalent to solve followed by extract, but without the intermediate step #[derive(StructOpt)] struct Combine { + #[structopt(short="p", long="prime", default_value="p384", parse(try_from_str))] + prime: rssss::primes::Prime, + #[structopt(parse(from_os_str))] + data: Vec, } fn main() { @@ -156,8 +176,10 @@ impl Command for Solve { .and_then(|f| rssss::s4::Share::read_from(f, self.prime)) .map_err(|err| Error::from(err) .context(format!("Failed to read {:?}", filename)))?) - }).collect::, Error>>(); + }).collect::, Error>>()?; + let poly = rssss::s4::solve(&shares[..]); + serde_cbor::to_writer(&mut io::stdout(), &poly)?; Ok(()) } } @@ -176,7 +198,23 @@ impl Command for ExtractSecret { impl Command for Combine { fn run(&self) -> Result<(), Error> { - unimplemented!() + use std::fs::File; + use std::io::Write; + let shares = self.data.iter().map(|filename| { + Ok(File::open(&filename) + .and_then(|f| rssss::s4::Share::read_from(f, self.prime)) + .map_err(|err| Error::from(err) + .context(format!("Failed to read {:?}", filename)))?) + }).collect::, Error>>()?; + + let secret = rssss::s4::Secret::from(rssss::s4::interpolate0(&shares[..])); + + if !secret.check_crc() { + eprintln!("CRC failed; not enough shares or corrupted share") + } + + io::stdout().write_all(&secret[..])?; + Ok(()) } } diff --git a/src/poly.rs b/src/poly.rs index 46e9829..c70f78f 100644 --- a/src/poly.rs +++ b/src/poly.rs @@ -45,6 +45,16 @@ impl Poly { } result } + + pub fn trim(&mut self) { + while !self.coeff.is_empty() { + if self.coeff[self.coeff.len()-1].is_zero() { + self.coeff.pop(); + } else { + break + } + } + } } impl Add<&Poly> for Poly { @@ -179,4 +189,12 @@ impl Neg for Poly { } self } +} + +#[cfg(test)] +mod test { + use super::Poly; + use num_bigint::BigUint; + use crate::primes::Prime; + } \ No newline at end of file diff --git a/src/primes.rs b/src/primes.rs index dde6389..e6e74a8 100644 --- a/src/primes.rs +++ b/src/primes.rs @@ -10,7 +10,7 @@ lazy_static!{ // 2^384 - 2^128 - 2^96 + 2^32 - 1 // Chosen to be large enough to hold any AES key static ref PRIME_P384: BigUint = BigUint::new(vec![ - 0xFFFFFFFF, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFE, + 0xFFFFFFFF, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, ]); @@ -18,17 +18,36 @@ lazy_static!{ static ref PRIME_P448: BigUint = BigUint::new(vec![ 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFE, - 0xFFFFFFFE, 0xFFFFFFFF, 0xFFFFFFFE, 0xFFFFFFFF, - 0xFFFFFFFE, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, + 0xFFFFFFFF, 0xFFFFFFFF, ]); + + static ref PRIME_M12: BigUint = BigUint::new(vec![ + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x7FFFFFFF, + ]); + + static ref PRIME_M13: BigUint = (BigUint::one() << 521) - BigUint::one(); + static ref PRIME_M19: BigUint = (BigUint::one() << 4253) - BigUint::one(); + static ref PRIME_M34: BigUint = (BigUint::one() << 1257787) - BigUint::one(); } +// Other primes to possibly implemet: +// 2^521-1 +// 2^384 - 2^128 - 2^96 + 2^32 - 1 (still a bit painful) +// 2^448 - 2^224 - 1 +// 2^256 - 2^32 - 977 + + #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)] pub enum Prime { // 2^384 - 2^128 - 2^96 + 2^32 - 1 P384, // 2^448 - 2^224 - 1 P448, + M12, + M13, + M19, + M34, } impl Prime { @@ -36,6 +55,10 @@ impl Prime { match self { Prime::P384 => 1, Prime::P448 => 2, + Prime::M12 => 3, + Prime::M13 => 4, + Prime::M19 => 5, + Prime::M34 => 6, } } @@ -43,6 +66,10 @@ impl Prime { match self { Prime::P448 => 56, Prime::P384 => 48, + Prime::M12 => 16, + Prime::M13 => 66, + Prime::M19 => 532, + Prime::M34 => 157224, } } @@ -50,15 +77,24 @@ impl Prime { match self { Prime::P384 => &PRIME_P384, Prime::P448 => &PRIME_P448, + Prime::M12 => &PRIME_M12, + Prime::M13 => &PRIME_M13, + Prime::M19 => &PRIME_M19, + Prime::M34 => &PRIME_M34, } } pub fn inverse(self, value: BigUint) -> BigUint { - ext_gcd(value, self.modulus().clone()).0 + let saved_value = value.clone(); + let a = ext_gcd(self, value, self.modulus().clone()).0; + assert_eq!((&a * saved_value) % self.modulus(), BigUint::one()); + a } - pub fn negate(self, value: &BigUint) -> BigUint { - self.modulus() - value + pub fn negate(self, mut value: &BigUint) -> BigUint { + let result = self.modulus() - (value % self.modulus()); + assert_eq!(BigUint::zero(), (&result + value) % self.modulus()); + result } } @@ -70,12 +106,16 @@ impl FromStr for Prime { match s.to_lowercase().as_str() { "p384" => Ok(Prime::P384), "p448" => Ok(Prime::P448), - _ => Err(failure::err_msg("Invalid prime spec; expected P384 or P448")), + "m12" => Ok(Prime::M12), + "m13" => Ok(Prime::M13), + "m19" => Ok(Prime::M19), + "m34" => Ok(Prime::M34), + _ => Err(failure::err_msg("Invalid prime spec; expected P384, P448, M12, M13, M19, or M34")), } } } -fn ext_gcd(mut a: BigUint, mut b: BigUint) -> (BigUint, BigUint) { +fn ext_gcd(prime: Prime, mut a: BigUint, mut b: BigUint) -> (BigUint, BigUint) { use std::mem::swap; let mut x = BigUint::zero(); let mut last_x = BigUint::one(); @@ -84,17 +124,17 @@ fn ext_gcd(mut a: BigUint, mut b: BigUint) -> (BigUint, BigUint) { while !b.is_zero() { let quot = &a / &b; - swap(&mut a, &mut b); - b %= &a; - swap(&mut x, &mut last_x); - swap(&mut y, &mut last_y); + let new_b = a % &b; + a = b; + b = new_b; + let new_x = (last_x + prime.negate(&(&x * "))) % prime.modulus(); + let new_y = (last_y + prime.negate(&(&y * "))) % prime.modulus(); - last_x *= " - x -= &last_x; - - last_y *= quot; - y -= &last_y; + last_x = x; + last_y = y; + x = new_x; + y = new_y; } (last_x, last_y) } @@ -104,13 +144,16 @@ mod test { use num_bigint::BigUint; use super::Prime; + // These definitions are much slower than the above static ones, but in + // exchange, they're much more likely to be correct #[test] fn check_value_of_p384() { let one = &BigUint::new(vec![1]); - assert_eq!(Prime::P384::modulus, one << 384 - one - one << 128 - one << 96 - one << 32); + assert_eq!(Prime::P384.modulus(), &((one << 384) - one - (one << 128) - (one << 96) + (one << 32))); } + #[test] fn check_value_of_p448() { let one = &BigUint::new(vec![1]); - assert_eq!(Prime::P448::modulus, one << 448 - one - one << 224); + assert_eq!(Prime::P448.modulus(), &((one << 448) - one - (one << 224))); } } \ No newline at end of file diff --git a/src/s4.rs b/src/s4.rs index ec5ccbc..e640ad2 100644 --- a/src/s4.rs +++ b/src/s4.rs @@ -1,10 +1,13 @@ use std::io::{self, prelude::*}; use std::ops::Deref; - use num_bigint::BigUint; use serde::Serialize; +use crate::poly::Poly; +use crate::primes::Prime; + + /// Structure /// | off | len | meaning | /// | 0 | 1 | Length of payload (L) @@ -14,7 +17,7 @@ use serde::Serialize; /// this way, the high bit is always unset #[derive(Default,Clone, Debug)] pub struct Secret { - length: u8, + length: u16, data: Vec, crc32: u32, @@ -24,12 +27,10 @@ impl Secret { pub fn new(data: Vec) -> Self { if data.len() == 0 { panic!("Must have at least one byte of payload"); - } else if data.len() > 127 { - panic!("Too much data"); } let mut res = Secret { - length: data.len() as u8, + length: data.len() as u16, data, crc32: 0, }; @@ -43,7 +44,7 @@ impl Secret { use crc::crc32::{CASTAGNOLI, Digest, Hasher32}; let mut hasher = Digest::new(CASTAGNOLI); hasher.reset(); - hasher.write(&[self.length]); + hasher.write(&[1]); hasher.write(&self.data[..]); hasher.write(&[0, 0, 0, 0]); hasher.sum32() @@ -68,10 +69,14 @@ impl From for Secret { use byteorder::{BigEndian, ByteOrder}; let decoded = ui.to_bytes_be(); let mut res = Secret::default(); - res.length = decoded[0]; - let len = res.length as usize; - res.data = decoded[1..len+1].to_owned(); - res.crc32 = BigEndian::read_u32(&decoded[len+1..len+5]); + if decoded[0] != 1 { + panic!("Unknown version {}", decoded[0]); + } + if decoded.len() < 5 { + panic!("Data too short"); + } + res.data = decoded[1..decoded.len() - 4].to_owned(); + res.crc32 = BigEndian::read_u32(&decoded[decoded.len()-4 .. ]); res } @@ -82,7 +87,7 @@ impl From for BigUint { use std::io::Write; use byteorder::{WriteBytesExt, BigEndian}; let mut buf = vec![]; - buf.write(&[obj.length]); + buf.write(&[1]); buf.write(&obj.data[..]); buf.write_u32::(obj.crc32); BigUint::from_bytes_be(&buf[..]) @@ -135,4 +140,41 @@ impl Share { Ok(Share{ prime, x, y }) } +} + +pub fn solve(shares: &[Share]) -> Poly { + let prime = shares[0].prime; + let mut result = Poly::zero(prime); + for j in 0..shares.len() { + let mut basis = Poly::zero(prime) + &shares[j].y; + for m in 0..shares.len() { + if j == m { + continue; + } + basis = (Poly::x(prime) + prime.negate(&shares[m].x)) + / (&shares[j].x + prime.negate(&shares[m].x)) * &basis; + } + result += &basis; + } + result.trim(); + result +} + +pub fn interpolate0(shares: &[Share]) -> BigUint { + use num_traits::Zero; + let prime = shares[0].prime; + let mut result = BigUint::zero(); + for j in 0..shares.len() { + let mut basis = shares[j].y.clone(); + for m in 0..shares.len() { + if j == m { + continue; + } + basis *= &shares[m].x * + prime.inverse(&shares[m].x + prime.negate(&shares[j].x)); + basis %= prime.modulus(); + } + result += basis; + } + result % prime.modulus() } \ No newline at end of file