feat(alg): impl sha2

This commit is contained in:
cscherr 2025-07-18 14:30:21 +02:00
parent 11677d8fe0
commit bb81a7dd49
Signed by: cscherrNT
GPG key ID: 8E2B45BC51A27EA7
6 changed files with 371 additions and 38 deletions

30
Cargo.lock generated
View file

@ -15,8 +15,10 @@ dependencies = [
name = "algorithms"
version = "0.1.0"
dependencies = [
"assert_hex",
"criterion",
"iai",
"pretty_assertions",
]
[[package]]
@ -28,6 +30,12 @@ dependencies = [
"stable_deref_trait",
]
[[package]]
name = "assert_hex"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7010f1430f0fc8ca80bdb5e5d074db68776a2e268ec6cf80b53712d3ea4bca7"
[[package]]
name = "atty"
version = "0.2.14"
@ -282,6 +290,12 @@ dependencies = [
"defmt 1.0.1",
]
[[package]]
name = "diff"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]]
name = "either"
version = "1.15.0"
@ -517,6 +531,16 @@ dependencies = [
"plotters-backend",
]
[[package]]
name = "pretty_assertions"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
dependencies = [
"diff",
"yansi",
]
[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
@ -1002,3 +1026,9 @@ name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"

View file

@ -6,8 +6,10 @@ edition = "2024"
[dependencies]
[dev-dependencies]
assert_hex = "0.4.1"
criterion = "0.3"
iai = "0.1.1"
pretty_assertions = "1.4.1"
[[bench]]
name = "crc32bench"
@ -16,3 +18,8 @@ harness = false
[[bench]]
name = "crc32bench_iai"
harness = false
[features]
default = []
std = []
show_internals = ["std"]

View file

@ -6,8 +6,7 @@
#include <threads.h>
#ifdef TEST
// #define SHOW_INTERNALS
#define SHOW_INTERNALS
#endif // TEST
#ifdef SHOW_INTERNALS
@ -48,7 +47,8 @@ static uint32_t addTemp = 0;
#define SHA256_ROTL(bits, word) (((word) << (bits)) | ((word) >> (32 - (bits))))
#define SHA256_ROTR(bits, word) (((word) >> (bits)) | ((word) << (32 - (bits))))
/* Define the SHA SIGMA and sigma macros */
/* Define the SHA SIGMA and sigma macros, yes these have the same name and are
* different */
#define SHA256_SIGMA0(word) \
(SHA256_ROTR(2, word) ^ SHA256_ROTR(13, word) ^ SHA256_ROTR(22, word))
#define SHA256_SIGMA1(word) \
@ -168,9 +168,7 @@ static void sha2_256_process_message_block(SHA2Context *context) {
#ifdef SHOW_INTERNALS
TRACE("DONE:\t")
dump_alphabet;
#endif // SHOW_INTERNALS
#ifdef SHOW_INTERNALS
TRACE("Intermediate hash before we add the working variables:\n");
for (int o = 0; o < SHA2_256_HashParts; o++) {
TRACE("\t%d:\t%032b\n", o, context->intermediate_hash[o]);
@ -254,14 +252,7 @@ static void sha2_256_pad_message(SHA2Context *context, uint8_t pad_byte) {
}
static void sha2_256_finalize(SHA2Context *context, uint8_t pad_byte) {
size_t i;
sha2_256_pad_message(context, pad_byte);
/* message may be sensitive, so clear it out */
for (i = 0; i < SHA2_256_BlockSize; ++i)
context->message_block[i] = 0;
context->length_high = 0; /* and clear length */
context->length_low = 0;
context->computed = true;
}
/*

View file

@ -7,7 +7,7 @@ use super::super::sha2::{DIGEST256_EMPTY, Digest256, SHA2_256_BLOCK_SIZE};
#[allow(unused)]
#[allow(clippy::enum_variant_names)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub enum SHA2Result {
pub enum SHA2Outcome {
shaSuccess = 0,
shaNull, /* Null pointer parameter */
shaInputTooLong, /* input data too long */
@ -50,45 +50,48 @@ impl Default for Sha2_256Context {
mod ffi {
use super::*;
unsafe extern "C" {
pub fn sha2_256_oneshot(data: *const u8, len: usize, digest: *mut Digest256) -> SHA2Result;
pub fn sha2_256_reset(context: *mut Sha2_256Context) -> SHA2Result;
pub fn sha2_256_oneshot(data: *const u8, len: usize, digest: *mut Digest256)
-> SHA2Outcome;
pub fn sha2_256_reset(context: *mut Sha2_256Context) -> SHA2Outcome;
pub fn sha2_256_input(
context: *mut Sha2_256Context,
data: *const u8,
len: usize,
) -> SHA2Result;
pub fn sha2_256_result(context: *mut Sha2_256Context, digest: *mut Digest256)
-> SHA2Result;
) -> SHA2Outcome;
pub fn sha2_256_result(
context: *mut Sha2_256Context,
digest: *mut Digest256,
) -> SHA2Outcome;
}
}
#[inline]
pub fn sha2_256_oneshot(data: &[u8]) -> Result<Digest256, SHA2Result> {
pub fn sha2_256_oneshot(data: &[u8]) -> Result<Digest256, SHA2Outcome> {
let mut digest: Digest256 = DIGEST256_EMPTY;
let res =
unsafe { ffi::sha2_256_oneshot(ref_to_ptr(data), data.len(), ref_to_ptr_mut(&mut digest)) };
to_res(digest, res)
}
pub fn sha2_256_reset(context: &mut Sha2_256Context) -> Result<(), SHA2Result> {
pub fn sha2_256_reset(context: &mut Sha2_256Context) -> Result<(), SHA2Outcome> {
let res = unsafe { ffi::sha2_256_reset(ref_to_ptr_mut(context)) };
to_res((), res)
}
pub fn sha2_256_input(context: &mut Sha2_256Context, data: &[u8]) -> Result<(), SHA2Result> {
pub fn sha2_256_input(context: &mut Sha2_256Context, data: &[u8]) -> Result<(), SHA2Outcome> {
let res = unsafe { ffi::sha2_256_input(ref_to_ptr_mut(context), ref_to_ptr(data), data.len()) };
to_res((), res)
}
pub fn sha2_256_result(context: &mut Sha2_256Context) -> Result<Digest256, SHA2Result> {
pub fn sha2_256_result(context: &mut Sha2_256Context) -> Result<Digest256, SHA2Outcome> {
let mut digest: Digest256 = DIGEST256_EMPTY;
let res = unsafe { ffi::sha2_256_result(ref_to_ptr_mut(context), ref_to_ptr_mut(&mut digest)) };
to_res(digest, res)
}
#[inline]
pub(crate) fn to_res<T>(if_ok: T, res: SHA2Result) -> Result<T, SHA2Result> {
if res == SHA2Result::shaSuccess {
pub(crate) fn to_res<T>(if_ok: T, res: SHA2Outcome) -> Result<T, SHA2Outcome> {
if res == SHA2Outcome::shaSuccess {
Ok(if_ok)
} else {
Err(res)

View file

@ -1,4 +1,4 @@
use crate::hash::ffi::SHA2Result;
use crate::hash::ffi::SHA2Outcome;
pub const SHA2_256_HASH_BITS: usize = 256;
pub const SHA2_256_HASH_BYTES: usize = SHA2_256_HASH_BITS / 8;
@ -8,19 +8,48 @@ pub const SHA2_256_BLOCK_SIZE: usize = 2 * SHA2_256_HASH_BYTES;
pub type Digest256 = [u32; SHA2_256_HASH_PARTS];
pub(crate) const DIGEST256_EMPTY: Digest256 = [0; SHA2_256_HASH_PARTS];
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub struct SHA2_256Context {
macro_rules! show_internals {
($block:block) => {
#[cfg(feature = "show_internals")]
$block
};
}
#[derive(PartialEq, Eq, Clone, Hash)]
pub struct Sha2_256Context {
intermediate_hash: Digest256,
block: [u8; SHA2_256_BLOCK_SIZE],
block_idx: usize,
length_low: usize,
length_high: usize,
}
impl SHA2_256Context {
impl Sha2_256Context {
const K: [u32; SHA2_256_BLOCK_SIZE] = [
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4,
0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe,
0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f,
0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116,
0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
0xc67178f2,
];
const H: [u32; SHA2_256_HASH_PARTS] = [
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
0x5be0cd19,
];
const PAD_BYTE: u8 = 0x80;
pub fn new() -> Self {
Self {
intermediate_hash: Default::default(),
intermediate_hash: Self::H,
block: [0; SHA2_256_BLOCK_SIZE],
block_idx: Default::default(),
length_low: Default::default(),
length_high: Default::default(),
}
}
@ -28,28 +57,293 @@ impl SHA2_256Context {
*self = Self::new();
}
pub fn input(&mut self, data: &[u8]) -> Result<(), SHA2Result> {
todo!()
pub fn input(&mut self, data: &[u8]) -> Result<(), SHA2Outcome> {
if data.is_empty() {
return Ok(());
}
for byte in data {
self.block[self.block_idx] = *byte;
self.block_idx += 1;
if self.add_length(8).is_ok() && self.block_idx == SHA2_256_BLOCK_SIZE {
self.process_block();
}
}
Ok(())
}
pub fn finish(self) -> Result<Digest256, SHA2Result> {
todo!()
pub fn finish(mut self) -> Result<Digest256, SHA2Outcome> {
self.finalize();
Ok(self.intermediate_hash)
}
// BUG: false values at end?
fn process_block(&mut self) {
show_internals!({
self.show_internal_state("start processing");
});
let mut w = [0; SHA2_256_BLOCK_SIZE];
#[allow(clippy::needless_range_loop)]
for t in 0..16 {
let t4 = t * 4;
w[t] = ((self.block[t4] as u32) << 24)
| ((self.block[t4 + 1] as u32) << 16)
| ((self.block[t4 + 2] as u32) << 8)
| (self.block[t4 + 3] as u32);
}
for t in 16..SHA2_256_BLOCK_SIZE {
w[t] = Self::sha256_sigma1_1(w[t - 2])
.wrapping_add(w[t - 7])
.wrapping_add(Self::sha256_sigma0_1(w[t - 15]))
.wrapping_add(w[t - 16]);
}
show_internals!({
for (i, w) in w.iter().enumerate() {
println!("w[{i}]:\t{w:032b}")
}
});
let mut a = self.intermediate_hash[0];
let mut b = self.intermediate_hash[1];
let mut c = self.intermediate_hash[2];
let mut d = self.intermediate_hash[3];
let mut e = self.intermediate_hash[4];
let mut f = self.intermediate_hash[5];
let mut g = self.intermediate_hash[6];
let mut h = self.intermediate_hash[7];
show_internals!({
println!("A:\t{a:032b}");
println!("B:\t{b:032b}");
println!("C:\t{c:032b}");
println!("D:\t{d:032b}");
println!("E:\t{e:032b}");
println!("F:\t{f:032b}");
println!("G:\t{g:032b}");
println!("H:\t{h:032b}");
});
let mut temp1;
let mut temp2;
#[allow(clippy::needless_range_loop)]
for t in 0..SHA2_256_BLOCK_SIZE {
show_internals!({
println!("Iter {t}");
});
// BUG: the alphabet values are not being changed correctly?
temp1 = h
.wrapping_add(Self::sha256_sigma1_0(e))
.wrapping_add(Self::sha_ch(e, f, g))
.wrapping_add(Self::K[t])
.wrapping_add(w[t]);
temp2 = Self::sha256_sigma0_0(a).wrapping_add(Self::sha_maj(a, b, c));
h = g;
g = f;
f = e;
e = d.wrapping_add(temp1);
d = c;
c = b;
b = a;
a = temp1.wrapping_add(temp2);
show_internals!({
println!("A:\t{a:032b} {a:08x}");
println!("B:\t{b:032b} {b:08x}");
println!("C:\t{c:032b} {c:08x}");
println!("D:\t{d:032b} {d:08x}");
println!("E:\t{e:032b} {e:08x}");
println!("F:\t{f:032b} {f:08x}");
println!("G:\t{g:032b} {g:08x}");
println!("H:\t{h:032b} {h:08x}");
for (i, h) in self.intermediate_hash.iter().enumerate() {
println!("h[{i}]:\t{h:032b} {h:08x}");
}
});
}
show_internals!({
self.show_internal_state("assigning intermediate_hash");
});
self.intermediate_hash[0] = self.intermediate_hash[0].wrapping_add(a);
self.intermediate_hash[1] = self.intermediate_hash[1].wrapping_add(b);
self.intermediate_hash[2] = self.intermediate_hash[2].wrapping_add(c);
self.intermediate_hash[3] = self.intermediate_hash[3].wrapping_add(d);
self.intermediate_hash[4] = self.intermediate_hash[4].wrapping_add(e);
self.intermediate_hash[5] = self.intermediate_hash[5].wrapping_add(f);
self.intermediate_hash[6] = self.intermediate_hash[6].wrapping_add(g);
self.intermediate_hash[7] = self.intermediate_hash[7].wrapping_add(h);
self.block_idx = 0;
// BUG: something is weird with these hash values, they are almost right?
show_internals!({
self.show_internal_state("processing end");
for (i, h) in self.intermediate_hash.iter().enumerate() {
println!("h[{i}]:\t{h:032b}");
}
});
}
fn add_length(&mut self, len: usize) -> Result<(), SHA2Outcome> {
let add_temp = self.length_low;
// Update length_low and check for overflow
self.length_low = self.length_low.wrapping_add(len);
if self.length_low < add_temp {
// Overflow occurred, increment length_high
self.length_high = self.length_high.wrapping_add(1);
if self.length_high == 0 {
return Err(SHA2Outcome::shaInputTooLong);
}
}
impl Default for SHA2_256Context {
Ok(())
}
fn finalize(&mut self) {
self.pad_block();
}
fn pad_block(&mut self) {
if self.block_idx >= (SHA2_256_BLOCK_SIZE - 8) {
self.block[self.block_idx] = Self::PAD_BYTE;
self.block_idx += 1;
while self.block_idx < SHA2_256_BLOCK_SIZE {
self.block[self.block_idx] = 0;
self.block_idx += 1;
}
self.process_block();
} else {
self.block[self.block_idx] = Self::PAD_BYTE;
self.block_idx += 1;
}
while self.block_idx < (SHA2_256_BLOCK_SIZE - 8) {
self.block[self.block_idx] = 0;
self.block_idx += 1;
}
self.block[56] = (self.length_high >> 24) as u8;
self.block[57] = (self.length_high >> 16) as u8;
self.block[58] = (self.length_high >> 8) as u8;
self.block[59] = (self.length_high) as u8;
self.block[60] = (self.length_low >> 24) as u8;
self.block[61] = (self.length_low >> 16) as u8;
self.block[62] = (self.length_low >> 8) as u8;
self.block[63] = (self.length_low) as u8;
self.process_block();
}
#[cfg(feature = "show_internals")]
fn show_internal_state(&self, msg: impl core::fmt::Display) {
println!("{msg}\n{self:#x?}")
}
}
/// internal functions for SHA2 process_block
impl Sha2_256Context {
#[inline]
fn sha256_shr(bits: u32, word: u32) -> u32 {
word >> bits
}
#[inline]
fn sha256_rotr(bits: u32, word: u32) -> u32 {
u32::rotate_right(word, bits)
}
fn sha256_sigma0_0(word: u32) -> u32 {
Self::sha256_rotr(2, word) ^ Self::sha256_rotr(13, word) ^ Self::sha256_shr(22, word)
}
fn sha256_sigma1_0(word: u32) -> u32 {
Self::sha256_rotr(6, word) ^ Self::sha256_rotr(11, word) ^ Self::sha256_shr(25, word)
}
fn sha256_sigma0_1(word: u32) -> u32 {
Self::sha256_rotr(7, word) ^ Self::sha256_rotr(18, word) ^ Self::sha256_shr(3, word)
}
fn sha256_sigma1_1(word: u32) -> u32 {
Self::sha256_rotr(17, word) ^ Self::sha256_rotr(19, word) ^ Self::sha256_shr(10, word)
}
#[inline]
fn sha_ch(x: u32, y: u32, z: u32) -> u32 {
(x & (y ^ z)) ^ z
}
#[inline]
fn sha_maj(x: u32, y: u32, z: u32) -> u32 {
(x & (y | z)) | (y & z)
}
}
impl Default for Sha2_256Context {
fn default() -> Self {
Self::new()
}
}
pub fn sha2_256_oneshot(data: &[u8]) -> Result<Digest256, SHA2Result> {
todo!()
pub fn sha2_256_oneshot(data: &[u8]) -> Result<Digest256, SHA2Outcome> {
let mut ctx = Sha2_256Context::default();
ctx.input(data)?;
ctx.finish()
}
#[cfg(feature = "show_internals")]
#[allow(unused)]
mod debug {
use super::*;
struct InlineArrHex2<'a, T: core::fmt::Debug + core::fmt::LowerHex>(&'a [T]);
impl<'a, T> core::fmt::Debug for InlineArrHex2<'a, T>
where
T: core::fmt::Debug + core::fmt::LowerHex,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "[\n")?;
for (i, t) in self.0.iter().enumerate() {
write!(f, "0x{t:02x}")?;
if i < self.0.len() - 1 {
write!(f, ", ")?;
}
if i % 8 == 7 && i < self.0.len() - 1 {
writeln!(f)?;
}
}
write!(f, "\n]")?;
Ok(())
}
}
impl core::fmt::Debug for Sha2_256Context {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Sha2_256Context")
.field("intermediate_hash", &InlineArrHex2(&self.intermediate_hash))
.field("block", &InlineArrHex2(&self.block))
.field("block_idx", &self.block_idx)
.field("length_low", &self.length_low)
.field("length_high", &self.length_high)
.finish()
}
}
}
#[cfg(feature = "show_internals")]
use debug::*;
#[cfg(test)]
mod test {
use super::*;
use assert_hex::*;
use pretty_assertions::{assert_eq, assert_ne};
const TEST_VALUES: &[(&str, Digest256)] = &[
(
@ -70,8 +364,16 @@ mod test {
#[test]
fn test_check() {
let mut dig;
for (input, expected_output) in TEST_VALUES.iter().copied() {
assert_eq!(sha2_256_oneshot(input.as_bytes()), Ok(expected_output))
dig = sha2_256_oneshot(input.as_bytes()).unwrap();
println!(
"Binary of first seg matches: {}\n{:032b}\n{:032b}",
dig[0] == expected_output[0],
dig[0],
expected_output[0]
);
assert_eq_hex!(dig, expected_output)
}
}
}

View file

@ -7,7 +7,7 @@
//! Many thanks to the [crc-catalogue](https://reveng.sourceforge.io/crc-catalogue/) for providing
//! an overview over the various algorithms with test vectors and examples.
#![cfg_attr(not(test), no_std)]
#![cfg_attr(not(any(test, feature = "std")), no_std)]
pub mod crc;
pub mod hash;