Files
rustcrypto-hashes/sha1-checked/src/lib.rs

375 lines
9.9 KiB
Rust

#![no_std]
#![doc = include_str!("../README.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg"
)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![warn(missing_docs, rust_2018_idioms)]
//! Collision checked Sha1.
//!
//! General techniques and implementation are based on the research and implementation done in [1], [2] by
//! Marc Stevens.
//!
//!
//! Original license can be found in [3].
//!
//! [1]: https://github.com/cr-marcstevens/sha1collisiondetection
//! [2]: https://marc-stevens.nl/research/papers/C13-S.pdf
//! [3]: https://github.com/cr-marcstevens/sha1collisiondetection/blob/master/LICENSE.txt
pub use digest::{self, Digest};
use core::slice::from_ref;
#[cfg(feature = "std")]
extern crate std;
use digest::{
FixedOutput, FixedOutputReset, HashMarker, Output, OutputSizeUser, Reset, Update,
array::Array,
block_buffer::{BlockBuffer, Eager},
core_api::BlockSizeUser,
typenum::{U20, U64, Unsigned},
};
#[cfg(feature = "zeroize")]
use zeroize::{Zeroize, ZeroizeOnDrop};
const BLOCK_SIZE: usize = <sha1::Sha1Core as BlockSizeUser>::BlockSize::USIZE;
const STATE_LEN: usize = 5;
const INITIAL_H: [u32; 5] = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0];
mod compress;
mod ubc_check;
/// SHA-1 collision detection hasher state.
#[derive(Clone)]
pub struct Sha1 {
h: [u32; STATE_LEN],
block_len: u64,
detection: Option<DetectionState>,
buffer: BlockBuffer<U64, Eager>,
}
impl HashMarker for Sha1 {}
impl Default for Sha1 {
fn default() -> Self {
Builder::default().build()
}
}
impl Sha1 {
/// Create a new Sha1 instance, with collision detection enabled.
pub fn new() -> Self {
Self::default()
}
/// Create a new Sha1 builder to configure detection.
pub fn builder() -> Builder {
Builder::default()
}
/// Oneshot hashing, reporting the collision state.
///
/// # Examples
///
/// ```
/// use hex_literal::hex;
/// use sha1_checked::Sha1;
///
/// let result = Sha1::try_digest(b"hello world");
/// assert_eq!(result.hash().as_ref(), hex!("2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"));
/// assert!(!result.has_collision());
/// ```
pub fn try_digest(data: impl AsRef<[u8]>) -> CollisionResult {
let mut hasher = Self::default();
Digest::update(&mut hasher, data);
hasher.try_finalize()
}
/// Try finalization, reporting the collision state.
pub fn try_finalize(mut self) -> CollisionResult {
let mut out = Output::<Self>::default();
self.finalize_inner(&mut out);
if let Some(ref ctx) = self.detection {
if ctx.found_collision {
if ctx.safe_hash {
return CollisionResult::Mitigated(out);
}
return CollisionResult::Collision(out);
}
}
CollisionResult::Ok(out)
}
fn finalize_inner(&mut self, out: &mut Output<Self>) {
let bs = 64;
let buffer = &mut self.buffer;
let h = &mut self.h;
if let Some(ref mut ctx) = self.detection {
let last_block = buffer.get_data();
compress::finalize(h, bs * self.block_len, last_block, ctx);
} else {
let bit_len = 8 * (buffer.get_pos() as u64 + bs * self.block_len);
buffer.len64_padding_be(bit_len, |b| sha1::compress(h, from_ref(b.into())));
}
for (chunk, v) in out.chunks_exact_mut(4).zip(h.iter()) {
chunk.copy_from_slice(&v.to_be_bytes());
}
}
}
/// Result when trying to finalize a hash.
#[derive(Debug)]
pub enum CollisionResult {
/// No collision.
Ok(Output<Sha1>),
/// Collision occurred, but was mititgated.
Mitigated(Output<Sha1>),
/// Collision occurred, the hash is the one that collided.
Collision(Output<Sha1>),
}
impl CollisionResult {
/// Returns the output hash.
pub fn hash(&self) -> &Output<Sha1> {
match self {
CollisionResult::Ok(s) => s,
CollisionResult::Mitigated(s) => s,
CollisionResult::Collision(s) => s,
}
}
/// Returns if there was a collision
pub fn has_collision(&self) -> bool {
!matches!(self, CollisionResult::Ok(_))
}
}
impl core::fmt::Debug for Sha1 {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
f.write_str("Sha1CollisionDetection { .. }")
}
}
impl Reset for Sha1 {
#[inline]
fn reset(&mut self) {
self.h = INITIAL_H;
self.block_len = 0;
self.buffer.reset();
if let Some(ref mut ctx) = self.detection {
ctx.reset();
}
}
}
impl Update for Sha1 {
#[inline]
fn update(&mut self, input: &[u8]) {
let Self {
h,
detection,
buffer,
..
} = self;
buffer.digest_blocks(input, |blocks| {
self.block_len += blocks.len() as u64;
if let Some(ctx) = detection {
// SAFETY: GenericArray<u8, U64> and [u8; 64] have
// exactly the same memory layout
let blocks: &[[u8; BLOCK_SIZE]] =
unsafe { &*(blocks as *const _ as *const [[u8; BLOCK_SIZE]]) };
compress::compress(h, ctx, blocks);
} else {
let blocks = Array::cast_slice_to_core(blocks);
sha1::compress(h, blocks);
}
});
}
}
impl OutputSizeUser for Sha1 {
type OutputSize = U20;
}
impl BlockSizeUser for Sha1 {
type BlockSize = U64;
}
impl FixedOutput for Sha1 {
#[inline]
fn finalize_into(mut self, out: &mut Output<Self>) {
self.finalize_inner(out);
}
}
impl FixedOutputReset for Sha1 {
#[inline]
fn finalize_into_reset(&mut self, out: &mut Output<Self>) {
self.finalize_inner(out);
Reset::reset(self);
}
}
#[cfg(feature = "zeroize")]
impl ZeroizeOnDrop for Sha1 {}
impl Drop for DetectionState {
#[inline]
fn drop(&mut self) {
#[cfg(feature = "zeroize")]
{
self.ihv1.zeroize();
self.ihv2.zeroize();
self.m1.zeroize();
self.m2.zeroize();
self.state_58.zeroize();
self.state_65.zeroize();
}
}
}
#[cfg(feature = "zeroize")]
impl ZeroizeOnDrop for DetectionState {}
#[cfg(feature = "oid")]
impl digest::const_oid::AssociatedOid for Sha1 {
const OID: digest::const_oid::ObjectIdentifier = sha1::Sha1Core::OID;
}
#[cfg(feature = "std")]
impl std::io::Write for Sha1 {
#[inline]
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
Update::update(self, buf);
Ok(buf.len())
}
#[inline]
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
/// Builder for collision detection configuration.
#[derive(Clone)]
pub struct Builder {
detect_collision: bool,
safe_hash: bool,
ubc_check: bool,
reduced_round_collision: bool,
}
impl Default for Builder {
fn default() -> Self {
Self {
detect_collision: true,
safe_hash: true,
ubc_check: true,
reduced_round_collision: false,
}
}
}
impl Builder {
/// Should we detect collisions at all? Default: true
pub fn detect_collision(mut self, detect: bool) -> Self {
self.detect_collision = detect;
self
}
/// Should a fix be automatically be applied, or the original hash be returned? Default: true
pub fn safe_hash(mut self, safe_hash: bool) -> Self {
self.safe_hash = safe_hash;
self
}
/// Should unavoidable bitconditions be used to speed up the check? Default: true
pub fn use_ubc(mut self, ubc: bool) -> Self {
self.ubc_check = ubc;
self
}
/// Should reduced round collisions be used? Default: false
pub fn reduced_round_collision(mut self, reduced: bool) -> Self {
self.reduced_round_collision = reduced;
self
}
fn into_detection_state(self) -> Option<DetectionState> {
if self.detect_collision {
Some(DetectionState {
safe_hash: self.safe_hash,
reduced_round_collision: self.reduced_round_collision,
ubc_check: self.ubc_check,
found_collision: false,
ihv1: Default::default(),
ihv2: Default::default(),
m1: [0; 80],
m2: [0; 80],
state_58: Default::default(),
state_65: Default::default(),
})
} else {
None
}
}
/// Create a Sha1 with a specific collision detection configuration.
pub fn build(self) -> Sha1 {
let detection = self.into_detection_state();
Sha1 {
h: INITIAL_H,
block_len: 0,
detection,
buffer: Default::default(),
}
}
}
/// The internal state used to do collision detection.
#[derive(Clone, Debug)]
struct DetectionState {
safe_hash: bool,
ubc_check: bool,
reduced_round_collision: bool,
/// Has a collision been detected?
found_collision: bool,
ihv1: [u32; 5],
ihv2: [u32; 5],
m1: [u32; 80],
m2: [u32; 80],
/// Stores past states, for faster recompression.
state_58: [u32; 5],
state_65: [u32; 5],
}
impl Default for DetectionState {
fn default() -> Self {
Builder::default()
.into_detection_state()
.expect("enabled by default")
}
}
impl DetectionState {
fn reset(&mut self) {
// Do not reset the config, it needs to be preserved
self.found_collision = false;
self.ihv1 = Default::default();
self.ihv2 = Default::default();
self.m1 = [0; 80];
self.m2 = [0; 80];
self.state_58 = Default::default();
self.state_65 = Default::default();
}
}