How to generate cryptographically random numbers? [duplicate] - php

Use case: the "I forgot my password" button. We can't find the user's original password because it's stored in hashed form, so the only thing to do is generate a new random password and e-mail it to him. This requires cryptographically unpredictable random numbers, for which mt_rand is not good enough, and in general we can't assume a hosting service will provide access to the operating system to install a cryptographic random number module etc. so I'm looking for a way to generate secure random numbers in PHP itself.
The solution I've come up with so far involves storing an initial seed, then for each call,
result = seed
seed = sha512(seed . mt_rand())
This is based on the security of the sha512 hash function (the mt_rand call is just to make life a little more difficult for an adversary who obtains a copy of the database).
Am I missing something, or are there better known solutions?

I strongly recommend targeting /dev/urandom on unix systems or the crypto-api on the windows platform as an entropy source for passwords.
I can't stress enough the importance of realizing hashes are NOT magical entropy increasing devices. Misusing them in this manner is no more secure than using the seed and rand() data before it had been hashed and I'm sure you recognize that is not a good idea. The seed cancels out (deterministic mt_rand()) and so there is no point at all in even including it.
People think they are being smart and clever and the result of their labor are fragile systems and devices which put the security of their systems and the security of other systems (via poor advice) in unecessary jeopardy.
Two wrongs don't make a right. A system is only as strong as its weakest part. This is not a license or excuse to accept making even more of it insecure.
Here is some PHP code to obtain a secure random 128-bit string, from this comment at php.net by Mark Seecof:
"If you need some pseudorandom bits for security or cryptographic purposes (e.g.g., random IV for block cipher, random salt for password hash) mt_rand() is a poor source. On most Unix/Linux and/or MS-Windows platforms you can get a better grade of pseudorandom bits from the OS or system library, like this:
<?php
// get 128 pseudorandom bits in a string of 16 bytes
$pr_bits = '';
// Unix/Linux platform?
$fp = #fopen('/dev/urandom','rb');
if ($fp !== FALSE) {
$pr_bits .= #fread($fp,16);
#fclose($fp);
}
// MS-Windows platform?
if (#class_exists('COM')) {
// http://msdn.microsoft.com/en-us/library/aa388176(VS.85).aspx
try {
$CAPI_Util = new COM('CAPICOM.Utilities.1');
$pr_bits .= $CAPI_Util->GetRandom(16,0);
// if we ask for binary data PHP munges it, so we
// request base64 return value. We squeeze out the
// redundancy and useless ==CRLF by hashing...
if ($pr_bits) { $pr_bits = md5($pr_bits,TRUE); }
} catch (Exception $ex) {
// echo 'Exception: ' . $ex->getMessage();
}
}
if (strlen($pr_bits) < 16) {
// do something to warn system owner that
// pseudorandom generator is missing
}
?>
NB: it is generally safe to leave both the attempt to read /dev/urandom and the attempt to access CAPICOM in your code, though each will fail silently on the other's platform. Leave them both there so your code will be more portable."

You can also consider using OpenSSL openssl_random_pseudo_bytes, it's available since PHP 5.3.
string openssl_random_pseudo_bytes ( int $length [, bool &$crypto_strong ] )
Generates a string of pseudo-random bytes, with the number of bytes determined by the length parameter.
It also indicates if a cryptographically strong algorithm was used to produce the pseudo-random bytes, and does this via the optional crypto_strong parameter. It's rare for this to be FALSE, but some systems may be broken or old.
http://www.php.net/manual/en/function.openssl-random-pseudo-bytes.php
Since PHP 7 there is also random_bytes function available
string random_bytes ( int $length )
http://php.net/manual/en/function.random-bytes.php

