Does size matter when choosing the right algorithm to use for a session hash.
I recently read this article and it suggested using whirlpool to create a hash for session id. Whirlpool generates a 128 character hash string, is this too large?
The plan is to store the session hash in a db. Is there much of a difference between maybe using 64 character field (sha256), 96 character field (sha384) or 128 character field (whirlpool)? One of the initial arguments made for whirlpool was the speed vs other algorithms but looking at the speed results sha384 doesn't fair too badly.
There is the option truncate the hash to make it smaller than 128 characters.
I did modify the original code snippet, to allow changing of the algorithm based of the needs.
Update: There was some discussion about string being hashed, so I've included the code.
function generateUniqueId($maxLength = null) {
$entropy = '';
// try ssl first
if (function_exists('openssl_random_pseudo_bytes')) {
$entropy = openssl_random_pseudo_bytes(64, $strong);
// skip ssl since it wasn't using the strong algo
if($strong !== true) {
$entropy = '';
}
}
// add some basic mt_rand/uniqid combo
$entropy .= uniqid(mt_rand(), true);
// try to read from the windows RNG
if (class_exists('COM')) {
try {
$com = new COM('CAPICOM.Utilities.1');
$entropy .= base64_decode($com->GetRandom(64, 0));
} catch (Exception $ex) {
}
}
// try to read from the unix RNG
if (is_readable('/dev/urandom')) {
$h = fopen('/dev/urandom', 'rb');
$entropy .= fread($h, 64);
fclose($h);
}
// create hash
$hash = hash('whirlpool', $entropy);
// truncate hash if max length imposed
if ($maxLength) {
return substr($hash, 0, $maxLength);
}
return $hash;
}
The time taken to create the hash is not important, and as long as your database is properly indexed, the storage method should not be a major factor either.
However, the hash has to be transmitted with the client's request every time, frequently as a cookie. Large cookies can add a small amount of additional time to each request. See Yahoo!'s page performance best practices for more information. Smaller cookies, thus a smaller hash, have benefits.
Overall, large hash functions are probably not justified. For their limited scope, good old md5 and sha1 are probably just fine as the source behind a session token.
Yes, size matters.
If it's too short, you run the risk of collisions. You also make it practical for an attacker to find someone else's session by brute-force attack.
Being too long matters less, but every byte of the session ID has to be transferred from the browser to the server with every request, so if you're really optimising things, you may not want an ID that's too long.
You don't have to use all the bits of a hash algorithm, though - there's nothing stopping you from using something like Whirlpool, then only taking the first 128 bits (32 characters in hex). Practically speaking, 128 bits is a good lower bound on length, too.
As erickson points out, though, using a hash is a bit odd. Unless you have at least as much entropy as input as the length of the ID you're using, you're vulnerable to attacks that guess the input to your hash.
The article times out when I try to read it, but I can't think of a good reason to use a hash as a session identifier. Session identifiers should be unpredictable; given the title of the article, it sounds like the authors acknowledge that principle. Then, why not use a cryptographic random number generator to produce session identifiers?
A hash takes input, and if that input is predictable, so is the hash, and that's bad.
SHA1 or MD5 is probably enough for your needs. In practice, the probability of a collision is so small that it will likely never happen.
Ultimately, though, it all depends upon your required level of security. Do also keep in mind that longer hashes are both more expensive to compute and require more storage space.
Related
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.
The standard way to create a cryptographically secure token using PHP seems to be:
$token = bin2hex(openssl_random_pseudo_bytes(16));
I understand if you're using Linux (which I always do) because this uses /dev/urandom — which is changed according all the many things that go on in the operating system —it makes it nigh impossible to predict.
My function is more like this so I can do it by char length rather than bit length (though I don't really ever use it, see below):
function token($charLength = 32) {
// Each byte produces 2 hexadecimal characters so bit length should be half the char length
$bitLength = $charLength / 2;
// Generate token
$token = bin2hex(openssl_random_pseudo_bytes($bitLength));
return $token;
}
Is it the unpredictability that makes it secure? I can't help thinking it's less secure because the output is hexadecimal and therefore is less hard to guess or brute-force than a string with the same number of chars that contains the rest of the alphabet, uppercase letters, other symbols, etc.
Is this why when people refer to tokens they refer to the bit length as opposed to char length?
Consider instead:
function randomString($length,
$alpha = true,
$alphau = true,
$numeric = true,
$specialChars = '') {
$string = $specialChars;
if($alpha === true) {
$string .= 'abcdefghijklmnopqrstuvwxyz';
}
if($alphau === true) {
$string .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
}
if($numeric === true) {
$string .= '0123456789';
}
$array = str_split($string);
$string = '';
for($counter = 0; $counter < $length; $counter ++) {
$string .= $array[array_rand($array)];
}
return $string;
}
In the context of web development when would you use the first function over the second for:
Creating a random password for a password reset
Creating a one-time use token (e.g. for a forgotten password link)
Creating a salt for a password hash (e.g. bcrypt, sha512, PBKDF2)
Creating a token for a “remember me” cookie token
In all instances I would use randomString() over token() so I guess I'm asking if and why I'm wrong in any of the above.
My rationale in relation to the above points:
12 char random password with uppercase, lower case and numbers is hard to guess; plus I freeze people out for 15 mins after 5 failed login attempts
64 char random string, If someone tried brute-forcing the token to reset a password the firewall would pick up on it
Salts should be assumed to be public anyway, so long as they're different per password it makes it impossible to produce a rainbow table
My remember me token is 128 char random string stored in a cookie and is salted and sha 512'd in the database
The primary concern with random number generators is generally not the output created, but the predictability in which this data is generated. Your basic question is why not use array_rand (which internally uses php_rand) over openssl_random_pseudo_bytes for cryptographic purposes. The answer has to do with the technique each function takes, with array_rand being a much more predictable (and reproduce-able) approach. See Pádraic Brady's article "Predicting Random Numbers In PHP – It’s Easier Than You Think!" for more detail: http://blog.astrumfutura.com/2013/03/predicting-random-numbers-in-php-its-easier-than-you-think/.
Concerning the output of random number generators, password/key strength in relation to brute force attacks is often measured in entropy. This is usually listed in bits with the more bits the better. The Wikipedia page on password strength (http://en.wikipedia.org/wiki/Password_strength) has some great reference tables for determining the entropy level of passwords at different lengths and using various combinations of character types. The openssl_random_pseudo_bytes() function utilizes all binary/hex values resulting in a full 8 bits of entropy per symbol. At best your randomString() function would result in 5.954 bits of entropy per symbol.
Use of a crypto strong random number should be used in all security related scenarios where the ability to guess one of these numbers would negatively affect your site in some manner. The only item in your list of 4 where I see a crypto strong random number not being required is with salt values for hashes. A salt value must be universally unique. It can certainly be produced by a crypto random number generator (CRNG), but this is not required as the resulting value can be made public. See https://security.stackexchange.com/questions/8246/what-is-a-good-enough-salt-for-a-saltedhash
I am working on a project which needs a very advanced security system when it comes to saving passwords etc. So my question is, is this way to save passwords safe enough?
This is the way I programmed:
When a user registers, a salt will be created out of the following details:
An unique user ID (primary key in mySQL)
The users emailaddress
The current (micro)timestamp
Some random key defined in the configuration of the website
This salt is being hashed into a sha512 key.
After the salt has been created, the following string is being hashed using Bcrypt: password + sha512 salt (worklevel 10, ($2a$10...)).
Then I skip the first 5 characters of the Bcrypt output ($2a$10) and I will save the remaining string into the database.
When a user tries to log in, I first check if the username exists. If it does, I check if the password is correct using the check() function.
I use this Bcrypt class to encrypt and check.
Can you guys tell me if this way of encrypting and verifying is well enough for a big project?
Regards,
Jan Willem
There's absolutely no point in deriving the salt from any particular input. In fact, this can only serve to weaken it. A salt derived from any value that has a relation with the value to be hashed is not a salt, it's just an altered hashing algorithm. Salts are entirely (pseudo) random noise, period. Their only point is to make the input unique; and the most unique value is random noise.
If you derive the salt from a good pseudo random number generator, there's no need to hash it. This can only serve to reduce its entropy.
You should store the entire result in the database (including $2a$10). If you're doing it properly, that hash is virtually impossible to brute force as is. Omitting that piece of information only makes your job more difficult, it doesn't make it meaningfully more difficult for a potential attacker.
Storing that value lets you see what algorithm the hash was created with and upgrade it over time. As hardware becomes faster you should increase the work factor of the algorithm and as better algorithms become available (hello scrypt!) you should upgrade the algorithm used. The way that works is that you check whether the hash was created using the latest and greatest when a user logs in. At that point is your opportunity to rehash the cleartext password and upgrade it.
Use PHP's new password_hash API or the password_compat shim for older versions, those are well tested and perfectly "advanced".
First, don't use bcrypt. Use PBKDF2 instead. This will allow you to increase the difficulty of doing an offline brute force attack. [Not saying that bcrypt won't, they are essentially the same, but PBKDF2 supports any hashing function, not just Blowfish]
Second, here is how you should do this.
Randomly Generate (don't base it on anything) a 128-bit random number for the Salt
Store the Salt as it's own parameter in the database. Remember the purpose of the salt is to make it so that the same password hashed twice doesn't have the same result.
Pick a good Hashing Algorithm. My recommendation, use SHA-256 or SHA-512. This is what you will use as part of the PBKDF2 function.
Do some research, figure out what is a good number of rounds to make the hashing of the password take 1 second. Remember if we have a password keyspace of 96 characters and a minimum of 8 characters wide, and each permutation takes a required 1 second to calculate, then an attacker will cry. The nice part of this is that over time as computers become faster, you can reevaluate this value and hash the hash a few more times to bring it up to the new 1 second requirement. Example: Say that 12 rounds of SHA-256 takes 1 second on modern computers. In 5 years, the computers are now so fast that 12 rounds takes 20ms instead. But 16 rounds takes 1 second on the hardware then. Just hashing the hash 4 additional times now keeps it at 1 second. The user never knew this happened because you didn't have to ask them to change their password.
You could store your password in the Shadow format, sure, but if you are using a database, just use database fields. You parsing a string is slower than the database just knowing what to give you.
Now I want to point out a timing attack in your system. If you tell an attacker that a username doesn't exist or the amount of time it takes to return an error is different (and shorter) than it takes to return a success, then you have a possibility for a side channel attack. Additional information can make it easier for an attacker to enumerate user accounts on your system (and if the username is based on Email, then they can now do a phishing attack against the user directly).
At first I want to thank you guys for the quick response, it is very important to me to make the system as hard to hack as possible.
Based on your recommendations I made the following system:
When a user registers, an unique hash is being created:
$unique_hash = hash('sha512', some defined security key (in the config) . openssl_random_pseudo_bytes(50));
At second, a salt is created:
$salt = strtr(base64_encode(openssl_random_pseudo_bytes(50)), '+', '.');
In another table, located in another database, this two variables are being combined and only the $unique_hash is stored in the users row in the users table.
Then we create the encrypted password, I use this function:
<?php
const COUNT = 8192;
const KEY_LENGTH = 254;
const ALGORITHM = 'sha512';
public static function encrypt($password, $salt, $algorithm = PBKDF2::ALGORITHM, $count = PBKDF2::COUNT, $key_length = PBKDF2::KEY_LENGTH, $raw_output = false) {
$algorithm = strtolower($algorithm);
if(!in_array($algorithm, hash_algos(), true))
die('PBKDF2 ERROR: Invalid hash algorithm.');
if($count <= 0 || $key_length <= 0)
die('PBKDF2 ERROR: Invalid parameters.');
$hash_length = strlen(hash($algorithm, "", true));
$block_count = ceil($key_length / $hash_length);
$output = "";
for($i = 1; $i <= $block_count; $i++)
{
$last = $salt . pack("N", $i);
$last = $xorsum = hash_hmac($algorithm, $last, $password, true);
for ($j = 1; $j < $count; $j++)
$xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true));
$output .= $xorsum;
}
if($raw_output)
return substr($output, 0, $key_length);
else
return bin2hex(substr($output, 0, $key_length));
}
?>
Then we store this password in the default user table.
Last step; logging in. When a user tries to log in, at first I check if the given username exists in the database. When it does, I call the $unique_hash, located in the same row. Since we need the salt, I search in the other database, where the hashes and salts are stored. Then the salt will be returned and we are able to validate the given password with the same function (encrypt()).
This will be it. Is this safe enough?
Jan Willem
At the moment I have a database with md5 passwords stored, a few years back this was considered a little more secure than it is now and it's got to the point where the passwords need to be more secure.
I've read a lot of posts on here about crypt, md5, hash, bcrypt, etc and have come to consider using something along the lines of the following to 'secure' the passwords better than they are now.
I will use a combination of hash("sha512" and two salts, the first salt will be a site wide salt stored in a file such as .htaccess and the second salt will be created for each user.
Here's an example along the lines of what I'm testing at the moment:
.htaccess
SetEnv SITEWIDE_SALT NeZa5Edabex?26Y#j5pr7VASpu$8UheVaREj$yA*59t*A$EdRUqer_prazepreTr
example.php
$currentpassword = //get password
$pepper = getenv('SITEWIDE_SALT');
$salt = microtime().ip2long($_SERVER['REMOTE_ADDR']);
$saltpepper = $salt.$pepper;
$password = hash("sha512", md5($currentpassword).$saltpepper);
The salt would obviously need to be stored in a separate table to allow checking of future inserted login passwords but it would never be possible for a user to see. Do you think this is a sufficient way to go about this?
Ok, let's go over a few points here
What you have in $salt is not a salt. It's deterministic (meaning that there is no randomness in there at all). If you want a salt, use either mcrypt_create_iv($size, MCRYPT_DEV_URANDOM) or some other source of actual random entropy. The point is that it should be both unique and random. Note that it doesn't need to be cryptographically secure random... At absolute worst, I'd do something like this:
function getRandomBytes($length) {
$bytes = '';
for ($i = 0; $i < $length; $i++) {
$bytes .= chr(mt_rand(0, 255));
}
return $bytes;
}
As #Anony-Mousse indicated, never feed the output of one hash function into another without re-appending the original data back to it. Instead, use a proper iterative algorithm such as PBKDF2, PHPASS or CRYPT_BLOWFISH ($2a$).
My suggestion would be to use crypt with blowfish, as it's the best available for PHP at this time:
function createBlowfishHash($password) {
$salt = to64(getRandomBytes(16));
$salt = '$2a$10$' . $salt;
$result = crypt($password, $salt);
}
And then verify using a method like this:
function verifyBlowfishHash($password, $hash) {
return $hash == crypt($password, $hash);
}
(note that to64 is a good method defined here). You could also use str_replace('+', '.', base64_encode($salt));...
I'd also suggest you read the following two:
Fundamental difference between hashing and encrypting
Many hash iterations, append salt every time?
Edit: To Answer the Migration Question
Ok, so I realize that my answer did not address the migration aspect of the original question. So here's how I would solve it.
First, build a temporary function to create a new blowfish hash from the original md5 hash, with a random salt and a prefix so that we can detect this later:
function migrateMD5Password($md5Hash) {
$salt = to64(getRandomBytes(16));
$salt = '$2a$10$' . $salt;
$hash = crypt($md5Hash, $salt);
return '$md5' . $hash;
}
Now, run all the existing md5 hashes through this function and save the result in the database. We put our own prefix in so that we can detect the original password and add the additional md5 step. So now we're all migrated.
Next, create another function to verify passwords, and if necessary update the database with a new hash:
function checkAndMigrateHash($password, $hash) {
if (substr($hash, 0, 4) == '$md5') {
// Migrate!
$hash = substr($hash, 4);
if (!verifyBlowfishHash(md5($password), $hash) {
return false;
}
// valid hash, so let's generate a new one
$newHash = createBlowfishHash($password);
saveUpdatedPasswordHash($newHash);
return true;
} else {
return verifyBlowfishHash($password, $hash);
}
}
This is what I would suggest for a few reasons:
It gets the md5() hashes out of your database immediately.
It eventually (next login for each user) updates the hash to a better alternative (one that's well understood).
It's pretty easy to follow in code.
To answer the comments:
A salt doesn't need to be random - I direct you to RFC 2898 - Password Based Cryptography. Namely, Section 4.1. And I quote:
If there is no concern about interactions between multiple uses
of the same key (or a prefix of that key) with the password-
based encryption and authentication techniques supported for a
given password, then the salt may be generated at random and
need not be checked for a particular format by the party
receiving the salt. It should be at least eight octets (64
bits) long.
Additionally,
Note. If a random number generator or pseudorandom generator is not
available, a deterministic alternative for generating the salt (or
the random part of it) is to apply a password-based key derivation
function to the password and the message M to be processed.
A PseudoRandom Generator is available, so why not use it?
Is your solution the same as bcrypt? I can't find much documentation on what bcrypt actually is? - I'll assume that you already read the bcrypt Wikipedia Article, and try to explain it better.
BCrypt is based off the Blowfish block cipher. It takes the key schedule setup algorithm from the cipher, and uses that to hash the passwords. The reason that it is good, is that the setup algorithm for Blowfish is designed to be very expensive (which is part of what makes blowfish so strong of a cypher). The basic process is as follows:
A 18 element array (called P boxes, 32 bits in size) and 4 2-dimensional arrays (called S boxes, each with 256 entries of 8 bits each) are used to setup the schedule by initializing the arrays with predetermined static values. Additionally, a 64 bit state is initialized to all 0's.
The key passed in is XOred with all 18 P boxes in order (rotating the key if it's too short).
The P boxes are then used to encrypt the state that was previously initialized.
The ciphertext produced by step 3 is used to replace P1 and P2 (the first 2 elements of the P array).
Step 3 is repeated, and the result is put in P3 and P4. This continues until P17 and P18 are populated.
That's the key derivation from the Blowfish Cipher. BCrypt modifies that to this:
The 64 bit state is initialized to an encrypted version of the salt.
Same
The P boxes are then used to encrypt the (state xor part of the salt) that was previously initialized.
Same
Same
The resulting setup is then used to encrypt the password 64 times. That's what's returned by BCrypt.
The point is simple: It's a very expensive algorithm that takes a lot of CPU time. That's the real reason that it should be used.
I hope that clears things up.
Implementation of your new, more secure, password storage should use bcrypt or PBKDF2, as that's really the best solution out there right now.
Don't nest things, as you don't get any real security out of this due to collisions as #Anony-Mousse describes.
What you may want to do it implement a "transition routine" where your app transitions users over from the old MD5-based system to the new more secure system as they log in. When a login request comes in, see if the user is in the new, more secure, system. If so, bcrypt/PBKDF2 the password, compare, and you're good to go. If they are not (no one will be at first), check them using the older MD5-based system. If it matches (password is correct), perform the bcrypt/PBKDF2 transformation of the password (since you now have it), store it in the new system, and delete the old MD5 record. Next time they log in, they have an entry in the new system so you're good to go. Once all of the users have logged in once you implement this, you can remove this transition functionality and just authenticate against the new system.
Do not nest md5 inside your sha512 hash. An md5 collision then implies a hash collision in the outer hash, too (because you are hashing the same values!)
The common way of storing passwords is to use a scheme such as
<method><separator><salt><separator><hash>
When validating the password, you read <method> and <salt> from this field, reapply them to the password, and then check that it produces the same <hash>.
Check the crypt functions you have available. On a modern Linux system, crypt should be able to use sha512 password hashing in a sane way: PHP crypt manual. Do not reinvent the wheel, you probably just screw up more badly than md5, unless you are an expert on cryptographic hashing. It will even take care of above scheme: the Linux standard is to use $ as separator, and $6$ is the method ID for sha512, while $2a$ indicates you want to use blowfish. So you can even have multiple hashes in use in your database. md5 hashes are prefixed with $1$<salt>$ (unless you reinvented md5 hashing, then your hashes may be incompatible).
Seriously, reuse the existing crypt function. It is well checked by experts, extensible, and compatible across many applications.
I looked into this subject a while back and found the following link of great use:
Secure hash and salt for PHP passwords
I also use the following to create a random salt:
public static function getRandomString($length = 20) {
$characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
$string = '';
for ($i = 0; $i < $length; $i++) {
$string .= substr($characters, (mt_rand() % strlen($characters)), 1);
}
return $string;
}
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.