krypteia-arcana 0.1.0

Pure-Rust classical cryptographic primitives: RSA (PKCS#1 v1.5, OAEP), ECC (NIST P-256/384/521, secp256k1), ECDSA, EdDSA (Ed25519), X25519, AES (128/192/256, GCM/CBC), DES/3DES, SHA-1/2/3, HMAC. Side-channel-aware (Montgomery ladder, branchless point_add_ct). Targets embedded (no_std), STM32 M0/M4/M33, ESP32-C3 RISC-V. Zero runtime dependencies.
Documentation
//! SHA-1 hash function (FIPS 180-4).
//!
//! 160-bit output, 512-bit (64-byte) blocks, Merkle-Damgard construction.
//! **Deprecated for security use** — provided for legacy compatibility.

use crate::Hasher;

const H0: [u32; 5] = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0];

/// SHA-1 hasher (FIPS 180-4). 160-bit output, 64-byte blocks.
///
/// **Collision-broken** since SHAttered (2017): do not use for any new
/// digital signature design. Shipped here for legacy interop only --
/// HMAC-SHA-1 in TLS 1.2 cipher suites, X.509 certificates from the
/// early 2000s, Git's content-addressed storage.
#[derive(Clone)]
pub struct Sha1 {
    state: [u32; 5],
    buf: [u8; 64],
    buf_len: usize,
    total_len: u64,
}

fn compress(state: &mut [u32; 5], block: &[u8]) {
    let mut w = [0u32; 80];
    for i in 0..16 {
        w[i] = u32::from_be_bytes([block[4 * i], block[4 * i + 1], block[4 * i + 2], block[4 * i + 3]]);
    }
    for i in 16..80 {
        w[i] = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]).rotate_left(1);
    }

    let mut a = state[0];
    let mut b = state[1];
    let mut c = state[2];
    let mut d = state[3];
    let mut e = state[4];

    for i in 0..80 {
        let (f, k) = match i {
            0..=19 => ((b & c) | ((!b) & d), 0x5A827999u32),
            20..=39 => (b ^ c ^ d, 0x6ED9EBA1u32),
            40..=59 => ((b & c) | (b & d) | (c & d), 0x8F1BBCDCu32),
            _ => (b ^ c ^ d, 0xCA62C1D6u32),
        };
        let temp = a
            .rotate_left(5)
            .wrapping_add(f)
            .wrapping_add(e)
            .wrapping_add(k)
            .wrapping_add(w[i]);
        e = d;
        d = c;
        c = b.rotate_left(30);
        b = a;
        a = temp;
    }

    state[0] = state[0].wrapping_add(a);
    state[1] = state[1].wrapping_add(b);
    state[2] = state[2].wrapping_add(c);
    state[3] = state[3].wrapping_add(d);
    state[4] = state[4].wrapping_add(e);
}

impl Hasher for Sha1 {
    const OUTPUT_LEN: usize = 20;
    const BLOCK_LEN: usize = 64;

    fn new() -> Self {
        Self {
            state: H0,
            buf: [0u8; 64],
            buf_len: 0,
            total_len: 0,
        }
    }

    fn update(&mut self, data: &[u8]) {
        let mut pos = 0;
        self.total_len += data.len() as u64;

        if self.buf_len > 0 {
            let need = 64 - self.buf_len;
            let take = need.min(data.len());
            self.buf[self.buf_len..self.buf_len + take].copy_from_slice(&data[..take]);
            self.buf_len += take;
            pos = take;
            if self.buf_len == 64 {
                let block = self.buf;
                compress(&mut self.state, &block);
                self.buf_len = 0;
            }
        }

        while pos + 64 <= data.len() {
            compress(&mut self.state, &data[pos..pos + 64]);
            pos += 64;
        }

        if pos < data.len() {
            let remaining = data.len() - pos;
            self.buf[..remaining].copy_from_slice(&data[pos..]);
            self.buf_len = remaining;
        }
    }

    fn finalize(self) -> Vec<u8> {
        let mut out = vec![0u8; 20];
        self.finalize_into(&mut out);
        out
    }

    fn finalize_into(mut self, out: &mut [u8]) {
        let bit_len = self.total_len * 8;
        // Padding
        let mut pad = [0u8; 72]; // max padding needed
        pad[0] = 0x80;
        let pad_len = if self.buf_len < 56 {
            56 - self.buf_len
        } else {
            120 - self.buf_len
        };
        self.update(&pad[..pad_len]);
        self.update(&bit_len.to_be_bytes());

        for (i, word) in self.state.iter().enumerate() {
            let bytes = word.to_be_bytes();
            let start = i * 4;
            if start + 4 <= out.len() {
                out[start..start + 4].copy_from_slice(&bytes);
            } else if start < out.len() {
                let end = out.len() - start;
                out[start..].copy_from_slice(&bytes[..end]);
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::Hasher;

    #[test]
    fn test_sha1_empty() {
        let digest = Sha1::hash(b"");
        let expected: [u8; 20] = [
            0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8,
            0x07, 0x09,
        ];
        assert_eq!(&digest[..], &expected[..]);
    }

    #[test]
    fn test_sha1_abc() {
        let digest = Sha1::hash(b"abc");
        let expected: [u8; 20] = [
            0xa9, 0x99, 0x3e, 0x36, 0x47, 0x06, 0x81, 0x6a, 0xba, 0x3e, 0x25, 0x71, 0x78, 0x50, 0xc2, 0x6c, 0x9c, 0xd0,
            0xd8, 0x9d,
        ];
        assert_eq!(&digest[..], &expected[..]);
    }
}