PHP ships with a new set of CSPRNG functions (random_bytes() and random_int()). It's trivial to turn the latter function into a string generator function:
<?php
/**
* Generate a random string, using a cryptographically secure
* pseudorandom number generator (random_int)
*
* For PHP 7, random_int is a PHP core function
* For PHP 5.x, depends on https://github.com/paragonie/random_compat
*
* #param int $length How many characters do we want?
* #param string $keyspace A string of all possible characters
* to select from
* #return string
*/
function random_str(
$length,
$keyspace = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
) {
$str = '';
$max = mb_strlen($keyspace, '8bit') - 1;
if ($max < 1) {
throw new Exception('$keyspace must be at least two characters long');
}
for ($i = 0; $i < $length; ++$i) {
$str .= $keyspace[random_int(0, $max)];
}
return $str;
}
If you need to use this in a PHP 5 project, feel free to grab a copy of random_compat, which is a polyfill for these functions.

How about something like
<?
$length = 100;
$random = substr(hash('sha512', openssl_random_pseudo_bytes(128)), 0, $length);
I just noticed its about numbers, so heres the solution for numbers:
<?
$max = 1000;
$random = (unpack('n', openssl_random_pseudo_bytes(2))[1] * time()) % $max;

Related

PHP Seeded, Deterministic, Cryptographically Secure PRNG (PseudoRandom Number Generator). Is it possible?

