Skip to content

Commit

Permalink
Completely refactoring movingwindow - much better now
Browse files Browse the repository at this point in the history
  • Loading branch information
phayes committed Mar 11, 2020
1 parent 73baed3 commit e96824a
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 50 deletions.
13 changes: 8 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,21 @@ categories = ["cryptography", "no-std"]

[dependencies]
digest = "0.8.1"
failure = {version = "0.1.7", optional = true}
num-bigint = { version = "0.6", optional=true, features = ["zeroize"], package = "num-bigint-dig" }
bitvec = "0.17.3"
subtle = "2.2.2"

# Should use same version as in "digest"
generic-array = "0.12.3"
failure = {version = "0.1.6", optional = true}
num-bigint = {version="0.2.6", optional = true}
bitvec = "0.17.2"

[dev-dependencies]
sha2 = "0.8.1"
sha-1 = "0.8.2"
sha3 = "0.8.2"
hex = "0.4.1"
hex = "0.4.2"
num-traits = "0.2.11"
num-integer = "0.1.42"
num-integer = "0.1.42"

[badges]
travis-ci = { repository = "phayes/fdh-rs", branch = "master" }
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ use num_bigint::BigUint;
#[cfg(feature = "std")]
extern crate std;

#[cfg(feature = "std")]
pub mod movingwindow;

