Add support for SHAKE128 and SHAKE256.

This commit is contained in:
2019-06-08 14:56:59 -07:00
parent c675aaa5f6
commit 7c45f898ab
6 changed files with 12954 additions and 6 deletions

View File

@@ -41,6 +41,10 @@ pub mod ed25519;
/// The `ssh` module provides support for parsing OpenSSH-formatted SSH keys, /// The `ssh` module provides support for parsing OpenSSH-formatted SSH keys,
/// both public and private. /// both public and private.
pub mod ssh; pub mod ssh;
/// The `shake` modules provides support for SHAKE128 and SHAKE256, two
/// variable-length hash functions that derive from the same core hash
/// as SHA3.
pub mod shake;
/// The `x509` module supports parsing and generating x.509 certificates, as /// The `x509` module supports parsing and generating x.509 certificates, as
/// used by TLS and others. /// used by TLS and others.
pub mod x509; pub mod x509;

View File

@@ -61,3 +61,4 @@ pub use super::Hash;
pub use self::sha1::SHA1; pub use self::sha1::SHA1;
pub use self::sha2::{SHA224,SHA256,SHA384,SHA512}; pub use self::sha2::{SHA224,SHA256,SHA384,SHA512};
pub use self::sha3::{SHA3_224,SHA3_256,SHA3_384,SHA3_512}; pub use self::sha3::{SHA3_224,SHA3_256,SHA3_384,SHA3_512};
pub(crate) use self::sha3::Keccak;

View File

