For CSRF tokens, mt_rand() is ok-ish but openssl_random_pseudo_bytes() is a lot better

On the pages for rand() and uniqid(), as well as looking at the C code, they specifically state that these functions should not be used for generating secure tokens.  They tend to generate predictable values.  And the documentation for md5() states that it should not be used for password hashing.  Granted we’re not hashing passwords when creating a CSRF token, but with the tooling available shouldn’t we be using functions that are more cryptographically secure?

The goal here is the random value.  As such the hashing using hash_hmac() does not buy you a whole lot extra.  The number of possible values in a 32 byte random string is 1.1579208923731619542357098500869e+77.  That alone would seem to be enough for a CSRF prevention token.  mt_rand() returns an integer which gives you  about 4 billion possible numbers.  While that will probably protect you, the other value will offer you better protection.  There’s no sense in gambling with a smaller value if you have the ability to generate a larger value with virtually no additional cost.

So it would seem that, for generating a proper token the code that you would really need is this:

$token = base64_encode( openssl_random_pseudo_bytes(32));

The only reason for the base64_encode() call is to make sure that the value provided will not break your HTML layout.

Looks like we need to update Aura.Session to use openssl when available and fall back to mt_rand() when it’s not. Via Generating secure cross site request forgery tokens (csrf).

Are you stuck with a legacy PHP application? You should buy my book because it gives you a step-by-step guide to improving your codebase, all while keeping it running the whole time.

19 thoughts on “For CSRF tokens, mt_rand() is ok-ish but openssl_random_pseudo_bytes() is a lot better

    • See the comments in that article. It looks like OpenSSL is treated as best, mcrypt with urandom as good, and (mt_)rand as worst.

  1. A few notes about random bytes.

    It is a bit misleading to say that openssl_random_pseudo_bytes() is “better” (security-wise speaking) than any other method that relies on /dev/urandom (or the Windows equivalence on Windows). Reading straight from /dev/urandom, or fetching bytes some other way (which uses /dev/urandom) are all practically equal.

    Care should be taken to make sure to avoid those quirks when fetching random bytes. For example, openssl_random_pseudo_bytes() blocking on certain versions, /dev/uradom not available on Windows and security issues with mcrypt_create_iv() (using DEV_URANDOM) on certain versions on Windows.

  2. The csrf token can end inside an url so the proper call would be
    rtrim( strtr( base64_encode( openssl_random_pseudo_bytes(32)), ‘+/’, ‘-_’), ‘=’)

  3. Did try and post this on the original blog item, but seems it’s not happening..

    I don’t like the reliance on random numbers.
    I actually think the first suggestion of a HMAC is on the right path, but again not hashing random bytes.

    The $data argument to hash_hmac should be made up from serialised data. This should include the full uri to where the form is to be posted, session id, and any hidden values in the form ().

    This provides not only CSRF protection, but also another layer of validation to parts of the form.

    The $key parameter for the CSRF could be a site wide secret, and do away with needing to use $_SESSION at all.

  4. The result of a HMAC with known data and a secret key is unguessable. The random 32 byte string is far from perfect as it relies on $_SESSION storage.

    HMACs don’t leak data.

    Including the hidden values as part of the data to be hashed by the HMAC prevents an attacker changing those values, from when they were sent. Because the attacker can’t create a valid token for them to be accepted.

  5. Guess we’re going have to disagree, because I completely disagree with your assumption that is a “risk”.

    A HTTP POST request is just a message, and using a message authentication code seems completely logical to me, especially when the sender of the message and the receiver are one and the same (the webapp).

Leave a Reply

Your email address will not be published. Required fields are marked *