#![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)]
pub use digest::{self, Digest};
use core::slice::from_ref;
#[cfg(feature = "std")]
extern crate std;
use digest::{
block_buffer::{BlockBuffer, Eager},
core_api::BlockSizeUser,
typenum::{Unsigned, U20, U64},
FixedOutput, FixedOutputReset, HashMarker, Output, OutputSizeUser, Reset, Update,
};
#[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;
#[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 {
pub fn new() -> Self {
Self::default()
}
pub fn builder() -> Builder {
Builder::default()
}
pub fn try_digest(data: impl AsRef<[u8]>) -> CollisionResult {
let mut hasher = Self::default();
Digest::update(&mut hasher, data);
hasher.try_finalize()
}
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)));
}
for (chunk, v) in out.chunks_exact_mut(4).zip(h.iter()) {
chunk.copy_from_slice(&v.to_be_bytes());
}
}
}
#[derive(Debug)]
pub enum CollisionResult {
Ok(Output<Sha1>),
Mitigated(Output<Sha1>),
Collision(Output<Sha1>),
}
impl CollisionResult {
pub fn hash(&self) -> &Output<Sha1> {
match self {
CollisionResult::Ok(ref s) => s,
CollisionResult::Mitigated(ref s) => s,
CollisionResult::Collision(ref s) => s,
}
}
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(ref mut ctx) = detection {
let blocks: &[[u8; BLOCK_SIZE]] =
unsafe { &*(blocks as *const _ as *const [[u8; BLOCK_SIZE]]) };
compress::compress(h, ctx, blocks);
} else {
sha1::compress(h, blocks);
}
});
}
}
impl OutputSizeUser for Sha1 {
type OutputSize = U20;
}
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")]
#[cfg_attr(docsrs, doc(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(())
}
}
#[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 {
pub fn detect_collision(mut self, detect: bool) -> Self {
self.detect_collision = detect;
self
}
pub fn safe_hash(mut self, safe_hash: bool) -> Self {
self.safe_hash = safe_hash;
self
}
pub fn use_ubc(mut self, ubc: bool) -> Self {
self.ubc_check = ubc;
self
}
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
}
}
pub fn build(self) -> Sha1 {
let detection = self.into_detection_state();
Sha1 {
h: INITIAL_H,
block_len: 0,
detection,
buffer: Default::default(),
}
}
}
#[derive(Clone, Debug)]
struct DetectionState {
safe_hash: bool,
ubc_check: bool,
reduced_round_collision: bool,
found_collision: bool,
ihv1: [u32; 5],
ihv2: [u32; 5],
m1: [u32; 80],
m2: [u32; 80],
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) {
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();
}
}