Skip to main content

happy_cracking/crypto/
hash_ext.rs

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
37// Performs a SHA-256 hash length extension attack.
38//
39// Given H(secret || message) and the total length of (secret || message),
40// computes H(secret || message || padding || append) without knowing the secret.
41pub 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    // Recover the SHA-256 internal state from the hash output
56    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    // Compute the padding that would have been applied to the original message
67    let glue_padding = sha256_padding(original_len);
68
69    // The total length processed so far (must be a multiple of 64)
70    let total_processed = original_len + glue_padding.len() as u64;
71
72    // Build the message blocks for the appended data and process them
73    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    // Process each 64-byte block through SHA-256 compression
79    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    // Produce the new hash
86    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    // The forged suffix is the original padding + appended data
92    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
101// Computes the SHA-256 padding for a message of the given byte length.
102// Padding is: 0x80, then zeros, then 8-byte big-endian bit length,
103// such that the total padded length is a multiple of 64 bytes.
104fn sha256_padding(message_len: u64) -> Vec<u8> {
105    let bit_len = message_len * 8;
106    // Number of bytes in the last incomplete block
107    let remainder = (message_len % 64) as usize;
108    // We need at least 1 + 8 bytes (0x80 + length), padded to 64
109    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
122// Computes padding for the appended data block, using the total accumulated
123// bit length (including the original message + glue padding + append data).
124fn 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
139// SHA-256 round constants
140const 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
151// SHA-256 compression function.
152// Processes one 64-byte block and updates the state in-place.
153fn sha256_compress(state: &mut [u32; 8], block: &[u8; 64]) {
154    // Prepare message schedule
155    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}