Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
669 lines (572 sloc) 19.9 KB
// MIT License
// Copyright (c) 2018-2019 The orion Developers
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//! # Parameters:
//! - `data`: The data to be hashed.
//!
//! # Exceptions:
//! An exception will be thrown if:
//! - `finalize()` is called twice without a `reset()` in between.
//! - `update()` is called after `finalize()` without a `reset()` in between.
//!
//! # Security:
//! - SHA512 is vulnerable to length extension attacks.
//!
//! # Recommendation:
//! - It is recommended to use BLAKE2b when possible.
//!
//! # Example:
//! ```
//! use orion::hazardous::hash::sha512;
//!
//! // Using the streaming interface
//! let mut state = sha512::init();
//! state.update(b"Hello world").unwrap();
//! let hash = state.finalize().unwrap();
//!
//! // Using the one-shot function
//! let hash_one_shot = sha512::digest(b"Hello world").unwrap();
//!
//! assert_eq!(hash, hash_one_shot);
//! ```
use crate::{
endianness::{load_u64_into_be, store_u64_into_be},
errors::{FinalizationCryptoError, UnknownCryptoError},
hazardous::constants::SHA2_BLOCKSIZE,
};
construct_digest! {
/// A type to represent the `Digest` that SHA512 returns.
///
/// # Exceptions:
/// An exception will be thrown if:
/// - `slice` is empty.
/// - `slice` is greater than 64 bytes.
(Digest, 64)
}
#[rustfmt::skip]
#[allow(clippy::unreadable_literal)]
/// The SHA512 constants as defined in the FIPS 180-4.
const K: [u64; 80] = [
0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc,
0x3956c25bf348b538, 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118,
0xd807aa98a3030242, 0x12835b0145706fbe, 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2,
0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, 0xc19bf174cf692694,
0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65,
0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5,
0x983e5152ee66dfab, 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4,
0xc6e00bf33da88fc2, 0xd5a79147930aa725, 0x06ca6351e003826f, 0x142929670a0e6e70,
0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, 0x53380d139d95b3df,
0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b,
0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30,
0xd192e819d6ef5218, 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8,
0x19a4c116b8d2d0c8, 0x1e376c085141ab53, 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8,
0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, 0x682e6ff3d6b2b8a3,
0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec,
0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b,
0xca273eceea26619c, 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178,
0x06f067aa72176fba, 0x0a637dc5a2c898a6, 0x113f9804bef90dae, 0x1b710b35131c471b,
0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, 0x431d67c49c100d4c,
0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817,
];
#[rustfmt::skip]
#[allow(clippy::unreadable_literal)]
/// The SHA512 initial hash value H(0) as defined in the FIPS 180-4.
const H0: [u64; 8] = [
0x6a09e667f3bcc908, 0xbb67ae8584caa73b, 0x3c6ef372fe94f82b, 0xa54ff53a5f1d36f1,
0x510e527fade682d1, 0x9b05688c2b3e6c1f, 0x1f83d9abfb41bd6b, 0x5be0cd19137e2179,
];
#[derive(Clone)]
/// SHA512 as specified in the [FIPS PUB 180-4](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf).
pub struct Sha512 {
working_state: [u64; 8],
buffer: [u8; SHA2_BLOCKSIZE],
leftover: usize,
message_len: [u64; 2],
is_finalized: bool,
}
impl Drop for Sha512 {
fn drop(&mut self) {
use clear_on_drop::clear::Clear;
self.working_state.clear();
self.buffer.clear();
self.message_len.clear();
}
}
impl core::fmt::Debug for Sha512 {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(
f,
"Sha512 {{ working_state: [***OMITTED***], buffer: [***OMITTED***], leftover: {:?}, \
message_len: {:?}, is_finalized: {:?} }}",
self.leftover, self.message_len, self.is_finalized
)
}
}
impl Sha512 {
#[inline]
/// The Ch function as specified in FIPS 180-4 section 4.1.3.
fn ch(&self, x: u64, y: u64, z: u64) -> u64 { z ^ (x & (y ^ z)) }
#[inline]
/// The Maj function as specified in FIPS 180-4 section 4.1.3.
fn maj(&self, x: u64, y: u64, z: u64) -> u64 { (x & y) | (z & (x | y)) }
#[inline]
/// The Big Sigma 0 function as specified in FIPS 180-4 section 4.1.3.
fn big_sigma_0(&self, x: u64) -> u64 {
(x.rotate_right(28)) ^ x.rotate_right(34) ^ x.rotate_right(39)
}
#[inline]
/// The Big Sigma 1 function as specified in FIPS 180-4 section 4.1.3.
fn big_sigma_1(&self, x: u64) -> u64 {
(x.rotate_right(14)) ^ x.rotate_right(18) ^ x.rotate_right(41)
}
#[inline]
/// The Small Sigma 0 function as specified in FIPS 180-4 section 4.1.3.
fn small_sigma_0(&self, x: u64) -> u64 { (x.rotate_right(1)) ^ x.rotate_right(8) ^ (x >> 7) }
#[inline]
/// The Small Sigma 1 function as specified in FIPS 180-4 section 4.1.3.
fn small_sigma_1(&self, x: u64) -> u64 { (x.rotate_right(19)) ^ x.rotate_right(61) ^ (x >> 6) }
#[inline]
#[allow(clippy::many_single_char_names)]
#[allow(clippy::too_many_arguments)]
/// Message compression adopted from [mbed TLS](https://tls.mbed.org/sha-512-source-code).
fn compress(
&self,
a: u64,
b: u64,
c: u64,
d: &mut u64,
e: u64,
f: u64,
g: u64,
h: &mut u64,
x: u64,
ki: u64,
) {
let temp1 = h
.wrapping_add(self.big_sigma_1(e))
.wrapping_add(self.ch(e, f, g))
.wrapping_add(ki)
.wrapping_add(x);
let temp2 = self.big_sigma_0(a).wrapping_add(self.maj(a, b, c));
*d = d.wrapping_add(temp1);
*h = temp1.wrapping_add(temp2);
}
#[inline]
#[rustfmt::skip]
#[allow(clippy::many_single_char_names)]
/// Process data in `self.buffer`.
fn process(&mut self) {
let mut w = [0u64; 80];
load_u64_into_be(&self.buffer, &mut w[..16]);
for t in 16..80 {
w[t] = self
.small_sigma_1(w[t - 2])
.wrapping_add(w[t - 7])
.wrapping_add(self.small_sigma_0(w[t - 15]))
.wrapping_add(w[t - 16]);
}
// Initialize working variables
let mut a = self.working_state[0];
let mut b = self.working_state[1];
let mut c = self.working_state[2];
let mut d = self.working_state[3];
let mut e = self.working_state[4];
let mut f = self.working_state[5];
let mut g = self.working_state[6];
let mut h = self.working_state[7];
let mut t = 0;
while t < 80 {
self.compress(a, b, c, &mut d, e, f, g, &mut h, w[t], K[t]); t += 1;
self.compress(h, a, b, &mut c, d, e, f, &mut g, w[t], K[t]); t += 1;
self.compress(g, h, a, &mut b, c, d, e, &mut f, w[t], K[t]); t += 1;
self.compress(f, g, h, &mut a, b, c, d, &mut e, w[t], K[t]); t += 1;
self.compress(e, f, g, &mut h, a, b, c, &mut d, w[t], K[t]); t += 1;
self.compress(d, e, f, &mut g, h, a, b, &mut c, w[t], K[t]); t += 1;
self.compress(c, d, e, &mut f, g, h, a, &mut b, w[t], K[t]); t += 1;
self.compress(b, c, d, &mut e, f, g, h, &mut a, w[t], K[t]); t += 1;
}
self.working_state[0] = self.working_state[0].wrapping_add(a);
self.working_state[1] = self.working_state[1].wrapping_add(b);
self.working_state[2] = self.working_state[2].wrapping_add(c);
self.working_state[3] = self.working_state[3].wrapping_add(d);
self.working_state[4] = self.working_state[4].wrapping_add(e);
self.working_state[5] = self.working_state[5].wrapping_add(f);
self.working_state[6] = self.working_state[6].wrapping_add(g);
self.working_state[7] = self.working_state[7].wrapping_add(h);
}
/// Reset to `init()` state.
pub fn reset(&mut self) {
self.working_state = H0;
self.buffer = [0u8; SHA2_BLOCKSIZE];
self.leftover = 0;
self.message_len = [0u64; 2];
self.is_finalized = false;
}
#[inline]
/// Increment the message length during processing of data.
fn increment_mlen(&mut self, length: u64) {
// left-shift to get bit-sized representation of length
// using .unwrap() because it should not panic in practice
let len = length.checked_shl(3).unwrap();
let (res, was_overflow) = self.message_len[1].overflowing_add(len);
self.message_len[1] = res;
if was_overflow {
// If this panics size limit is reached.
self.message_len[0] = self.message_len[0].checked_add(1).unwrap();
}
}
#[must_use]
/// Update state with `data`. This can be called multiple times.
pub fn update(&mut self, data: &[u8]) -> Result<(), FinalizationCryptoError> {
if self.is_finalized {
return Err(FinalizationCryptoError);
}
if data.is_empty() {
return Ok(());
}
let mut bytes = data;
// First fill up if there is leftover space
if self.leftover > 0 {
// Using .unwrap() since overflow should not happen in practice
let fill = SHA2_BLOCKSIZE.checked_sub(self.leftover).unwrap();
if bytes.len() < fill {
self.buffer[self.leftover..(self.leftover + bytes.len())].copy_from_slice(&bytes);
// Using .unwrap() since overflow should not happen in practice
self.leftover = self.leftover.checked_add(bytes.len()).unwrap();
self.increment_mlen(bytes.len() as u64);
return Ok(());
}
self.buffer[self.leftover..(self.leftover + fill)].copy_from_slice(&bytes[..fill]);
// Process data
self.process();
self.increment_mlen(fill as u64);
self.leftover = 0;
// Reduce by slice
bytes = &bytes[fill..];
}
while bytes.len() >= SHA2_BLOCKSIZE {
// Process data
self.buffer.copy_from_slice(&bytes[..SHA2_BLOCKSIZE]);
self.process();
self.increment_mlen(SHA2_BLOCKSIZE as u64);
// Reduce by slice
bytes = &bytes[SHA2_BLOCKSIZE..];
}
if !bytes.is_empty() {
self.buffer[self.leftover..(self.leftover + bytes.len())].copy_from_slice(&bytes);
// Using .unwrap() since overflow should not happen in practice
self.leftover = self.leftover.checked_add(bytes.len()).unwrap();
self.increment_mlen(bytes.len() as u64);
}
Ok(())
}
#[must_use]
/// Return a SHA512 digest.
pub fn finalize(&mut self) -> Result<Digest, FinalizationCryptoError> {
if self.is_finalized {
return Err(FinalizationCryptoError);
}
self.is_finalized = true;
// self.leftover should not be greater than SHA2_BLCOKSIZE
// as that would have been processed in the update call
assert!(self.leftover < SHA2_BLOCKSIZE);
self.buffer[self.leftover] = 0x80;
// Using .unwrap() since overflow should not happen in practice
self.leftover = self.leftover.checked_add(1).unwrap();
for itm in self.buffer.iter_mut().skip(self.leftover) {
*itm = 0;
}
// Check for available space for length padding
if (SHA2_BLOCKSIZE - self.leftover) < 16 {
self.process();
for itm in self.buffer.iter_mut().take(self.leftover) {
*itm = 0;
}
}
// Pad with length
self.buffer[SHA2_BLOCKSIZE - 16..SHA2_BLOCKSIZE - 8]
.copy_from_slice(&self.message_len[0].to_be_bytes());
self.buffer[SHA2_BLOCKSIZE - 8..SHA2_BLOCKSIZE]
.copy_from_slice(&self.message_len[1].to_be_bytes());
self.process();
let mut digest = [0u8; 64];
store_u64_into_be(&self.working_state, &mut digest);
Ok(Digest::from_slice(&digest)?)
}
}
#[must_use]
/// Initialize a `Sha512` struct.
pub fn init() -> Sha512 {
Sha512 {
working_state: H0,
buffer: [0u8; SHA2_BLOCKSIZE],
leftover: 0,
message_len: [0u64; 2],
is_finalized: false,
}
}
#[must_use]
/// Calculate a SHA512 digest of some `data`.
pub fn digest(data: &[u8]) -> Result<Digest, UnknownCryptoError> {
let mut state = init();
state.update(data)?;
Ok(state.finalize()?)
}
#[cfg(test)]
/// Compare two Sha512 state objects to check if their fields
/// are the same.
pub fn compare_sha512_states(state_1: &Sha512, state_2: &Sha512) {
assert_eq!(state_1.working_state, state_2.working_state);
assert_eq!(state_1.buffer[..], state_2.buffer[..]);
assert_eq!(state_1.leftover, state_2.leftover);
assert_eq!(state_1.message_len, state_2.message_len);
assert_eq!(state_1.is_finalized, state_2.is_finalized);
}
// Testing public functions in the module.
#[cfg(test)]
mod public {
use super::*;
// One function tested per submodule.
mod test_reset {
use super::*;
#[test]
fn test_double_reset_ok() {
let data = "what do ya want for nothing?".as_bytes();
let mut state = init();
state.update(data).unwrap();
let _ = state.finalize().unwrap();
state.reset();
state.reset();
}
}
mod test_update {
use super::*;
#[test]
fn test_update_after_finalize_with_reset_ok() {
let data = "what do ya want for nothing?".as_bytes();
let mut state = init();
state.update(data).unwrap();
let _ = state.finalize().unwrap();
state.reset();
state.update(data).unwrap();
}
#[test]
/// Related bug: https://github.com/brycx/orion/issues/28
fn test_update_after_finalize_err() {
let data = "what do ya want for nothing?".as_bytes();
let mut state = init();
state.update(data).unwrap();
let _ = state.finalize().unwrap();
assert!(state.update(data).is_err());
}
}
mod test_finalize {
use super::*;
#[test]
fn test_double_finalize_with_reset_no_update_ok() {
let data = "what do ya want for nothing?".as_bytes();
let mut state = init();
state.update(data).unwrap();
let _ = state.finalize().unwrap();
state.reset();
let _ = state.finalize().unwrap();
}
#[test]
fn test_double_finalize_with_reset_ok() {
let data = "what do ya want for nothing?".as_bytes();
let mut state = init();
state.update(data).unwrap();
let one = state.finalize().unwrap();
state.reset();
state.update(data).unwrap();
let two = state.finalize().unwrap();
assert_eq!(one.as_bytes(), two.as_bytes());
}
#[test]
fn test_double_finalize_err() {
let data = "what do ya want for nothing?".as_bytes();
let mut state = init();
state.update(data).unwrap();
let _ = state.finalize().unwrap();
assert!(state.finalize().is_err());
}
}
mod test_streaming_interface {
use super::*;
/// Related bug: https://github.com/brycx/orion/issues/46
/// Testing different usage combinations of init(), update(),
/// finalize() and reset() produce the same Digest.
fn produces_same_hash(data: &[u8]) {
// init(), update(), finalize()
let mut state_1 = init();
state_1.update(data).unwrap();
let res_1 = state_1.finalize().unwrap();
// init(), reset(), update(), finalize()
let mut state_2 = init();
state_2.reset();
state_2.update(data).unwrap();
let res_2 = state_2.finalize().unwrap();
// init(), update(), reset(), update(), finalize()
let mut state_3 = init();
state_3.update(data).unwrap();
state_3.reset();
state_3.update(data).unwrap();
let res_3 = state_3.finalize().unwrap();
// init(), update(), finalize(), reset(), update(), finalize()
let mut state_4 = init();
state_4.update(data).unwrap();
let _ = state_4.finalize().unwrap();
state_4.reset();
state_4.update(data).unwrap();
let res_4 = state_4.finalize().unwrap();
assert_eq!(res_1, res_2);
assert_eq!(res_2, res_3);
assert_eq!(res_3, res_4);
}
/// Related bug: https://github.com/brycx/orion/issues/46
/// Testing different usage combinations of init(), update(),
/// finalize() and reset() produce the same Digest.
fn produces_same_state(data: &[u8]) {
// init()
let state_1 = init();
// init(), reset()
let mut state_2 = init();
state_2.reset();
// init(), update(), reset()
let mut state_3 = init();
state_3.update(data).unwrap();
state_3.reset();
// init(), update(), finalize(), reset()
let mut state_4 = init();
state_4.update(data).unwrap();
let _ = state_4.finalize().unwrap();
state_4.reset();
compare_sha512_states(&state_1, &state_2);
compare_sha512_states(&state_2, &state_3);
compare_sha512_states(&state_3, &state_4);
}
#[test]
/// Related bug: https://github.com/brycx/orion/issues/46
fn test_produce_same_state() { produces_same_state(b"Tests"); }
#[test]
/// Related bug: https://github.com/brycx/orion/issues/46
fn test_produce_same_hash() { produces_same_hash(b"Tests"); }
#[test]
#[cfg(feature = "safe_api")]
// Test for issues when incrementally processing data
// with leftover
fn test_streaming_consistency() {
for len in 0..SHA2_BLOCKSIZE * 4 {
let data = vec![0u8; len];
let mut state = init();
let mut other_data: Vec<u8> = Vec::new();
other_data.extend_from_slice(&data);
state.update(&data).unwrap();
if data.len() > SHA2_BLOCKSIZE {
other_data.extend_from_slice(b"");
state.update(b"").unwrap();
}
if data.len() > SHA2_BLOCKSIZE * 2 {
other_data.extend_from_slice(b"Extra");
state.update(b"Extra").unwrap();
}
if data.len() > SHA2_BLOCKSIZE * 3 {
other_data.extend_from_slice(&[0u8; 256]);
state.update(&[0u8; 256]).unwrap();
}
let digest_one_shot = digest(&other_data).unwrap();
assert!(state.finalize().unwrap().as_bytes() == digest_one_shot.as_bytes());
}
}
// Proptests. Only exectued when NOT testing no_std.
#[cfg(feature = "safe_api")]
mod proptest {
use super::*;
quickcheck! {
/// Related bug: https://github.com/brycx/orion/issues/46
/// Test different streaming state usage patterns.
fn prop_same_hash_different_usage(data: Vec<u8>) -> bool {
// Will panic on incorrect results.
produces_same_hash(&data[..]);
true
}
}
quickcheck! {
/// Related bug: https://github.com/brycx/orion/issues/46
/// Test different streaming state usage patterns.
fn prop_same_state_different_usage(data: Vec<u8>) -> bool {
// Will panic on incorrect results.
produces_same_state(&data[..]);
true
}
}
quickcheck! {
/// Using the one-shot function should always produce the
/// same result as when using the streaming interface.
fn prop_digest_same_as_streaming(data: Vec<u8>) -> bool {
let mut state = init();
state.update(&data[..]).unwrap();
let stream = state.finalize().unwrap();
let one_shot = digest(&data[..]).unwrap();
(one_shot == stream)
}
}
}
}
}
// Testing private functions in the module.
#[cfg(test)]
mod private {
use super::*;
// One function tested per submodule.
mod test_increment_mlen {
use super::*;
#[test]
fn test_mlen_increase_values() {
let mut context = Sha512 {
working_state: H0,
buffer: [0u8; SHA2_BLOCKSIZE],
leftover: 0,
message_len: [0u64; 2],
is_finalized: false,
};
context.increment_mlen(1);
assert!(context.message_len == [0u64, 8u64]);
context.increment_mlen(17);
assert!(context.message_len == [0u64, 144u64]);
context.increment_mlen(12);
assert!(context.message_len == [0u64, 240u64]);
// Overflow
context.increment_mlen(u64::max_value());
assert!(context.message_len == [1u64, 232u64]);
}
#[test]
#[should_panic]
fn test_panic_on_second_overflow() {
let mut context = Sha512 {
working_state: H0,
buffer: [0u8; SHA2_BLOCKSIZE],
leftover: 0,
message_len: [u64::max_value(), u64::max_value() - 7],
is_finalized: false,
};
// u64::max_value() - 7, to leave so that the length represented
// in bites should overflow by exactly one.
context.increment_mlen(1);
}
}
}