While working on my current project I was needed to generate a unique random string for an email confirmation. It’s a pretty common task and it has a bunch of simple solutions.
But I wanted it to be really random and flexible. I have ended with a helper with static functions which I want to share.
The problem
The problem is self-explanatory. As a PHP developer, we constantly need some uuid or random string to be generated for our need. It’s a common and simple task with several solutions already described on SO. But it’s also could be tricky. It’s because that not all suggested solutions are cryptographically secure and not all of them really unique and unpredictable.
Also, since new PHP versions were developed, some better new functions were added to the language. And some were deprecated and not a “good practice” no more.
That’s why in order to find the best and robust solution I have searched in different places. And I did helper class with static methods in the end.
Static methods are not good practice
Since the use of static methods stands in the same row as the use of Singletons and global constants, I would say – it depends. Depend on common sense and in a way how it would be used. If project architecture development process built on strict rules about where each class shall be placed in the project directory tree and where classes from each place could be used and where not – it’s fine to use a bit of comfortable static methods. Especially if these methods provide only common Infrastructure functionality and nothing bounded to Domain.
In my case, I build projects based on DDD in the core and Hexagonal architecture (some part of it) around the core. And in my Infrastructure, I have two static helpers for a random string, salt, and token generation. One of them I’m going to share.
The code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
final class RandomGenerator { /** * @param int $length * @return string */ public static function getRandomString(int $length = 32): ?string { if (!null === $length || (int)$length <= 8) { $length = 32; } if (function_exists('random_bytes')) { return static::generateRandomBytes($length); } if (function_exists('openssl_random_pseudo_bytes')) { return static::generateOpensslRandomPseudoBytes($length); } if (function_exists('random_int')) { return static::generateByRandomInt($length); } if (function_exists('mt_rand')) { return static::generateMtRand($length); } if (function_exists('mcrypt_create_iv')) { return static::generateMcryptIV($length); } } /** * @param int $length * @return bool|string */ private static function generateRandomBytes(int $length) { return substr(bin2hex(random_bytes($length)), -$length, $length); } /** * @param int $length * @return bool|string */ private static function generateOpensslRandomPseudoBytes(int $length) { $random = openssl_random_pseudo_bytes($length, $isSourceStrong); if (false === $isSourceStrong || false === $random) { throw new \RuntimeException('IV generation failed'); } return substr(bin2hex($random), -$length, $length); } /** * does not generate cryptographically secure values * @param int $length * @return bool|string */ private static function generateMtRand(int $length) { return substr(md5(uniqid(mt_rand(), true)), -$length, $length); } /** * @deprecated * @param int $length * @return bool|string */ private static function generateMcryptIV(int $length) { $randomM = mcrypt_create_iv($length, MCRYPT_DEV_RANDOM); if (false === $randomM) { throw new \RuntimeException('IV generation failed'); } return substr(bin2hex($randomM), -$length, $length); } /** * @param int $length * @param string $keySpace * @return string */ private static function generateByRandomInt(int $length, string $keySpace = '') { if (!$keySpace) { $keySpace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; } $str = ''; $max = mb_strlen($keySpace, '8bit') - 1; for ($i = 0; $i < $length; ++$i) { $str .= $keySpace[random_int(0, $max)]; } return $str; } /** * debug * @param int $length * @return array */ public static function getRandomStrings(int $length = 32): array { $random = []; if (!null === $length || (int)$length <= 8) { $length = 32; } if (function_exists('random_bytes')) { $random['random_bytes'] = static::generateRandomBytes($length); } if (function_exists('openssl_random_pseudo_bytes')) { $random['openssl_random_pseudo_bytes'] = static::generateOpensslRandomPseudoBytes($length); } if (function_exists('random_int')) { $random['random_int'] = static::generateByRandomInt($length); } if (function_exists('mt_rand')) { $random['mt_rand'] = static::generateMtRand($length); } if (function_exists('mcrypt_create_iv')) { $random['mcrypt_create_iv'] = static::generateMcryptIV($length); } return $random; } /** * @param int $length * @return bool|string */ public static function getSalt(int $length = 44) { return substr( str_replace( base64_encode(hex2bin(static::getRandomString(32))), '+', '.'), 0, $length); } } |
In order to come up with this solution I have investigated following sources:
First find: https://stackoverflow.com/questions/2088969/generating-confirmation-code-for-an-email-confirmation
The most discussable one: https://stackoverflow.com/questions/4356289/php-random-string-generator
And the git hub repository of the “phpinspections” plugin for PhpStorm: https://github.com/kalessil/phpinspectionsea/blob/master/docs/security.md#cryptographically-secure-randomness
Why it’s robust? Because it uses several available solutions to get a random string with the desired length. If some of the solutions will not be available due to lack of installed PHP extensions or because of PHP version – the next one will be executed.
And method called getGeneratedStrings() made for purpose of debugging, to get know which methods are available.
random_bytes() have been added in PHP 7 and it’s the most reliable method for a generation at the moment.
openssl_random_pseudo_bytes() is another good alternative and usually available on all Linux based systems with OpenSSL installed.
A method based on random_int() also available since PHP 7. I have added it because it provides a way to set range of characters for a random string as a parameter.
mt_rand() in combination with uniqid() is the less reliable one, but it’s simple and will work if previous methods are not available.
Mcrypt is deprecated but included here intentionally which allows using this helper class with older PHP versions.
That’s all. I hope it will be useful for somebody. Don’t hesitate to add comments with suggestions and critic.