3

To generate a secure URL-safe hash for password reset or email activation, which one of these functions would be more secure, bin2hex or base64_encode in the following application?

1. Generate token (using one of these):

$token = bin2hex(openssl_random_pseudo_bytes(32));
$token = strtr(base64_encode(openssl_random_pseudo_bytes(64)), "+/=", "XXX");

2. Store in database:

$token_hash = password_hash($token, PASSWORD_BCRYPT);
INSERT INTO hash_table SET token_hash='$token_hash', series='X'

3. Validate token:

SELECT token_hash FROM hash_table WHERE series='X'
if (password_verify($hash_from_url,$token_hash)) {
  // Success
}

I noticed that when $hash_from_url is more than 72 characters, it doesn't make a difference anymore.

But wouldn't it be better to use base64, to get 61 different characters rather than bin2hex that uses only 16, assuming I use a larger number of random bytes, for an equally long string, that is 72 characters of length?

Niclas
  • 1,362
  • 1
  • 11
  • 24

1 Answers1

3

The encoding itself doesn't actually change anything in regard of security.

As you already pointed out, bin2hex() will return longer strings as base64_encode() for the same number of bytes, because of the smaller base of possible characters. That means, if the length of the token is given, base64 encoding produces tokens with more entropy and is therefore better suited.

If your tokens are strong enough (min 20 characters a..z, A..Z, 0..9), you can use a simple hash function (e.g. SHA-256) without salting and keystretching instead of password_hash. This way you can create searchable hashes in the database.

Some time ago, I wrote a small class to generate base62 tokens of a certain length for password resets, maybe you want to have a look at the code.

martinstoeckli
  • 23,430
  • 6
  • 56
  • 87
  • Thanks, I thought it was more secure to NOT store the hash that was emailed to the user, but to instead store a series identifier, and then compare the user supplied hash with the encrypted hash using password_hash? Why would I want a searchable hash? Isn't that unsecure due to timing attacks? – Niclas Nov 03 '16 at 08:26
  • @Niclas - Good questions. Only to clarify, you do not send the hash to the user, instead the link should contain the original token. A timing attack doesn't work then, because your server application calculates the hash again and searches for this hash. The hash doesn't allow to make conclusions about similarities of two original tokens. The problem of the identifier is, that you have to store it somewhere too, and you have to send it with the link. This allows for shorter tokens (stored with key-stretching), but the URL grows because of the included identifier. – martinstoeckli Nov 03 '16 at 08:35
  • Thanks again, I'm probably mixing up the words token with hash in my example, which makes it a bit confusing. I will check your class, and also read more about key stretching. I have no problem with long tokens, and I feel they are more secure, but maybe 128 chars base62 is overkill? I hide the reset link behind HTML, but maybe there's a problem if someone if reading the plain version, and URL's break. But should I sacrifice security for lesser email clients? – Niclas Nov 03 '16 at 08:40
  • @Niclas - From a certain point, making tokens longer does not improve security anymore. If an attacker can brute-force 100Giga SHA256 per second, we can still expect about 1E17 years for a random 20 character token till he finds a match (a number with 17 zeros). With 24 characters you are on the safe side. – martinstoeckli Nov 03 '16 at 08:46
  • Is http://stackoverflow.com/questions/1354999/keep-me-logged-in-the-best-approach the method that you mean, by storing a searchable hash in the database? Actually I'm using this for keep me logged in, but would it be practical for a reset password URL? – Niclas Nov 03 '16 at 09:34
  • @Niclas - The linked [answer](http://stackoverflow.com/a/17266448/575765) could be used to create password reset tokens, but there are two problems. 1) The link would become much larger than with our token, because of the essential MAC (which is no problem for a cookie). 2) The token is stored plaintext in the database which is a (somewhat theoretical) weakness, because it relies on the fact that the SECRET_KEY remains secret. If the token is hashed as described above, you can publish your code without risking a breach. The mentioned 128 bit tokens are equal to 22 character base62 tokens. – martinstoeckli Nov 03 '16 at 16:10