# Does this use of hash provide anything over a nonce? HMAC instead?

I ran across some CSRF code that does:

Tokens.prototype._tokenize = function tokenize (secret, salt) {
return salt + '-' + hash(salt + '-' + secret)
}


where hash seems to be a SHA1 digest modified to fit in a multipart/form-data body without extra encoding:

function hash (str) {
return crypto
.createHash('sha1')
.update(str, 'ascii')
.digest('base64')
.replace(PLUS_GLOBAL_REGEXP, '-')
.replace(SLASH_GLOBAL_REGEXP, '_')
.replace(EQUAL_GLOBAL_REGEXP, '')
}


IIUC, the use of hash doesn't prevent forging. Does this provide much over just using a closely held nonce?

Does hash make it particularly difficult to derive the input? Since secret is not encrypted, this seems to assume that either

• secret need not be secret or
• the token is closely held, which should be the case for a CSRF token.

the use of hash doesn't prevent forging. Does this provide much over just using a closely held nonce?

on the contrary, the use of hash described should prevent forging assuming the secret is kept .. well.. secret

Does hash make it particularly difficult to derive the input? Since secret is not encrypted, this seems to assume that either

Indeed, it should be very hard to derive input (secret) from its hash. That's one of the mandatory properties of a hash function. The secret should stay secret on the server side (not revealed to the client)

secret need not be secret or the token is closely held, which should be the case for a CSRF token.

if the secret is revealed, the client could forge a new valid CSRF token

• Re "very hard to derive input (secret) from its hash", the input is actually salt+'-'+secret and the salt precedes the token in plain text. IIUC brute forcing a hash(secret) would require roughly 2¹⁶⁰ operations. But a bit more than half the input is not kept secret, so would brute forcing takes √2¹⁶⁰=2⁸⁰ operations? This answer suggests that 80b of entropy is not sufficient. – Mike Samuel Nov 2 '17 at 14:27
• @MikeSamuel no.. the salt length is not important, important is length of the secret. Even salt would make up 90% of the input length (and is known) and secret is 160 bit, it would require 2^160 to brute-force the the input – gusto2 Nov 2 '17 at 19:43
• Thanks for explaining. My intuition was that if I know all but one bit of the input, then I can brute force it by testing 2 values, one where the unknown bit is unset, and one where it is set. If one can't predict which input bits flip which output bits, then brute forcing takes |possibleInputs| steps which is also consistent with my in-extremis intuition. – Mike Samuel Nov 2 '17 at 19:52

This looks like a straightforward application for a MAC. Let's summarize the security property of MACs: if the honest party chooses the key at random and keeps it secret, it should be impossible in practice for an adversary to forge a tag for any input of their choice. This property is known as existential unforgeability under chosen message attack, a.k.a. EU-CMA. So HMAC is just a perfect tool for this task, and should be preferred over homebrew solutions like the code you show.

Since secret is not encrypted [...]

You seem to have some misconception here. secret needs to be, well, secret—i.e., known to the honest parties but unknown to the adversaries. That has nothing to do with whether it's encrypted or not.

I don't understand what you mean by a "closely held nonce" but I suspect by "closely held" you actually mean secret.

• Thanks. By "closely held" I mean steps are taken to limit access. "Secret" seems to be more declarative -- access limits are effective. – Mike Samuel Nov 1 '17 at 20:01
• I think my misconception was around whether a hash where half the input is known effectively limits access to the other half but gusto answered that it's fine. – Mike Samuel Nov 1 '17 at 20:04