@@ -1,6 +1,6 @@
use super::super::Hash; use super::super::Hash;
struct Keccak { pub(crate) struct Keccak {
rate_in_bytes: usize, rate_in_bytes: usize,
rate_in_longs: usize, rate_in_longs: usize,
buffer: Vec<u8>, buffer: Vec<u8>,
@@ -47,7 +47,7 @@ macro_rules! absorb {
} }
impl Keccak { impl Keccak {
fn new(rate: usize) -> Self pub fn new(rate: usize) -> Self
{ {
assert_eq!(rate % 64, 0); assert_eq!(rate % 64, 0);
Keccak { Keccak {
@@ -151,7 +151,7 @@ impl Keccak {
} }
} }
fn process(&mut self, bytes: &[u8]) pub fn process(&mut self, bytes: &[u8])
{ {
if self.output.is_none() { if self.output.is_none() {
let mut offset = 0; let mut offset = 0;
@@ -184,7 +184,7 @@ impl Keccak {
} }
} }
fn tag_and_pad(&mut self, tag_byte: u8) pub fn tag_and_pad(&mut self, tag_byte: u8)
{ {
if self.output.is_none() { if self.output.is_none() {
assert!(self.buffer.len() < self.rate_in_bytes); assert!(self.buffer.len() < self.rate_in_bytes);
@@ -197,7 +197,7 @@ impl Keccak {
} }
} }
fn squeeze(&mut self, output_len: usize) -> Vec<u8> pub fn squeeze(&mut self, output_len: usize) -> Vec<u8>
{ {
if let Some(ref result) = self.output { if let Some(ref result) = self.output {
result.clone() result.clone()

215
src/shake.rs Normal file
View File

@@ -0,0 +1,215 @@
//! This module implements the SHAKE family of variable-length hash functions,
//! which NIST also referes to as Extendable-Output Functions (XOFs). They are
//! based on the same underlying hashing mechanism used in SHA3, but can be
//! tuned to output a variety of different hash lengths. One trick is that the
//! security of the hash is the minimum of the defined bit size (128 for
//! SHAKE128, or 256 for SHAKE256) and the output hash length, so if you use
//! shorter hashes you lose some amount of collision protection.
//!
//! Because the output is variable length, these don't quite fit into the
//! normal `Hash` trait. Instead, they implement the same basic functions,
//! but with `hash` and `finalize` functions extended with an additional
//! output length function. Usage is thus in the analagous way to normal
//! hashing:
//!
//! ```rust
//! use simple_crypto::shake::SHAKE128;
//!
//! // Use SHAKE incrementally
//! let empty = [0; 0];
//! let mut shakef = SHAKE128::new();
//! shakef.update(&empty);
//! let result_inc = shakef.finalize(384);
//! // Use SHAKE directly
//! let result_dir = SHAKE128::hash(&empty, 384);
//! // ... and the answers should be the same.
//! assert_eq!(result_inc, result_dir);
//! ```
use sha::Keccak;
/// The SHAKE128 variable-length hash.
///
/// This generates a variable-length hash value, although it's not necessarily as
/// strong as a hash of the same value. My understanding (which is admittedly
/// limited; I've never seen these used) is that this is more for convenience
/// when you want to fit into particularly-sized regions. The 128 is the
/// approximate maximum bit strength of the hash in bits; the true strength is
/// the minimum of the length of the output hash and 128.
///
/// `SHAKE128` does not implement `Hash`, because it is finalized differently,
/// but we've kept something of the flavor of the `Hash` interface for
/// familiarity.
///
/// Like the SHA3 variants, this can be used incrementally or directly, as per
/// usual:
///
/// ```rust
/// use simple_crypto::shake::SHAKE128;
///
/// // Use SHAKE incrementally
/// let empty = [0; 0];
/// let mut shakef = SHAKE128::new();
/// shakef.update(&empty);
/// let result_inc = shakef.finalize(384);
/// // Use SHAKE directly
/// let result_dir = SHAKE128::hash(&empty, 384);
/// // ... and the answers should be the same.
/// assert_eq!(result_inc, result_dir);
/// ```
pub struct SHAKE128 {
state: Keccak
}
impl SHAKE128 {
/// Create a fresh, new SHAKE128 instance for incremental use.
pub fn new() -> Self
{
SHAKE128{
state: Keccak::new(1600 - 256)
}
}
/// Add more data into the hash function for processing.
pub fn update(&mut self, buffer: &[u8])
{
self.state.process(&buffer);
}
/// Generate the final hash. Because this is a variable-length hash,
/// you will need to provide the output size in bits. Note that this
/// output size *must* be a multiple of 8, and that the security
/// strength of the whole hash is approximately the minimum of this
/// length and 128 bits.
pub fn finalize(&mut self, outsize: usize) -> Vec<u8>
{
assert_eq!(outsize % 8, 0);
self.state.tag_and_pad(0x1F);
self.state.squeeze(outsize / 8)
}
/// Directly generate the SHAKE128 hash of the given buffer, returning
/// a hash value of the given size (in bits). Presently, the output
/// size *must* be a multiple of 8, although this may change in the
/// future.
pub fn hash(buffer: &[u8], outsize: usize) -> Vec<u8>
{
let mut x = Self::new();
x.update(&buffer);
x.finalize(outsize)
}
}
#[cfg(test)]
use testing::run_test;
#[cfg(test)]
use cryptonum::unsigned::{Decoder,U192};
#[cfg(test)]
#[test]
fn shake128() {
let fname = "testdata/sha/shake128.test";
run_test(fname.to_string(), 4, |case| {
let (negl, lbytes) = case.get("l").unwrap();
let (negm, mbytes) = case.get("m").unwrap();
let (negd, dbytes) = case.get("d").unwrap();
let (nego, obytes) = case.get("o").unwrap();
assert!(!negl && !negm && !negd && !nego);
let msg = if lbytes[0] == 0 { Vec::new() } else { mbytes.clone() };
let osize = usize::from(U192::from_bytes(obytes));
let digest = SHAKE128::hash(&msg, osize);;
assert_eq!(dbytes, &digest);
});
}
/// The SHAKE256 variable-length hash.
///
/// This generates a variable-length hash value, although it's not necessarily as
/// strong as a hash of the same value. My understanding (which is admittedly
/// limited; I've never seen these used) is that this is more for convenience
/// when you want to fit into particularly-sized regions. The 256 is the
/// approximate maximum bit strength of the hash in bits; the true strength is
/// the minimum of the length of the output hash and 256.
///
/// `SHAKE256` does not implement `Hash`, because it is finalized differently,
/// but we've kept something of the flavor of the `Hash` interface for
/// familiarity.
///
/// Like the SHA3 variants, this can be used incrementally or directly, as per
/// usual:
///
/// ```rust
/// use simple_crypto::shake::SHAKE256;
///
/// // Use SHAKE incrementally
/// let empty = [0; 0];
/// let mut shakef = SHAKE256::new();
/// shakef.update(&empty);
/// let result_inc = shakef.finalize(384);
/// // Use SHAKE directly
/// let result_dir = SHAKE256::hash(&empty, 384);
/// // ... and the answers should be the same.
/// assert_eq!(result_inc, result_dir);
/// ```
pub struct SHAKE256 {
state: Keccak
}
impl SHAKE256 {
/// Create a fresh, new SHAKE256 instance for incremental use.
pub fn new() -> Self
{
SHAKE256{
state: Keccak::new(1600 - 512)
}
}
/// Add more data into the hash function for processing.
pub fn update(&mut self, buffer: &[u8])
{
self.state.process(&buffer);
}
/// Generate the final hash. Because this is a variable-length hash,
/// you will need to provide the output size in bits. Note that this
/// output size *must* be a multiple of 8, and that the security
/// strength of the whole hash is approximately the minimum of this
/// length and 256 bits.
pub fn finalize(&mut self, outsize: usize) -> Vec<u8>
{
assert_eq!(outsize % 8, 0);
self.state.tag_and_pad(0x1F);
self.state.squeeze(outsize / 8)
}
/// Directly generate the SHAKE256 hash of the given buffer, returning
/// a hash value of the given size (in bits). Presently, the output
/// size *must* be a multiple of 8, although this may change in the
/// future.
pub fn hash(buffer: &[u8], outsize: usize) -> Vec<u8>
{
let mut x = Self::new();
x.update(&buffer);
x.finalize(outsize)
}
}
#[cfg(test)]
#[test]
fn shake256() {
let fname = "testdata/sha/shake256.test";
run_test(fname.to_string(), 4, |case| {
let (negl, lbytes) = case.get("l").unwrap();
let (negm, mbytes) = case.get("m").unwrap();
let (negd, dbytes) = case.get("d").unwrap();
let (nego, obytes) = case.get("o").unwrap();
assert!(!negl && !negm && !negd && !nego);
let msg = if lbytes[0] == 0 { Vec::new() } else { mbytes.clone() };
let osize = usize::from(U192::from_bytes(obytes));
let digest = SHAKE256::hash(&msg, osize);;
assert_eq!(dbytes, &digest);
});
}

6252
testdata/sha/shake128.test vendored Normal file

File diff suppressed because one or more lines are too long

6476
testdata/sha/shake256.test vendored Normal file

File diff suppressed because one or more lines are too long