91

In order to generate a 32 character token for access to our API we currently use:

$token = md5(uniqid(mt_rand(), true));

I have read that this method is not cryptographically secure as it's based on the system clock, and that openssl_random_pseudo_bytes would be a better solution as it would be harder to predict.

If this is the case, what would the equivalent code look like?

I presume something like this, but I don't know if this is right...

$token = md5(openssl_random_pseudo_bytes(32));

Also what length makes sense that I should pass to the function?

fire
  • 21,383
  • 17
  • 79
  • 114
  • 5
    Why md5 it though? Just convert the bytestream to hex: you're getting 32 bytes back from openssl_random_pseudo_bytes(), render each of those bytes as a hexvalue with bin2hex() as shown in the [PHP docs](http://www.php.net/manual/en/function.openssl-random-pseudo-bytes.php) examples – Mark Baker Sep 16 '13 at 14:54
  • I only want 32 characters? How would I do that? – fire Sep 16 '13 at 14:55
  • 11
    `md5()` generates a 32 character string, but has only 128bits worth of data in it. `openssl_random_pseudo_bytes()` returns true binary data, so has 32*8 = 256 bits of randomness. By stuffing your 32-byte random string through md5, you're effectively cutting its uniqueness by a massive amount. – Marc B Sep 16 '13 at 14:56
  • 1
    So is `$token = bin2hex(openssl_random_pseudo_bytes(16));` sufficient or do I need a loop of 16 iterations passing 1 as the length and appending in hex to a string? – fire Sep 16 '13 at 15:32
  • The final solution with 16 bytes converted to hex is correct. But you should not really rely on OpenSSL here [#1](https://externals.io/message/103345) [#2](https://github.com/paragonie/random_compat/issues/96) [#3](https://github.com/paragonie/random_compat/issues/5). As soon as you’re on PHP 7.0+, there’s really no excuse anymore to use it. Instead of OpenSSL and hex encoding, try [`Random::alphanumericString($length)`](https://github.com/delight-im/PHP-Random), which fits about 2 billion times as much “entropy” into those 16 characters. – caw Nov 22 '19 at 18:25

5 Answers5

201

Here is the correct solution:

$token = bin2hex(openssl_random_pseudo_bytes(16));

# or in php7
$token = bin2hex(random_bytes(16));
fire
  • 21,383
  • 17
  • 79
  • 114
  • 1
    I have added another answer on how you can generate a unique token using openssl_random_pseudo_bytes with CryptoLib. This allows you to generate tokens with all characters as opposed to just 0-9, A-F. – mjsa Dec 29 '14 at 00:46
  • 4
    PHP 5.x support for random_bytes() and random_int() https://github.com/paragonie/random_compat – MTK Apr 14 '17 at 11:46
  • 6
    You might also want `bin2hex(random_bytes($length/2))` because you can have 2 hex characters for every byte so the number of characters will always be double `$length` without it – A Friend Dec 13 '18 at 00:12
  • 4
    @AFriend Agreed. If you want to create a function that produces a string of `$length` number of characters (rather than caring about the byte size) you will want to pass `random_bytes($length/2)`. – BadHorsie Mar 07 '19 at 13:15
10

If you want to use openssl_random_pseudo_bytes it is best to use CrytoLib's implementation, this will allow you to generate all alphanumeric characters, sticking bin2hex around openssl_random_pseudo_bytes will just result in you getting A-F (caps) and numbers.

Replace path/to/ with where you put the cryptolib.php file (you can find it on GitHub at: https://github.com/IcyApril/CryptoLib)

<?php
  require_once('path/to/cryptolib.php');
  $token = IcyApril\CryptoLib::randomString(16);
?>

The full CryptoLib documentation is available at: https://cryptolib.ju.je/. It covers a lot of other random methods, cascading encryption and hash generation and validation; but it's there if you need it.

llui85
  • 177
  • 2
  • 14
mjsa
  • 4,221
  • 1
  • 25
  • 35
  • For the same number of bytes: `openssl_random_pseudo_bytes(16)` each byte can have any value (0-255), whereas `randomString(16)` each byte can only have a-z A-z 0-9 which is 62 combinations per byte. Yup randomString is better *per length of string*, as bin2hex is inefficient encoding (50%); better to use `base64_encode(openssl_random_pseudo_bytes(16))` for a shorter string and an exact known number of bytes of security (16 in this case but alter as necessary) – Rodney Nov 30 '18 at 15:01
  • 1
    @Rodney Bear in mind base 64 will also produce a string containing `+`, `/` and `=` characters, so if you're trying to generate a user-friendly token (e.g. an API key) it's not ideal. – BadHorsie Mar 07 '19 at 13:27
9

If you have a cryptographically secure random number generator, you don't need to hash it's output. In fact you don't want to. Just use

$token  = openssl_random_pseudo_bytes($BYTES,true)

Where $BYTES is however many bytes of data you want. MD5 has a 128bit hash, so 16 bytes will do.

As a side note, none of the functions you call in your original code are cryptographically safe, most are harmful enough that using just one would break be insecure even if combined with secure other functions. MD5 has security issues(though for this application they may not be relevant). Uniqid not just doesn't generate cryptographically random bytes by default (since it uses the system clock), the added entropy you pass in is combined using a linear congruent generator, which is not cryptographically secure. In fact, it probably means one could guess all your API keys given access to a few of them even if they had no idea the value of your server clock. Finally, mt_rand(), what you use as the extra entropy, is not a secure random number generator either.

imichaelmiers
  • 3,449
  • 2
  • 19
  • 25
  • 27
    The second argument to openssl_random_pseudo_bytes() should be a variable passed by reference. After openssl_random_pseudo_bytes, that variable will be true or false if openssl was able to use a cryptographically strong algorithm. It is not used to tell openssl to use cryptographically strong algorithm, which it will try to do anyways. – Jonathan Amend Dec 18 '13 at 16:54
0

Another option is using RandomLib from ircmaxell (https://github.com/ircmaxell/RandomLib)

Install: composer require ircmaxell/random-lib

Example medium strength

$factory = new Factory();
$factory->getMediumStrengthGenerator()->generateString(32);
MacroMan
  • 2,335
  • 1
  • 27
  • 36
-1

Reliable passwords You can only make from ascii characters a-zA-Z and numbers 0-9. To do that best way is using only cryptographically secure methods, like random_int() or random_bytes() from PHP7. Rest functions as base64_encode() You can use only as support functions to make reliability of string and change it to ASCII characters.

mt_rand() is not secure and is very old. From any string You must use random_int(). From binary string You should use base64_encode() to make binary string reliable or bin2hex, but then You will cut byte only to 16 positions (values). See my implementation of this functions. It uses all languages.