60
$\begingroup$

I have been trying to understand exactly how a length extension attack works on SHA-1. I'll detail below what I've understood so far so that I can convey my understanding of the same and hopefully get advice on where I'm going wrong.

Let's assume that for a message $m$, a secret $s$ is prepended to it and the SHA-1 hash is calculated. If $\textrm{len}(m) + \textrm{len}(s)$ is not a multiple of the block size, a certain amount of padding is added to it to make it a multiple, and the hash is generated using what is essentially $\textrm{H}(s \mathbin\| m \mathbin\| \textrm{padding})$.

Now SHA-1 internally uses 5 registers — it takes in a block, generates new register values, turns the crank, takes in another block, generates new register values, turns the crank ... and so on till the end. When all blocks are exhausted, the register values are concatenated and spit out.

Assume that an attacker knows a certain value of $m$, for which he also knows the hashed key. In such a case, he knows the state of the 5 registers when $(s \mathbin\ m \mathbin\ \textrm{padding})$ was processed. Let's also assume that the attacker knows $\textrm{len}(s)$ so that he can predict how much padding was added.

Here is where I get confused. From what I've read so far, the attack involves taking the known hash, adding a certain amount of padding, and then the value that we want. However, isn't the padding already used up in creating the hash? Why are we using padding in our attack?

$\endgroup$
1
  • $\begingroup$ Been wanting to ask the same but forgot! Is this by any chance in response to some blogpost unclearly explaining the 7th level of Stripe's CTF challenge? $\endgroup$
    – Luc
    Oct 7, 2012 at 18:08

2 Answers 2

84
$\begingroup$

SHA-1 processes data by 512-bit blocks (64 bytes). For a given input message m, it first appends some bits (at least 65, at most 576) so that the total length is a multiple of 512. Let's call p the added bits (that's the padding). The padding bits depend only on the length of m (these bits include an encoding of that length, but they do not depend on the value of the actual bits).

The padded message m||p is then split into successive 512-bit blocks, which are processed one after the other. SHA-1 uses an internal compression function (that's the traditional term); it also has a running state consisting of five 32-bit words. The compression function takes as input two values of 160 and 512 bits, respectively, and outputs 160 bits. The processing goes like this:

  • The running state is initialized to a fixed, conventional value (which is given in the SHA-1 specification).
  • For each input block, the compression function is evaluated, with as input the current running state, and the input block; the output of the function is the new running state.
  • The running state after processing the last block is the hash output.

So now, the length-extension attack. Suppose that you give me a hash value h, computed over a message m that is unknown to me. I know the length of m, but not its contents. Since I know the length of m, I can easily compute the padding p that you used. Then I imagine a message m', which begins with m||p; that is, m' = m||p||z where z is a sequence of bits that I can choose arbitrarily. I will now proceed to computing the SHA-1 hash of m', even though I do not know part of it (it begins with m, which I do not know).

When SHA-1 is computed over m', the latter is first padded with p', which depends on the length of m' (which I know). The resulting stream is m||p||z||p'. Then, the 512-bit blocks are processed one by one. I cannot do it for the first blocks, since I do not know m. However, I can imagine myself doing it. At some point, I would reach precisely the end of the p string (since the length of m||p is a multiple of 512). What would be the value of the running state at that point ? Well, that's exactly the hash value h that you gave me ! Therefore, I can stop imagining; I can start my SHA-1 computation of SHA-1(m') right at that point, at the beginning of z, using your hash value h as value for the running state.

That's the core of it. I can use SHA-1(m) to compute SHA-1(m'), a message which begins with the contents of m, and I can do that without knowing m. This property of SHA-1 (which applies to other Merkle-Damgård hash functions such as MD5 or SHA-256) is not in contradiction with the usual hash function security features (resistance to collisions, preimages and second preimages), but it shows that SHA-1 is not a random oracle.

What good can it do to the attacker ? Well, consider the following (flawed) construction for a Message Authentication Code: for a given key k and data to protect d, compute SHA-1(k||d) as the MAC value. This scheme breaks in the presence of the length-extension attack: if I, as an attacker, see a MAC for a message d, then I can compute the MAC for a message d' which extends d -- and I can do that without knowing k||d (so, in particular, without knowing the key). This allows me to forge messages with a valid MAC.

The length-extension attack is the reason why, when building a MAC out of a hash function, we need something a bit more convoluted, namely HMAC (which is safe against it).