I'm required to create a provably-fair (deterministic & seeded) cryptographically secure (CS) random number generator in PHP. We are running PHP 5 and PHP 7 isn't really an option right now. However, I found a polyfill for PHP 7's new CS functions so I've implemented that solution (https://github.com/paragonie/random_compat).
I thought that srand() could be used to seed random_int(), but now I'm not certain if that is the case. Can a CSPRNG even be seeded? If it can be seeded, will the output be deterministic (same random result, given same seed)?
Here is my code:
require_once($_SERVER['DOCUMENT_ROOT']."/lib/assets/random_compat/lib/random.php");
$seed_a = 8138707157292429635;
$seed_b = 'JuxJ1XLnBKk7gPASR80hJfq5Ey8QWEIc8Bt';
class CSPRNG{
private static $RNGseed = 0;
public function generate_seed_a(){
return random_int(0, PHP_INT_MAX);
}
public function generate_seed_b($length = 35){
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$randomString = '';
for($i = 0; $i < $length; $i++){
$randomString .= $characters[random_int(0, strlen($characters) - 1)];
}
return $randomString;
}
public function seed($s = 0) {
if($s == 0){
$this->RNGseed = $this->generate_seed_a();
}else{
$this->RNGseed = $s;
}
srand($this->RNGseed);
}
public function generate_random_integer($min=0, $max=PHP_INT_MAX, $pad_zeros = true){
if($this->RNGseed == 0){
$this->seed();
}
$rnd_num = random_int($min, $max);
if($pad_zeros == true){
$num_digits = strlen((string)$max);
$format_str = "%0".$num_digits."d";
return sprintf($format_str, $rnd_num);
}else{
return $rnd_num;
}
}
public function drawing_numbers($seed_a, $num_of_balls = 6){
$this->seed($seed_a);
$draw_numbers = array();
for($i = 0; $i < $num_of_balls; $i++) {
$number = ($this->generate_random_integer(1, 49));
if(in_array($number, $draw_numbers)){
$i = $i-1;
}else{
array_push($draw_numbers, $number);
}
}
sort($draw_numbers);
return $draw_numbers;
}
}
$CSPRNG= new CSPRNG();
echo '<p>Seed A: '.$seed_a.'</p>';
echo '<p>Seed B: '.$seed_b.'</p>';
$hash = hash('sha1', $seed_a.$seed_b);
echo '<p>Hash: '.$hash.'</p>';
$drawNumbers = $CSPRNG->drawing_numbers($seed_a);
$draw_str = implode("-", $drawNumbers);
echo "<br>Drawing: $draw_str<br>";
When this code is run, the Drawing ($draw_str) should be the same on each run, but it is not.
To prove that the drawing is fair, a seed (Seed A) is chosen before the winning number is picked and shown. Another random number is generated as well (Seed B). Seed B is used as a salt and combined with Seed A and the result is hashed. This hash is shown to the user prior to the drawing. They would also be provided with the source code so that when the winning number is picked, both seeds are revealed. They can verify that the hash matches and everything was done fairly.
Duskwuff asks:
How do you intend to prove that the seed was chosen fairly? A suspicious user can easily claim that you picked a seed that would result in a favorable outcome for specific users, or that you revealed the seed to specific users ahead of time.
Before you investigate solutions, what exactly is the problem you are trying to solve? What is your threat model?
It sounds like you want SeedSpring (version 0.3.0 supports PHP 5.6).
$prng = new \ParagonIE\SeedSpring\SeedSpring('JuxJ1XLnBKk7gPAS');
$byte = $prng->getBytes(16);
\var_dump(bin2hex($byte));
This should always return:
string(32) "76482c186f7c5d1cb3f895e044e3c649"
The numbers should be unbiased, but since it's based off a pre-shared seed, it is not, by strict definition, cryptographically secure.
Keep in mind that SeedSpring was created as a toy implementation/proof of concept rather than an official Paragon Initiative Enterprises open source security solution, so feel free to fork it and tweak it to suit your purposes. (I doubt our branch will ever reach a "stable 1.0.0 release").
(Also, if you're going to accept/award the bounty to any of these answers, Aaron Toponce's answer is more correct. Encrypting the nonce with ECB mode is more performant than encrypting a long stream of NUL bytes with AES-CTR, for approximately the same security benefit. This is one of the extremely rare occasions that ECB mode is okay.)
First, you shouldn't be implementing your own userspace CSPRNG. The operating system you have PHP 5 installed on already ships a CSPRNG, and you should be using that for all your randomness, unless you know you can use it, or performance is a concern. You should be using random_int(), random_bytes(), or openssl_random_pseudo_bytes().
However, if you must implement a userspace CSPRNG, then this can be done by simply using an AES library (E.G.: libsodium), and encrypting a counter. Psuedocode would be:
Uint-128 n = 0;
while true:
output = AES-ECB(key, n);
n++;
They AES key, in this case, needs sufficient entropy to withstand a sophisticated attack, or the security of your userspace CSPRNG falls apart, of course. The key could be the bcrypt() of a user-supplied password.
Provided your counter represented as a 128-bit unsigned integer is always unique, you will always get a unique output every time the generator is "seeded" with a new counter. If it's seeded with a previously used counter, but a different key, then the output will also be different. The best case scenario, would be a changing key and a changing counter every time the generator is called.
You may be tempted to use high precision timestamp, such as using microsecond accuracy, in your counter. This is fine, except you run the risk of someone or something manipulating the system clock. As such, if the clock can be manipulated, then the CSPRNG generator can be compromised. You're best off providing a new key every time you call the generator, and start encrypting with a 128-bit zero.
Also, notice that we're using ECB mode with AES. Don't freak out. ECB has problems with maintaining structure in the ciphertext that the plaintext provides. In general terms, you should not use ECB mode. However, with 128-bits of data, you will only be encrypting a single ECB block, so there will be no leak of structured data. ECB is preferred over CTR for a userspace CSPRNG, as you don't have to keep track of a key, a counter object, and the data to be encrypted. Only a key and the data are needed. Just make sure you are never encrypting more than 128-bits of data, and you'll never need more than 1 block.
Can a CSPRNG even be seeded?
Yes, and it should always be seeded. If you look at your GNU/Linux operating system, you'll likely notice a file in /var/lib/urandom/random-seed. When the operating system shuts down, it creates that file from the CSPRNG. On next boot, this file is used to seed the kernelspace CSPRNG to prevent reusing previous state of the generator. On every shutdown, that file should change.
If it can be seeded, will the output be deterministic (same random result, given same seed)?
Yes. Provided the same seed, key, etc., the output is deterministic, so the output will be the same. If one of your variables changes, then the output will be different. This is why on each call of the generator should be rekeyed.

Cryptographically Secure Random String Function

Goal: Find the most cryptographically secure random string generator. Using Alphabetic, numeric and if possible special characters in the string.
I have been reading on here and other places, but I still hear so many different answers/opinions. Can people who are up to date and knowledgeable about security and cryptography chime in here.
The following functions will be used to generate a 8 character random password and also generate a 128 character random token.
Function 1:
/**
* Used for generating a random string.
*
* #param int $_Length The lengtyh of the random string.
* #return string The random string.
*/
function gfRandomString($_Length) {
$alphabet = "abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUWXYZ0123456789";
$pass = array(); //remember to declare $pass as an array
$alphaLength = strlen($alphabet) - 1; //put the length -1 in cache
for ($i = 0; $i < $_Length; $i++) {
$n = rand(0, $alphaLength);
$pass[] = $alphabet[$n];
}
return implode($pass); //turn the array into a string
}
Function 2:
The PHP.net docs say:
crypto_strong:
If passed into the function, this will hold a boolean value that determines if the algorithm used was "cryptographically strong", e.g., safe for usage with GPG, passwords, etc. TRUE if it did, otherwise FALSE.
So is that based on the the server? If I test it once, and it is able to generate a crypto_strong string, will it always be able to? Or would I need to check each time and create a loop until it generates a crypto_strong string.
/**
* Used for generating a random string.
*
* #param int $_Length The length of bits.
* #return string The random string.
*/
function gfSecureString($_Length) {
$Str = bin2hex(openssl_random_pseudo_bytes($_Length));
return $Str;
}
I welcome any suggestions to improve the cryptographic strength.
So you want to securely generate random strings in PHP. Neither of the two functions in the question will give you what you want, but the rand() solution is the worst of the two. rand() is not secure, while bin2hex(openssl_random_pseudo_bytes()) limits your output character set.
Also, openssl_random_pseudo_bytes() might not be reliable under extreme conditions or exotic setups.
From what I understand, crypto_strong will only be set to false if RAND_pseudo_bytes() fails to return any data. If OpenSSL is not seeded when it's invoked, it will silently return weak (and possibly predictable) pseudorandom bytes. You have no way, from PHP, to determine if it's random either.
How to generate secure random strings today
If you want a solution that has received substantial review for PHP 5.x, use RandomLib.
$factory = new RandomLib\Factory;
$generator = $factory->getMediumStrengthGenerator();
$randomPassword = $generator->generateString(20, $alphabet);
Alternative solutions
If you'd rather not use RandomLib (even if, purely, because you want to have alternative options available), you can also use random_int() when PHP 7 comes out. If you can't wait until then, take a look at our random_compat project.
If you happen to be using the cryptography library, libsodium, you can generate random numbers like so:
/**
* Depends on the PECL extension libsodium
*
* #link https://stackoverflow.com/a/31498051/2224584
*
* #param int $length How long should the string be?
* #param string $alphabet Contains all of the allowed characters
*
* #return string
*/
function sodium_random_str($length, $alphabet = 'abcdefghijklmnopqrstuvwxyz')
{
$buf = '';
$alphabetSize = strlen($alphabet);
for ($i = 0; $i < $length; ++$i) {
$buf .= $alphabet[\Sodium\randombytes_uniform($alphabetSize)];
}
return $buf;
}
See this answer for example code that uses random_int(). I'd rather not duplicate the effort of updating the code in the future, should the need ever arise.
openssl_random_pseudo_bytes has a pretty large chance of being a cryptographically secure generator, while rand certainly isn't. However, it will only return binary data which you revert to hexadecimals. Hexadecimals are not enough to generate a password string. Neither function includes special characters as you seem to require.
So neither one of the code snippets fits your purpose.

Audit my PHP function to generate cryptography secure passwords [closed]

Closed. This question is off-topic. It is not currently accepting answers.
Want to improve this question? Update the question so it's on-topic for Stack Overflow.
Closed 9 years ago.
Improve this question
I wrote a function in PHP to generate a random password (only 0-9a-zA-Z) for my app. The resulting password must be cryptography secure, and as random as possible. I.E. the passwords are sensitive.
The big trick I do is shuffle $possible characters everytime, so even if mt_rand() is not truely random, it should not be predictable.
Any recommended changes or security issues in my function? Is using openssl_random_pseudo_bytes() instead of mt_rand() really going to make the algorithm stronger and more secure?
public function generate_random($length = 15) {
$random = "";
$possible = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
$possible = str_shuffle($possible);
$maxlength = strlen($possible);
if ($length > $maxlength) {
$length = $maxlength;
}
$i = 0;
while ($i < $length) {
$random .= substr($possible, mt_rand(0, $maxlength-1), 1);
$i++;
}
return $random;
}
Thanks.
To generate something really random, you have to use the random source of the operating system. After reading from this source, you need to encode the bytes to an alphabet of your choice.
An easy conversion is base64 encoding, but this will include '+' and '/' characters. To only get characters from the alphabet and digits, you need a base62 encoding, or you can simply replace those characters with other characters.
/**
* Generates a random string of a given length, using the random source of
* the operating system. The string contains only characters of this
* alphabet: +/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
* #param int $length Number of characters the string should have.
* #return string A random base64 encoded string.
*/
function generateRandomBase64String($length)
{
if (!defined('MCRYPT_DEV_URANDOM')) die('The MCRYPT_DEV_URANDOM source is required (PHP 5.3).');
// Generate random bytes, using the operating system's random source.
// Since PHP 5.3 this also uses the random source on a Windows server.
// Unlike /dev/random, the /dev/urandom does not block the server, if
// there is not enough entropy available.
$binaryLength = (int)($length * 3 / 4 + 1);
$randomBinaryString = mcrypt_create_iv($binaryLength, MCRYPT_DEV_URANDOM);
$randomBase64String = base64_encode($randomBinaryString);
return substr($randomBase64String, 0, $length);
}
The code is part of this class, have a look at the function generateRandomBase62String() for a complete example.
Adding pseudo-randomness to a pseudo-random string won't increase the entropy at all. The only way is to use a better random number generator.
Possible duplicate: Secure random number generation in PHP
If by cryptographically secure, you mean you intend to use the password as a key somewhere, it is important to realize that your space isn't nearly large enough. 15 characters with 62 possibilities each is less than 90 bits, which is about as strong as RSA-1024, and is considered unsafe today.
You should, however, not be doing such a thing in the first place. If you do require a human-readable string that maps to something that can be used as a cryptographic key, use something like PBKDF2.
Lastly, shuffling the string does not increase effective randomness. As long as you do not use it directly as a key, your function is fine - remember to first check the output against a dictionary of common passwords
(like a password list from a password cracker) and reject those.
This is not an answer of your question but it seems feasible to use this function to generate random random password containing only (0-9a-z,A-Z)
$password = base64_encode(openssl_random_pseudo_bytes(20, $strong));
$newstr = preg_replace('/[^a-zA-Z0-9\']/', '', $password);
echo $newstr;

Big random number generation

I need a big (like, say, 128-bit big) random number generator in PHP. I was thinking in storing this number in a string as hexadecimal.
Note that this is meant for a login system that mentioned the need for a "random" number, so I'm guessing I really need it to be "random-enough" (because I know pseudo-random is never truly random).
The algorithm I was thinking was generating the number one hex digit at a time, then concatenating it all. Like this:
$random = '';
for ($i = 0; $i < 32; ++$i) {
$digit = rand(0, 15);
$random .= ($digit < 10 ? $digit : ($digit - 10 + 'a'));
}
return $random;
Can I trust this function to return good pseudo-random numbers or am I messing with something I really shouldn't?
Try:
for ($str = '', $i = 0; $i < $len; $i++) {
$str .= dechex(mt_rand(0, 15));
}
I asked this question several years ago and, since then, my knowledge of this topic has improved.
First of all, I mentioned I wanted random numbers for a login system. Login systems are security mechanisms.
This means that any random number generators that the login system relies on should be cryptographically secure.
PHP's rand and mt_rand are not cryptographically secure.
In these cases, it's best to be safe than sorry. There are random number generators designed specifically to be secure, notably openssl_random_pseudo_bytes (which is unfortunately not always available -- you must enable the OpenSSL extension for it to work). On *NIX systems (such as Linux), bytes read from /dev/urandom can be used as well.
Unfortunately (for the purposes of this question), both of these approaches return binary data instead of hexadecimal. Fortunately, PHP already has a function to fix this for us, bin2hex, which works for strings of any length.
So here's how the code would look like:
function generate_secure_random_hex_string($length) {
// $length should be an even, non-negative number.
// Because each byte is represented as two hex digits, we'll need the binary
// string to be half as long as the hex string.
$binary_length = $length / 2;
// First, we'll generate the random binary string.
$random_result = openssl_random_pseudo_bytes($binary_length, $cstrong);
if (!$cstrong) {
// The result is not cryptographically secure. Abort.
// die() is just a placeholder.
// There might be better ways to handle this error.
die();
}
//Convert the result to hexadecimal
return bin2hex($random_result);
}
// Example:
echo generate_secure_random_hex_string(32);
I've often seen this handled in login systems by just doing something like:
$salt = "big string of random stuff"; // you can generate this once like above
$token = md5( $salt . time()); // this will be your "unique" number
MD5 hashes can have collisions, but this is pretty effective and very simple.
As of PHP 5.3:
function getRandomHex($num_bytes=4) {
return bin2hex(openssl_random_pseudo_bytes($num_bytes));
}
For your example of 128 bits:
$rand128 = getRandomHex(16);

md5(uniqid) makes sense for random unique tokens?

I want to create a token generator that generates tokens that cannot be guessed by the user and that are still unique (to be used for password resets and confirmation codes).
I often see this code; does it make sense?
md5(uniqid(rand(), true));
According to a comment uniqid($prefix, $moreEntopy = true) yields
first 8 hex chars = Unixtime, last 5 hex chars = microseconds.
I don't know how the $prefix-parameter is handled..
So if you don't set the $moreEntopy flag to true, it gives a predictable outcome.
QUESTION: But if we use uniqid with $moreEntopy, what does hashing it with md5 buy us? Is it better than:
md5(mt_rand())
edit1: I will store this token in an database column with a unique index, so I will detect columns. Might be of interest/
rand() is a security hazard and should never be used to generate a security token: rand() vs mt_rand() (Look at the "static" like images). But neither of these methods of generating random numbers is cryptographically secure. To generate secure secerts an application will needs to access a CSPRNG provided by the platform, operating system or hardware module.
In a web application a good source for secure secrets is non-blocking access to an entropy pool such as /dev/urandom. As of PHP 5.3, PHP applications can use openssl_random_pseudo_bytes(), and the Openssl library will choose the best entropy source based on your operating system, under Linux this means the application will use /dev/urandom. This code snip from Scott is pretty good:
function crypto_rand_secure($min, $max) {
$range = $max - $min;
if ($range < 0) return $min; // not so random...
$log = log($range, 2);
$bytes = (int) ($log / 8) + 1; // length in bytes
$bits = (int) $log + 1; // length in bits
$filter = (int) (1 << $bits) - 1; // set all lower bits to 1
do {
$rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));
$rnd = $rnd & $filter; // discard irrelevant bits
} while ($rnd >= $range);
return $min + $rnd;
}
function getToken($length=32){
$token = "";
$codeAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$codeAlphabet.= "abcdefghijklmnopqrstuvwxyz";
$codeAlphabet.= "0123456789";
for($i=0;$i<$length;$i++){
$token .= $codeAlphabet[crypto_rand_secure(0,strlen($codeAlphabet))];
}
return $token;
}
This is a copy of another question I found that was asked a few months before this one. Here is a link to the question and my answer: https://stackoverflow.com/a/13733588/1698153.
I do not agree with the accepted answer. According to PHPs own website "[uniqid] does not generate cryptographically secure tokens, in fact without being passed any additional parameters the return value is little different from microtime(). If you need to generate cryptographically secure tokens use openssl_random_pseudo_bytes()."
I do not think the answer could be clearer than this, uniqid is not secure.
I know the question is old, but it shows up in Google, so...
As others said, rand(), mt_rand() or uniqid() will not guarantee you uniqueness... even openssl_random_pseudo_bytes() should not be used, since it uses deprecated features of OpenSSL.
What you should use to generate random hash (same as md5) is random_bytes() (introduced in PHP7). To generate hash with same length as MD5:
bin2hex(random_bytes(16));
If you are using PHP 5.x you can get this function by including random_compat library.
Define "unique". If you mean that two tokens cannot have the same value, then hashing isn't enough - it should be backed with a uniqueness test. The fact that you supply the hash algorithm with unique inputs does not guarantee unique outputs.
To answer your question, the problem is you can't have a generator that is guaranteed random and unique as random by itself, i.e., md5(mt_rand()) can lead to duplicates. What you want is "random appearing" unique values. uniqid gives the unique id, rand() affixes a random number making it even harder to guess, md5 masks the result to make it yet even harder to guess. Nothing is unguessable. We just need to make it so hard that they wouldn't even want to try.
I ran into an interesting idea a couple of years ago.
Storing two hash values in the datebase, one generated with md5($a) and the other with sha($a). Then chek if both the values are corect. Point is, if the attacker broke your md5(), he cannot break your md5 AND sha in the near future.
Problem is: how can that concept be used with the token generating needed for your problem?
First, the scope of this kind of procedure is to create a key/hash/code, that will be unique for one given database. It is impossible to create something unique for the whole world at a given moment.
That being said, you should create a plain, visible string, using a custom alphabet, and checking the created code against your database (table).
If that string is unique, then you apply a md5() to it and that can't be guessed by anyone or any script.
I know that if you dig deep into the theory of cryptographic generation you can find a lot of explanation about this kind of code generation, but when you put it to real usage it's really not that complicated.
Here's the code I use to generate a simple 10 digit unique code.
$alphabet = "aA1!bB2#cC3#dD5%eE6^fF7&gG8*hH9(iI0)jJ4-kK=+lL[mM]nN{oO}pP\qQ/rR,sS.tT?uUvV>xX~yY|zZ`wW$";
$code = '';
$alplhaLenght = strlen($alphabet )-1;
for ($i = 1; $i <= 10; $i++) {
$n = rand(1, $alplhaLenght );
$code .= $alphabet [$n];
}
And here are some generated codes, although you can run it yourself to see it work:
SpQ0T0tyO%
Uwn[MU][.
D|[ROt+Cd#
O6I|w38TRe
Of course, there can be a lot of "improvements" that can be applied to it, to make it more "complicated", but if you apply a md5() to this, it'll become, let's say "unguessable" . :)
MD5 is a decent algorithm for producing data dependent IDs. But in case you have more than one item which has the same bitstream (content), you will be producing two similar MD5 "ids".
So if you are just applying it to a rand() function, which is guaranteed not to create the same number twice, you are quite safe.
But for a stronger distribution of keys, I'd personally use SHA1 or SHAx etc'... but you will still have the problem of similar data leads to similar keys.

Categories