#[derive(Clone, Debug, Default)]
pub struct FullDomainHash<H: Digest> {
output_size: usize,
Expand Down
10 changes: 5 additions & 5 deletions src/movingwindow/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
Moving Window FDH
-----------------

**This experiment has been shown to be very broken and insecure - don't use it**
Moving Window Full Domain Hash (MWFDH)
--------------------------------------

This is an experimental Full Domain Hash (FDH) that is constructed of a moving-window applied against an extendable ouput (XOF) hash function. Unlike a regular Full Domain Hash, it is designed to be contant-time.

This was experiemental, and has been shown to be insecure and broken.
## A note on security

Given the output of a MWFDH, the message hashed with a MWFDH is secure against recovery (in the normal way for one-way hash functions), however the moving window does not provide any level of secrecy to the underlying XOF hash digest outside of it's window. This means that the output of the underlying XOF should be considered "exposed" to anyone who has the final output of the MWFDH. For its intended purpose, this is acceptable, but protocol designers using the MWFDH should be aware of this poroperty of the MWFDH.

### Background and Rational

Expand Down
172 changes: 132 additions & 40 deletions src/movingwindow/mod.rs
Original file line number Diff line number Diff line change
@@ -1,57 +1,111 @@
use bitvec::*;
use bitvec::order::Msb0;
use bitvec::vec::*;
use digest::{ExtendableOutput, Input, XofReader};
use num_bigint::BigUint;
use std::vec;
use std::vec::Vec;
use subtle::Choice;

pub struct MWFullDomainHash<H: ExtendableOutput + Input + Default> {
pub struct MWFDH<H, C>
where
H: ExtendableOutput + Input + Default + Clone,
C: Fn(&[u8]) -> Choice,
{
iterations: usize,
output_size: usize,
inner_hash: H,
domain_function: C,
}

impl<H: ExtendableOutput + Input + Default> MWFullDomainHash<H> {
pub fn new(output_size: usize) -> Self {
MWFullDomainHash {
impl<H, C> MWFDH<H, C>
where
H: ExtendableOutput + Input + Default + Clone,
C: Fn(&[u8]) -> Choice,
{
pub fn new(iterations: usize, output_size: usize, domain_function: C) -> Self {
if iterations % 8 != 0 {
panic!("fdh-rs: movingwindow: iterations must be multiple of 8");
}

MWFDH {
iterations: iterations,
output_size: output_size,
inner_hash: H::default(),
domain_function: domain_function,
}
}

pub fn input(&mut self, input: &[u8]) {
self.inner_hash.input(input);
}

pub fn result_below(self, max: &[u8]) -> Vec<u8> {
let max = left_pad(max, self.output_size); // TODO: Can we skip this in the commong case when lengths match?

let mut reader = self.inner_hash.xof_result();
let mut result: Vec<u8> = vec![0x00; self.output_size];
reader.read(&mut result);
/// Get a digest value that is within the domain specified by the passed closure.
///
/// # Example
/// ```rust
/// use sha2::Sha512;
/// use fdh::{FullDomainHash, Input, VariableOutput};
/// use num_bigint::BigUint;
/// use num_integer::Integer;
///
/// // Get a full domain hash that is odd
/// let mut hasher = FullDomainHash::<Sha512>::new(64).unwrap();
/// hasher.input(b"ATTACKATDAWN");
///
/// let (digest, iv) = hasher.results_in_domain(0, |digest| BigUint::from_bytes_be(digest).is_odd()).unwrap();
/// ```
pub fn results_in_domain(&self) -> Result<Vec<u8>, ()> {
let mut all_candidates = self.all_candidates();

if &result as &[u8] < &max {
// Happy path
return result.into();
} else {
let mut result: BitVec<bitvec::BigEndian, u8> = result.into();
let mut read_buf = BitVec::<bitvec::BigEndian, u8>::with_capacity(8);
let max: BitVec<bitvec::BigEndian, u8> = max.into();
let mut read_buf_pos = 0;
while result > max {
if read_buf_pos == 0 {
let mut temp_buf: Vec<u8> = vec![0x00];
reader.read(&mut temp_buf);
read_buf = temp_buf.into();
}
result = result << 1;
result.push(read_buf[read_buf_pos]);

read_buf_pos += 1;
if read_buf_pos == 8 {
read_buf_pos = 0;
}
let mut selection: usize = 0;
let mut in_domain: Choice = 0.into();
for candidate in all_candidates.iter() {
in_domain |= (self.domain_function)(candidate);
if in_domain.into() {
selection += 0;
} else {
selection += 1;
}
result.into()
}

let found_domain: bool = in_domain.into();
if (!found_domain) {
return Err(());
}

let result: Vec<u8> = all_candidates.remove(selection);
Ok(result)
}

fn all_candidates(&self) -> Vec<Vec<u8>> {
let inner_hash = self.inner_hash.clone();
let mut reader = inner_hash.xof_result();
let underlying_size = self.output_size * (self.iterations / 8);
let mut result: Vec<u8> = vec![0x00; underlying_size];
reader.read(&mut result);

compute_candidates(result, self.output_size, self.iterations)
}
}

// Given a Vec<u8> (as you would get from an underlying digest),
// get a Vec of all moving windows applied against that input
fn compute_candidates(
input: Vec<u8>,
moving_window_size: usize,
num_iterations: usize,
) -> Vec<Vec<u8>> {
// TODO: debug assert input.len(), moving_window_size, and num_iterations all line up
let mut underlying_digest: BitVec<Msb0, u8> = input.into();
let mut all_candidates = Vec::<Vec<u8>>::with_capacity(num_iterations);

for _ in 0..num_iterations {
let u8_view = underlying_digest.as_slice();
all_candidates.push(u8_view[0..moving_window_size].iter().cloned().collect());
underlying_digest = underlying_digest << 1;
}

all_candidates
}

fn left_pad(input: &[u8], size: usize) -> std::vec::Vec<u8> {
Expand All @@ -66,24 +120,62 @@ fn left_pad(input: &[u8], size: usize) -> std::vec::Vec<u8> {
out
}

fn between(check: &[u8], min: &BigUint, max: &BigUint) -> Choice {
let check = BigUint::from_bytes_be(check);
((&check < max && &check > min) as u8).into()
}

fn lt(check: &[u8], max: &BigUint) -> Choice {
let check = BigUint::from_bytes_be(check);
((&check < max) as u8).into()
}

fn gt(check: &[u8], min: &BigUint) -> Choice {
let check = BigUint::from_bytes_be(check);
((&check > min) as u8).into()
}

#[cfg(test)]
mod tests {
use crate::movingwindow::MWFullDomainHash;
use super::*;
use num_bigint::BigUint;
use num_traits::Num;
use sha3::Shake128;
use std::dbg;

#[test]
fn all_candidates_test() {
let some_vec = vec![0, 0, 0, 0, 0, 255];
let candidates = compute_candidates(some_vec, 5, 8);

assert_eq!(candidates[0], vec![0, 0, 0, 0, 0]);
assert_eq!(candidates[7], vec![0, 0, 0, 0, 127]);
// IF we had shifted one more we would have [0, 0, 0, 0, 255]
}

#[test]
fn bit_test() {
fn test_results_within() {
use num_bigint::BigUint;
use num_traits::Num;

let min = BigUint::from_str_radix(
"51683095453715361952842063988888814558178328011011413557662527675023521115731",
10,
)
.unwrap();
let max = BigUint::from_str_radix(
"00523095453715361952842063988888814558178328011011413557662527675023521115731",
"63372381656167118369940880608146415619543459354936568979731399163319071519847",
10,
)
.unwrap();

let mut mwfdh = MWFullDomainHash::<Shake128>::new(256 / 8);
mwfdh.input(b"ATTACK AT DAWN");
let _result = mwfdh.result_below(&max.to_bytes_be());
}
let mut hasher = MWFDH::<Shake128, _>::new(128, 32, |check: _| between(check, &min, &max));

hasher.input(b"ATTACK AT DAWN");
let result = hasher.results_in_domain().unwrap();
assert_eq!(
hex::encode(result),
"7ebe111e3d443145d87f7b574f67f92be291f19d747a489601e40bd6f3671008"
);
}
}

0 comments on commit e96824a

Please sign in to comment.