$\endgroup$
23
  • 1
    $\begingroup$ How you can imagine a message starting with m||p since you don't know m? Do you mean that i feed the SHA-1 with h(m)||p||z? $\endgroup$
    – curious
    Oct 10, 2012 at 10:23
  • 4
    $\begingroup$ I imagine with my brain. That's the point of imagining: I don't have to do it for real. If I knew m, I could build the larger message. Application is given in the end of my answer: I don't know the MAC key but I can still forge a message by imagining I know it, and computing the MAC value that I would have obtained. $\endgroup$ Oct 10, 2012 at 11:11
  • $\begingroup$ It's really lot of text, hard to understand. Would help if you explain yourself more shortly. $\endgroup$ May 30, 2013 at 21:55
  • $\begingroup$ Question: HMAC-SHA-1 and HMAC-SHA2family is safe against length extension, and is better. However, as an alternative for, say, SHA-512, would simply truncating the output by N bytes prevent length extension, since an attacker no longer has the complete internal state? If so, for what values of N is this even possibly worthwhile? I assume there are other benefits HMAC gives outside the scope of this question, of course. $\endgroup$ Feb 23, 2014 at 7:39
  • 1
    $\begingroup$ @MehranTorki: the length which is encoded in the padding is the length. Not "the length modulo the block size". It is a multiple of 512, but you must still know which multiple of 512 it is. Is it 512, 1024, 1049088? $\endgroup$ Apr 18, 2016 at 20:01
38
$\begingroup$

The key to understanding hash extension attacks is to understand that the hash output isn't just the output of the machine generating the hash, it's also the state of the machine up till that point. In other words, just the hash output alone contains enough information for you to keep going and append more content to the hashed input.

The catch is that since hashes work on fixed-size blocks, if the data size doesn't line up properly along one of these block boundaries, it will be padded with zeros until it does. So we have to account for that in our attack. (Yes, SHA-1 has additional padding rules, which we'll ignore for simplicity)

An example

Let's say your super-secure authentication protocol is to take a given message, prepend a password to it, and send the hashed result as the "signature". I can compute the hash of your string plus an additional string just by knowing your hash output.

So lets say I have your "signature" for the string count=1&price=100.2, but I want to change the price to 1. I know your parsing algorithm replaces earlier values with later ones and it doesn't choke on embedded nulls. So theoretically I can compute the hash for my desired string, count=1&price=100.2&price=1.0.

But I can't just append my string to yours and expect it to work. I have to account for padding. We'll assume a block size of 10 for simplicity. (nb: "•" = null)

   123456789|123456789|123456789|123456789|             <- ruler
A: [mysecurepassword]count=1&price=100.2                
B: [mysecurepassword]count=1&price=100.2•••

Ostensibly I have the hash for string A, but what I really have is the hash for string B -- with the three nulls appended at the end for padding.

So instead of sending the string count=1&price=100.2&price=1, I would have to send the string count=1&price=100.2•••&price=1.0.

To generate my signature, I just initialize the hash engine with the signature you gave me, add &price=1.0, and output the result.

Of course, the rules of SHA-1 are somewhat different than my pretend 10-byte hash, but the principle still applies, it's just a matter of getting the bytes to line up.

$\endgroup$
7
  • $\begingroup$ excellent answer. i think i get it now $\endgroup$
    – wlad
    May 3, 2015 at 14:33
  • $\begingroup$ +1 for state and modifying the initial state (initialization vector) of the hash function $\endgroup$ Sep 5, 2016 at 13:53
  • 1
    $\begingroup$ "and it doesn't choke on embedded nulls" --> if embedded nulls are not possible, is length extension attack negated? $\endgroup$ Jan 26, 2017 at 2:39
  • $\begingroup$ I still don't fully get it. 1. Could the attacker computer the hash for any data, even one that completely replaces the original data? Or can they just append stuff to the end of the original data? If it's the latter, isn't the attack very limited? If the message is a JSON object for example, the opening and closing tags won't match, and the data will be invalid? 2. All explanations I've seen concatenated the secret key to the front of the data. Does that matter, or would the attack still work the same even if key is concatenated at the end of the data, in the middle of it or at both ends? $\endgroup$
    – Magnus
    Sep 9, 2022 at 7:53
  • $\begingroup$ Yeah, length extension attacks are only possible if the victim allows additional information to be accepted when it's tacked onto the end of the otherwise-authenticated string, as long as a new authentication hash can be computed. One way to thwart this is by making the hash not so dumb (use HMAC for example), another is to make the data itself non expandable, e.g. by adding an end marker or a length declaration or something. $\endgroup$
    – tylerl
    Sep 18, 2022 at 15:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.