A MAC is calculated the following way:

mac = sha1(secret || m)[0:8]

[0:8] denotes taking the first 8 characters from the resulting hash.

The length of the secret is unknown. Users (and potential attackers) can check the validity of a particular MAC and message pair online.

if(macInput == sha1(secret || m)[0:8]){
   // ok
   // access denied

Can this be attacked? I heard it can, but I don't see why and how.

  • $\begingroup$ Is the first 8 bytes or the first 8 characters and if characters, in which encoding (hex?, base64?)? $\endgroup$ – SEJPM Apr 15 '17 at 9:20
  • $\begingroup$ hmm. I'd say the message is in UTF8, the hash output is hex and it therefore takes 8 bytes. $\endgroup$ – netik Apr 15 '17 at 9:29

OK, there are two issues with this:

First, the MAC length is at most 64 bit, that is an attacker could try to brute-force-find a valid MAC with "merely" $2^{64}$ submissions, which is border-line feasible for large companies today.
If it actually only is 8 hex character, then each hex character encodes at most 4 bits of information and thus you get $8\cdot 4=32$ bits for the MAC security meaning brute-force would take at most $2^{32}$ (around 4bn) tries to recover a valid MAC.

Second, much more severe is the naive verification of the MAC which will probably allow for a classic timing side-channel attack.
Chances are that this multi-byte equality verification is implemented as an "optimized" check, that is (in a python-ish way):

if len(lb)!=len(rb):
   return false
for (lb,rb) in zip(l_input,r_input):
   if lb != rb :
      return false
return true

Which checks the that the input lengths match and then pairs all the bytes at the same position in both inputs and outputs false as soon as a miss-match is detected and true if no missmatch is found.

Now what we can do is to measure the time it takes to verify our MAC. So we would start of with 7 0 characters and prepend each possible value (character) value to this string. We send all to verification. The one that takes longest has a match on the first character. Now we fix this first character that we just verified and continue with the second with the same method. We continue with this process until we have recovered the full MAC and get access in return.

If we get all-equal times for the first character, we take iterate over all possible choices for the first two characters (enlarge as needed). If we know the size of the information $b$ (in bits) encoded in the constant-time verification data structure (eg 8 for byte-wise, 32 for integer-wise, ...) and we know the mac information $l$ in bits, then we take at most the following amount of calls to the verification oracle to recover the valid MAC:


If in your case 8 hex characters need to be verified this becomes $2^4\cdot 8=256$ calls to the verification oracle (because the effective information of the hash is 32 bits composed of 4 bit elements).

| improve this answer | |

I will generalize the question somewhat:

  1. The outputs of hash functions like SHA-1 are fixed-size bit strings, not "characters," so I'll proceed accordingly.
  2. Instead of a prespecified prefix size, I'll treat it as a variable.

Section 3.4 of Coron, Dodis, Malinaud and Puniya's 2005 paper on hash function constructions describes the chop-MD construction, where messages are hashed with a Merkle-Damgård hash function like MD5, SHA-1 or SHA-2, but the last $s$ bits of the digest are dropped. They give a result that this construction is random oracle indifferentiable, with a security level determined as a function of $s$.

Since random_oracle(key || msg) is a secure MAC construction, in principle the construction you propose could be used to construct a secure MAC, if chop-MD's full requirements are met. But this can't be accomplished with SHA-1, because the problem is that the numbers matter: for chop-MD to be secure, you need a sufficient number of both chopped and unchopped bits, and SHA-1's 160-bit output just ain't big enough to satisfy these conditions. Quoting the authors:

While really simple, the drawback of this method is that its exact security is proportional to $q^2 2^{−s}$, where $s$ is the number of chopped bits and $q$ is the number of oracle queries. Thus, to achieve adequate security level the value of $s$ has to be relatively high, which means that short-output hash functions such as SHA-1 and MD5 cannot be fixed using this method. However, functions such as SHA-512 can naturally be fixed (say, by setting $s = 256$).

So, for example, mac = sha512(key || msg)[0..32] (32 bytes of the binary output of SHA-512; that would be 64 hex digits) should be a secure MAC. In your SHA-1 based proposal, the chop size $s = 128$ is uncomfortably small; but of course because your MAC tag size $160 - s = 32$ is tiny, you can't afford to pick a bigger $s$, you just don't have the headroom.

Note also that:

  1. You should of course use HMAC or another standard MAC, not your home-brew alternative.
  2. Comparing MAC tags for equality has to be done with a constant-time comparison, as SEJPM points out.
  3. General hygiene calls for making concatenation of the key and the message unambiguous, so that it's impossible to get mixed up about which part of the concatenated hash input is the key and which is the output. Some mechanisms:
    • Only allow one fixed key size
    • Pad variable-length keys to a fixed maximum size (HMAC does this)
    • Prefix variable-length keys with their length
    • Put an unambiguous delimiter between the key and the message
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.