I don’t talk much about security. This is mostly because it’s such a moving target. I’m also horrified that I might give bad advice and someone will be hacked because of me.
But in researching the second edition for the IBM i Programmer’s Guide to PHP Jeff and I decided to include a chapter on security since we really didn’t talk much about it in the first edition. I’m talking about cross site request forgeries right now and I wanted to make sure that what I was going to suggest would not break the internet in some way.
I did some Google searching to see what other people were recommending. Almost all of the pages I found for generating a CSRF token use code like this
$token = md5(uniqid(rand(), true));
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? Like this?
$token = hash_hmac( 'sha512', openssl_random_pseudo_bytes(32), openssl_random_pseudo_bytes(16) );
Am I missing something or wouldn’t something like this be a whole lot better?
padraicb validated my thought on the matter. 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.