Skip to main content

ruscrypt/hash/
sha256.rs

1//! # SHA-256 Hash Function Implementation
2//!
3//! SHA-256 (Secure Hash Algorithm 256-bit) is a cryptographic hash function
4//! that produces a 256-bit (32-byte) hash value, typically rendered as a
5//! 64-character hexadecimal string.
6//!
7//! ✅ **Security Status**: SHA-256 is considered cryptographically secure
8//! and is widely used in modern applications including Bitcoin and TLS.
9//!
10//! ## Properties
11//!
12//! - **Deterministic**: Same input always produces same hash
13//! - **Fixed size**: Always outputs 256 bits (64 hex characters)
14//! - **Avalanche effect**: Small input changes cause large output changes
15//! - **One-way**: Computationally infeasible to reverse
16//!
17//! ## Examples
18//!
19//! ```rust
20//! use ruscrypt::hash::sha256;
21//!
22//! let hash = sha256::hash("Hello, World!").unwrap();
23//! println!("SHA-256: {}", hash);
24//! assert_eq!(hash.len(), 64); // Always 64 hex characters
25//!
26//! // Different inputs produce different hashes
27//! let hash1 = sha256::hash("Hello").unwrap();
28//! let hash2 = sha256::hash("Hello!").unwrap();
29//! assert_ne!(hash1, hash2);
30//! ```
31
32use anyhow::Result;
33
34/// Computes the SHA-256 hash of the input text.
35///
36/// This function implements the complete SHA-256 algorithm including:
37/// - Message preprocessing and padding
38/// - Processing in 512-bit blocks
39/// - 64 rounds of compression per block
40/// - Final hash value computation
41///
42/// # Arguments
43///
44/// * `input` - The text to hash (any UTF-8 string)
45///
46/// # Returns
47///
48/// Returns a 64-character lowercase hexadecimal string representing the
49/// 256-bit hash value.
50///
51/// # Examples
52///
53/// ```rust
54/// use ruscrypt::hash::sha256;
55///
56/// // Empty string
57/// let hash = sha256::hash("").unwrap();
58/// assert_eq!(hash, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
59///
60/// // Simple text
61/// let hash = sha256::hash("abc").unwrap();
62/// assert_eq!(hash, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
63///
64/// // Unicode support
65/// let hash = sha256::hash("Hello 世界").unwrap();
66/// assert_eq!(hash.len(), 64);
67///
68/// // Consistency check
69/// let hash1 = sha256::hash("test").unwrap();
70/// let hash2 = sha256::hash("test").unwrap();
71/// assert_eq!(hash1, hash2);
72/// ```
73///
74/// # Algorithm Details
75///
76/// The implementation follows RFC 6234 and includes:
77/// - Proper message padding with length encoding
78/// - 64 rounds of SHA-256 compression function
79/// - Correct handling of endianness
80/// - Support for messages of any length
81pub fn hash(input: &str) -> Result<String> {
82    let bytes = input.as_bytes();
83    let hash_bytes = sha256_hash(bytes);
84
85    // Convert to hexadecimal string
86    let hex_string = hash_bytes
87        .iter()
88        .map(|byte| format!("{byte:02x}"))
89        .collect::<String>();
90
91    Ok(hex_string)
92}
93
94/// Core SHA-256 implementation that processes the padded message.
95///
96/// This function implements the SHA-256 algorithm as specified in FIPS 180-4.
97/// It processes the input in 512-bit chunks and applies the compression function.
98///
99/// # Arguments
100///
101/// * `input` - Raw bytes to hash
102///
103/// # Returns
104///
105/// Returns a 32-byte array containing the hash value.
106///
107/// # Implementation Notes
108///
109/// - Uses the official SHA-256 constants and round functions
110/// - Processes message in 512-bit (64-byte) blocks
111/// - Applies proper padding according to the standard
112/// - Implements the complete message schedule and compression
113fn sha256_hash(input: &[u8]) -> [u8; 32] {
114    // SHA-256 constants (first 32 bits of fractional parts of cube roots of first 64 primes)
115    const K: [u32; 64] = [
116        0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4,
117        0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe,
118        0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f,
119        0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
120        0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
121        0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
122        0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116,
123        0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
124        0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
125        0xc67178f2,
126    ];
127
128    // Initialize hash values (first 32 bits of fractional parts of square roots of first 8 primes)
129    let mut h = [
130        0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
131        0x5be0cd19,
132    ];
133
134    // Pre-processing: add padding
135    let mut message = input.to_vec();
136    let original_len = message.len() as u64;
137
138    // Append '1' bit (plus seven '0' bits, represented as 0x80)
139    message.push(0x80);
140
141    // Append '0' bits until message length ≡ 448 (mod 512)
142    while (message.len() % 64) != 56 {
143        message.push(0);
144    }
145
146    // Append original length as 64-bit big-endian
147    message.extend_from_slice(&(original_len * 8).to_be_bytes());
148
149    // Process message in 512-bit chunks
150    for chunk in message.chunks_exact(64) {
151        let mut w = [0u32; 64];
152
153        // Break chunk into sixteen 32-bit big-endian words
154        for (i, word_bytes) in chunk.chunks_exact(4).enumerate() {
155            w[i] = u32::from_be_bytes([word_bytes[0], word_bytes[1], word_bytes[2], word_bytes[3]]);
156        }
157
158        // Extend the first 16 words into the remaining 48 words
159        for i in 16..64 {
160            let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3);
161            let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10);
162            w[i] = w[i - 16]
163                .wrapping_add(s0)
164                .wrapping_add(w[i - 7])
165                .wrapping_add(s1);
166        }
167
168        // Initialize working variables
169        let mut a: u32 = h[0];
170        let mut b: u32 = h[1];
171        let mut c: u32 = h[2];
172        let mut d: u32 = h[3];
173        let mut e: u32 = h[4];
174        let mut f: u32 = h[5];
175        let mut g: u32 = h[6];
176        let mut h_var: u32 = h[7];
177
178        // Main loop
179        for i in 0..64 {
180            let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
181            let ch = (e & f) ^ ((!e) & g);
182            let temp1 = h_var
183                .wrapping_add(s1)
184                .wrapping_add(ch)
185                .wrapping_add(K[i])
186                .wrapping_add(w[i]);
187            let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
188            let maj = (a & b) ^ (a & c) ^ (b & c);
189            let temp2 = s0.wrapping_add(maj);
190
191            h_var = g;
192            g = f;
193            f = e;
194            e = d.wrapping_add(temp1);
195            d = c;
196            c = b;
197            b = a;
198            a = temp1.wrapping_add(temp2);
199        }
200
201        // Add this chunk's hash to result
202        h[0] = h[0].wrapping_add(a);
203        h[1] = h[1].wrapping_add(b);
204        h[2] = h[2].wrapping_add(c);
205        h[3] = h[3].wrapping_add(d);
206        h[4] = h[4].wrapping_add(e);
207        h[5] = h[5].wrapping_add(f);
208        h[6] = h[6].wrapping_add(g);
209        h[7] = h[7].wrapping_add(h_var);
210    }
211
212    // Convert to bytes (big-endian)
213    let mut result = [0u8; 32];
214    for (i, &word) in h.iter().enumerate() {
215        let bytes = word.to_be_bytes();
216        result[i * 4..(i + 1) * 4].copy_from_slice(&bytes);
217    }
218
219    result
220}