1use anyhow::{Context, Result};
2use clap::Subcommand;
3
4#[derive(Subcommand)]
5pub enum HashExtAction {
6 #[command(about = "SHA-256 hash length extension attack")]
7 Sha256Extend {
8 #[arg(help = "Original hash (hex)")]
9 original_hash: String,
10 #[arg(long, help = "Length of secret + original message in bytes")]
11 original_len: u64,
12 #[arg(long, help = "Data to append")]
13 append: String,
14 },
15}
16
17pub fn run(action: HashExtAction) -> Result<()> {
18 match action {
19 HashExtAction::Sha256Extend {
20 original_hash,
21 original_len,
22 append,
23 } => {
24 let result = sha256_extend(&original_hash, original_len, append.as_bytes())?;
25 println!("New hash: {}", result.new_hash);
26 println!("Forged suffix: {}", hex::encode(&result.forged_suffix));
27 }
28 }
29 Ok(())
30}
31
32pub struct ExtensionResult {
33 pub new_hash: String,
34 pub forged_suffix: Vec<u8>,
35}
36
37pub fn sha256_extend(
42 original_hash_hex: &str,
43 original_len: u64,
44 append: &[u8],
45) -> Result<ExtensionResult> {
46 let hash_bytes =
47 hex::decode(original_hash_hex.trim()).context("Invalid hex in original hash")?;
48 if hash_bytes.len() != 32 {
49 anyhow::bail!(
50 "SHA-256 hash must be 32 bytes (64 hex chars), got {}",
51 hash_bytes.len()
52 );
53 }
54
55 let mut state = [0u32; 8];
57 for i in 0..8 {
58 state[i] = u32::from_be_bytes([
59 hash_bytes[i * 4],
60 hash_bytes[i * 4 + 1],
61 hash_bytes[i * 4 + 2],
62 hash_bytes[i * 4 + 3],
63 ]);
64 }
65
66 let glue_padding = sha256_padding(original_len);
68
69 let total_processed = original_len + glue_padding.len() as u64;
71
72 let mut buffer = append.to_vec();
74 let final_bit_len = (total_processed + append.len() as u64) * 8;
75 let append_padding = sha256_finish_padding(append.len() as u64, final_bit_len);
76 buffer.extend_from_slice(&append_padding);
77
78 for chunk in buffer.chunks(64) {
80 let mut block = [0u8; 64];
81 block[..chunk.len()].copy_from_slice(chunk);
82 sha256_compress(&mut state, &block);
83 }
84
85 let mut new_hash_bytes = Vec::with_capacity(32);
87 for &word in &state {
88 new_hash_bytes.extend_from_slice(&word.to_be_bytes());
89 }
90
91 let mut forged_suffix = glue_padding;
93 forged_suffix.extend_from_slice(append);
94
95 Ok(ExtensionResult {
96 new_hash: hex::encode(&new_hash_bytes),
97 forged_suffix,
98 })
99}
100
101fn sha256_padding(message_len: u64) -> Vec<u8> {
105 let bit_len = message_len * 8;
106 let remainder = (message_len % 64) as usize;
108 let padding_len = if remainder < 56 {
110 56 - remainder
111 } else {
112 120 - remainder
113 };
114
115 let mut padding = Vec::with_capacity(padding_len + 8);
116 padding.push(0x80);
117 padding.resize(padding_len, 0x00);
118 padding.extend_from_slice(&bit_len.to_be_bytes());
119 padding
120}
121
122fn sha256_finish_padding(append_len: u64, total_bit_len: u64) -> Vec<u8> {
125 let remainder = (append_len % 64) as usize;
126 let padding_len = if remainder < 56 {
127 56 - remainder
128 } else {
129 120 - remainder
130 };
131
132 let mut padding = Vec::with_capacity(padding_len + 8);
133 padding.push(0x80);
134 padding.resize(padding_len, 0x00);
135 padding.extend_from_slice(&total_bit_len.to_be_bytes());
136 padding
137}
138
139const K: [u32; 64] = [
141 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
142 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
143 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
144 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
145 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
146 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
147 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
148 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
149];
150
151fn sha256_compress(state: &mut [u32; 8], block: &[u8; 64]) {
154 let mut w = [0u32; 64];
156 for i in 0..16 {
157 w[i] = u32::from_be_bytes([
158 block[i * 4],
159 block[i * 4 + 1],
160 block[i * 4 + 2],
161 block[i * 4 + 3],
162 ]);
163 }
164 for i in 16..64 {
165 let s0 = w[i - 15].rotate_right(7) ^ w[i - 15].rotate_right(18) ^ (w[i - 15] >> 3);
166 let s1 = w[i - 2].rotate_right(17) ^ w[i - 2].rotate_right(19) ^ (w[i - 2] >> 10);
167 w[i] = w[i - 16]
168 .wrapping_add(s0)
169 .wrapping_add(w[i - 7])
170 .wrapping_add(s1);
171 }
172
173 let mut a = state[0];
174 let mut b = state[1];
175 let mut c = state[2];
176 let mut d = state[3];
177 let mut e = state[4];
178 let mut f = state[5];
179 let mut g = state[6];
180 let mut h = state[7];
181
182 for i in 0..64 {
183 let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25);
184 let ch = (e & f) ^ ((!e) & g);
185 let temp1 = h
186 .wrapping_add(s1)
187 .wrapping_add(ch)
188 .wrapping_add(K[i])
189 .wrapping_add(w[i]);
190 let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22);
191 let maj = (a & b) ^ (a & c) ^ (b & c);
192 let temp2 = s0.wrapping_add(maj);
193
194 h = g;
195 g = f;
196 f = e;
197 e = d.wrapping_add(temp1);
198 d = c;
199 c = b;
200 b = a;
201 a = temp1.wrapping_add(temp2);
202 }
203
204 state[0] = state[0].wrapping_add(a);
205 state[1] = state[1].wrapping_add(b);
206 state[2] = state[2].wrapping_add(c);
207 state[3] = state[3].wrapping_add(d);
208 state[4] = state[4].wrapping_add(e);
209 state[5] = state[5].wrapping_add(f);
210 state[6] = state[6].wrapping_add(g);
211 state[7] = state[7].wrapping_add(h);
212}