Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add finish_into to write the hash to a user provided buffer. #22

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/imp/commoncrypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,19 @@ impl Hasher {
Err(error) => panic!("CommonCrypto error: {}", error),
}
}

/// Generate a digest from the data written to the `Hasher`. `dest` must be
/// exactly the length of the digest.
pub fn finish_into(&mut self, dest: &mut [u8]) {
let Hasher(ref mut hasher) = *self;
match hasher.finish() {
Ok(digest) => {
assert_eq!(dest.len(), digest.len());
dest.copy_from_slice(&digest);
}
Err(error) => panic!("CommonCrypto error: {}", error),
}
}
}

impl io::Write for Hasher {
Expand Down
30 changes: 30 additions & 0 deletions src/imp/cryptoapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,19 @@ macro_rules! finish_algorithm {
}
}

macro_rules! finish_algorithm_into {
($func_name: ident, $size: ident) => {
fn $func_name(&mut self, dest: &mut [u8]) {
assert_eq!(dest.len(), $size);
let mut len = $size as u32;
call!(unsafe {
CryptGetHashParam(self.hcrypthash, HP_HASHVAL, dest.as_mut_ptr(), &mut len, 0)
});
assert_eq!(len as usize, dest.len());
}
}
}

const MD5_LENGTH: usize = 16;
const SHA1_LENGTH: usize = 20;
const SHA256_LENGTH: usize = 32;
Expand Down Expand Up @@ -135,10 +148,27 @@ impl Hasher {
}
}

/// Generate a digest from the data written to the `Hasher`. `dest` must be
/// exactly the length of the digest.
pub fn finish_into(&mut self, dest: &mut [u8]) {
match self.alg_id {
CALG_MD5 => self.finish_md5_into(dest),
CALG_SHA1 => self.finish_sha1_into(dest),
CALG_SHA_256 => self.finish_sha256_into(dest),
CALG_SHA_512 => self.finish_sha512_into(dest),
_ => panic!("Unknown algorithm {}", self.alg_id),
}
}

finish_algorithm!(finish_md5, MD5_LENGTH);
finish_algorithm!(finish_sha1, SHA1_LENGTH);
finish_algorithm!(finish_sha256, SHA256_LENGTH);
finish_algorithm!(finish_sha512, SHA512_LENGTH);

finish_algorithm_into!(finish_md5, MD5_LENGTH);
finish_algorithm_into!(finish_sha1, SHA1_LENGTH);
finish_algorithm_into!(finish_sha256, SHA256_LENGTH);
finish_algorithm_into!(finish_sha512, SHA512_LENGTH);
}

impl io::Write for Hasher {
Expand Down
13 changes: 13 additions & 0 deletions src/imp/openssl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,19 @@ impl Hasher {
Err(error_stack) => panic!("OpenSSL error(s): {}", error_stack),
}
}

/// Generate a digest from the data written to the `Hasher`. `dest` must be
/// exactly the length of the digest.
pub fn finish_into(&mut self, dest: &mut [u8]) {
let Hasher(ref mut hasher) = *self;
match hasher.finish() {
Ok(digest) => {
assert_eq!(dest.len(), digest.len());
dest.copy_from_slice(&digest)
}
Err(error_stack) => panic!("OpenSSL error(s): {}", error_stack),
}
}
}

impl io::Write for Hasher {
Expand Down
30 changes: 29 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,38 @@ pub enum Algorithm {
/// ```
pub fn digest(algorithm: Algorithm, data: &[u8]) -> Vec<u8> {
let mut hasher = imp::Hasher::new(algorithm);
hasher.write_all(data).expect("Could not write hash data");
hasher
.write_all(data)
.and_then(|_| hasher.flush())
.expect("Could not write hash data");
hasher.finish()
}

/// Helper function for `Hasher` which generates a cryptographic digest from the given
/// data and algorithm and writes it to `dest`, which must be exactly the
/// correct length for the digest.
///
/// # Examples
///
/// ```rust
/// use crypto_hash::{Algorithm, digest_into};
///
/// let data = b"crypto-hash";
/// let mut dest = [0; 20];
/// digest_into(Algorithm::SHA1, data, &mut dest);
/// let expected =
/// b"\x57\xba\xb6\xe4\x94\x8e\x3a\x06\x09\x3b\x5e\x46\x69\x2b\xa4\x80\xa1\x31\x0c\xfb";
/// assert_eq!(*expected, dest)
/// ```
pub fn digest_into(algorithm: Algorithm, data: &[u8], dest: &mut [u8]) {
let mut hasher = imp::Hasher::new(algorithm);
hasher
.write_all(data)
.and_then(|_| hasher.flush())
.expect("Could not write hash data");
hasher.finish_into(dest);
}

/// Helper function for `Hasher` which generates a cryptographic digest serialized in
/// hexadecimal from the given data and algorithm.
///
Expand Down
35 changes: 35 additions & 0 deletions src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const SHA512_EMPTY_STRING: &'static str = concat!(
);
const TO_HASH: &'static str = "The quick brown fox jumps over the lazy dog";
const TO_HASH_MD5: &'static str = "9e107d9d372bb6826bd81d3542a419d6";
const TO_HASH_SHA1: &'static str = "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12";

#[test]
fn md5_empty_string() {
Expand Down Expand Up @@ -72,11 +73,45 @@ fn hasher_with_write() {
let mut hasher = Hasher::new(Algorithm::MD5);
hasher
.write_all(TO_HASH.as_bytes())
.and_then(|_| hasher.flush())
.expect("Could not write to hasher");
let actual = hex::encode(hasher.finish());
assert_eq!(TO_HASH_MD5, actual)
}

#[test]
fn hasher_finalize_into() {
let mut hasher = Hasher::new(Algorithm::SHA1);
hasher
.write_all(TO_HASH.as_bytes())
.and_then(|_| hasher.flush())
.expect("Could not write to hasher");
let mut actual = [0; 20];
hasher.finish_into(&mut actual);
assert_eq!(*TO_HASH_SHA1, hex::encode(actual));
}

// This test is important, especially on Windows, where we pass this buffer
// directly into the unsafe C code to be written to, without an explicit length
// (the CryptoAPI assumes the buffer is the correct length).
//
// We opt not to handle this logic error dynamically, and instead replicate the
// same panic that safe code exhibits when indexing out of bounds.
#[test]
#[should_panic(expected = "assertion failed")]
fn hasher_finalize_into_short_buffer_no_overrun() {
let mut hasher = Hasher::new(Algorithm::SHA1);
hasher
.write_all(TO_HASH.as_bytes())
.and_then(|_| hasher.flush())
.expect("Could not write to hasher");
// A SHA1 digest is 20 bytes.
let mut actual = [0; 19];

// Should panic rather than overwrite the buffer in the unsafe impl code.
hasher.finish_into(&mut actual);
}

fn assert_hex_hashed_empty_string(algorithm: Algorithm, expected: &str) {
let vec = vec![];
assert_eq!(expected, hex_digest(algorithm, vec.as_slice()).as_str())
Expand Down