#![no_std]
#![warn(missing_docs, unreachable_pub)]
#[cfg(feature = "std")]
extern crate std;
use core::fmt;
pub mod mitigate;
mod block;
mod mem;
mod ubc_check;
use block::Backend;
const DIGEST_SIZE: usize = 20;
const BLOCK_SIZE: usize = 64;
const STATE_LEN: usize = 5;
const INITIAL_H: [u32; STATE_LEN] = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0];
pub(crate) const SCHEDULE_LEN: usize = 80;
#[derive(Clone)]
#[repr(transparent)]
pub(crate) struct Schedule([u32; SCHEDULE_LEN]);
impl Schedule {
pub(crate) const fn zeroed() -> Self {
Self([0; SCHEDULE_LEN])
}
#[cfg(all(test, feature = "std"))]
pub(crate) const fn from_words(words: [u32; SCHEDULE_LEN]) -> Self {
Self(words)
}
#[cfg(all(test, feature = "std"))]
pub(crate) fn expand(m: &[u32; 16]) -> Self {
let mut w = Self::zeroed();
for (t, word) in m.iter().enumerate() {
w[t] = *word;
}
for t in 16..SCHEDULE_LEN {
w[t] = (w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16]).rotate_left(1);
}
w
}
#[cfg(all(test, feature = "std"))]
pub(crate) const fn words(&self) -> &[u32; SCHEDULE_LEN] {
&self.0
}
pub(crate) const MIRRORED: bool = cfg!(any(target_arch = "x86", target_arch = "x86_64"));
#[inline(always)]
const fn at(t: usize) -> usize {
if Self::MIRRORED {
SCHEDULE_LEN - 1 - t
} else {
t
}
}
}
#[allow(dead_code, reason = "a target without either kind calls none of these")]
impl Schedule {
#[inline(always)]
pub(crate) fn window<const T: usize, const N: usize>(&self) -> &[u32; N] {
let at = const { Self::window_start(T, N) };
self.0[at..at + N].try_into().unwrap()
}
#[inline(always)]
pub(crate) fn window_mut<const T: usize, const N: usize>(&mut self) -> &mut [u32; N] {
let at = const { Self::window_start(T, N) };
(&mut self.0[at..at + N]).try_into().unwrap()
}
const fn window_start(t: usize, n: usize) -> usize {
assert!(t + n <= SCHEDULE_LEN, "the window runs past the schedule");
if Self::MIRRORED {
SCHEDULE_LEN - n - t
} else {
t
}
}
}
impl core::ops::Index<usize> for Schedule {
type Output = u32;
#[inline(always)]
fn index(&self, t: usize) -> &u32 {
&self.0[Self::at(t)]
}
}
impl core::ops::IndexMut<usize> for Schedule {
#[inline(always)]
fn index_mut(&mut self, t: usize) -> &mut u32 {
&mut self.0[Self::at(t)]
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Digest([u8; DIGEST_SIZE]);
impl Digest {
pub const fn as_bytes(&self) -> &[u8; DIGEST_SIZE] {
&self.0
}
pub const fn to_bytes(self) -> [u8; DIGEST_SIZE] {
self.0
}
}
impl AsRef<[u8]> for Digest {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl From<[u8; DIGEST_SIZE]> for Digest {
fn from(bytes: [u8; DIGEST_SIZE]) -> Self {
Self(bytes)
}
}
impl From<Digest> for [u8; DIGEST_SIZE] {
fn from(digest: Digest) -> Self {
digest.0
}
}
impl PartialEq<[u8; DIGEST_SIZE]> for Digest {
fn eq(&self, other: &[u8; DIGEST_SIZE]) -> bool {
self.0 == *other
}
}
impl PartialEq<[u8]> for Digest {
fn eq(&self, other: &[u8]) -> bool {
self.0 == *other
}
}
impl fmt::LowerHex for Digest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in &self.0 {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
impl fmt::UpperHex for Digest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in &self.0 {
write!(f, "{byte:02X}")?;
}
Ok(())
}
}
impl fmt::Display for Digest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::LowerHex::fmt(self, f)
}
}
impl fmt::Debug for Digest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Digest({self:x})")
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Collision {
digest: Digest,
}
impl Collision {
pub const fn digest(&self) -> Digest {
self.digest
}
}
impl fmt::Display for Collision {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("SHA-1 collision attack detected; digest is the colliding one")
}
}
impl core::error::Error for Collision {}
pub fn digest(data: &[u8]) -> Result<Digest, Collision> {
let mut hasher = Hasher::new();
hasher.update(data);
hasher.finalize()
}
#[derive(Clone)]
struct Inner {
state: State,
len: u64,
buffer: [u8; BLOCK_SIZE],
}
#[derive(Clone)]
struct State {
h: [u32; STATE_LEN],
backend: Backend,
scalar_only: bool,
safe_hash: bool,
ubc_check: bool,
reduced_round_collision: bool,
found_collision: bool,
}
impl Inner {
fn new(builder: Builder, safe_hash: bool) -> Self {
Self {
state: State {
h: INITIAL_H,
backend: if builder.scalar_backend {
Backend::scalar()
} else {
Backend::new()
},
scalar_only: builder.scalar_backend,
safe_hash,
ubc_check: builder.ubc_check,
reduced_round_collision: builder.reduced_round_collisions,
found_collision: false,
},
len: 0,
buffer: [0; BLOCK_SIZE],
}
}
fn buffered(&self) -> usize {
(self.len % BLOCK_SIZE as u64) as usize
}
fn update(&mut self, mut data: &[u8]) {
let pos = self.buffered();
self.len = self.len.wrapping_add(data.len() as u64);
if pos > 0 {
let take = (BLOCK_SIZE - pos).min(data.len());
self.buffer[pos..pos + take].copy_from_slice(&data[..take]);
data = &data[take..];
if pos + take < BLOCK_SIZE {
return;
}
block::compress(&mut self.state, &self.buffer);
self.buffer = [0; BLOCK_SIZE];
}
let blocks = data.len() / BLOCK_SIZE;
let (full, rest) = data.split_at(blocks * BLOCK_SIZE);
if blocks > 0 {
block::compress(&mut self.state, full);
}
self.buffer[..rest.len()].copy_from_slice(rest);
}
fn reset(&mut self) {
self.state.h = INITIAL_H;
self.len = 0;
self.buffer = [0; BLOCK_SIZE];
self.state.found_collision = false;
}
fn finish(&mut self) -> (Digest, bool) {
let digest = self.finalize_inner();
(digest, self.state.found_collision)
}
fn finalize_inner(&mut self) -> Digest {
let bit_len = self.len << 3;
let pos = self.buffered();
self.buffer[pos] = 0x80;
if pos + 1 > BLOCK_SIZE - 8 {
block::compress(&mut self.state, &self.buffer);
self.buffer = [0; BLOCK_SIZE];
}
self.buffer[BLOCK_SIZE - 8..].copy_from_slice(&bit_len.to_be_bytes());
block::compress(&mut self.state, &self.buffer);
let mut out = [0u8; DIGEST_SIZE];
for (chunk, v) in out.chunks_exact_mut(4).zip(self.state.h.iter()) {
chunk.copy_from_slice(&v.to_be_bytes());
}
Digest(out)
}
}
#[derive(Clone)]
pub struct Hasher(Inner);
impl Default for Hasher {
fn default() -> Self {
Self::new()
}
}
macro_rules! hasher_common {
($ty:ident, $err:ident, $detected:expr) => {
impl fmt::Debug for $ty {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct(stringify!($ty))
.field("collision_detected", &self.collision_detected())
.finish_non_exhaustive()
}
}
impl $ty {
pub fn update(&mut self, data: &[u8]) {
self.0.update(data);
}
pub fn finalize(mut self) -> Result<Digest, $err> {
let (digest, detected) = self.0.finish();
if detected {
Err($detected(digest))
} else {
Ok(digest)
}
}
pub fn finalize_reset(&mut self) -> Result<Digest, $err> {
let (digest, detected) = self.0.finish();
self.0.reset();
if detected {
Err($detected(digest))
} else {
Ok(digest)
}
}
pub fn reset(&mut self) {
self.0.reset();
}
pub const fn collision_detected(&self) -> bool {
self.0.state.found_collision
}
}
#[cfg(feature = "std")]
impl std::io::Write for $ty {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.update(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
};
}
pub(crate) use hasher_common;
hasher_common!(Hasher, Collision, |digest| Collision { digest });
impl Hasher {
pub fn new() -> Self {
Builder::new().build()
}
#[doc(hidden)]
pub fn builder() -> Builder {
Builder::new()
}
}
#[doc(hidden)]
#[derive(Clone, Debug)]
pub struct Builder {
scalar_backend: bool,
ubc_check: bool,
reduced_round_collisions: bool,
}
impl Default for Builder {
fn default() -> Self {
Self::new()
}
}
impl Builder {
pub const fn new() -> Self {
Self {
scalar_backend: false,
ubc_check: true,
reduced_round_collisions: false,
}
}
#[doc(hidden)]
pub const fn internal_use_ubc(mut self, use_ubc: bool) -> Self {
self.ubc_check = use_ubc;
self
}
#[doc(hidden)]
pub const fn internal_reduced_round_collisions(mut self, reduced: bool) -> Self {
self.reduced_round_collisions = reduced;
self
}
#[doc(hidden)]
pub const fn internal_scalar_backend(mut self) -> Self {
self.scalar_backend = true;
self
}
pub fn build(self) -> Hasher {
Hasher(self.inner(false))
}
pub fn build_mitigating(self) -> mitigate::Hasher {
mitigate::Hasher(self.inner(true))
}
fn inner(self, safe_hash: bool) -> Inner {
Inner::new(self, safe_hash)
}
}