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}