Viewed   68 times

I'm looking at some code that I have not written myself. The code tries to hash a password with SHA512 and uses just time() as the salt. Is time() too simple a salt for this or is this code safe?

Thanks for the answers and comments. I will sum it up here for the new readers:

  • salt should be different for each user, so if 2 users register at the same time, their salts won't be unique. This is a problem, but not a big one.
  • but salt shouldn't be in any way related to the user, so time() is not a good salt.
  • "Use a random, evenly distributed, high entropy salt." -- That's a mouthful, so what code could possibly generate a random, evenly distributed, high entropy salt?

Ok, so how about I replace time() with a random string 32 char long. The random string could be generated from looping 32 times over a set of alphabet chars. Does that sound good?

 Answers

3

Short answer:

No, time() is not a good salt.

Long answer:

copied from my answer to Salt Generation and open source software

What is a salt?

A salt is a random set of bytes of a fixed length that is added to the input of a hash algorithm.


Why is salting (or seeding) a hash useful?

Adding a random salt to a hash ensures that the same password will produce many different hashes. The salt is usually stored in the database, together with the result of the hash function. Salting a hash is good for a number of reasons:

  1. Salting greatly increases the difficulty/cost of precomputated attacks (including rainbow tables)
  2. Salting makes sure that the same password does not result in the same hash. This makes sure you cannot determine if two users have the same password. And, even more important, you cannot determine if the same person uses the same password across different systems.
  3. Salting increases the complexity of passwords, thereby greatly decreasing the effectiveness of both Dictionary- and Birthday attacks. (This is only true if the salt is stored separate from the hash).
  4. Proper salting greatly increases the storage need for precomputation attacks, up to the point where they are no longer practical. (8 character case-sensitive alpha-numeric passwords with 16 bit salt, hashed to a 128 bit value, would take up just under 200 exabytes without rainbow reduction).


There is no need for the salt to be secret.

A salt is not a secret key, instead a salt 'works' by making the hash function specific to each instance. With salted hash, there is not one hash function, but one for every possible salt value. This prevent the attacker from attacking N hashed passwords for less than N times the cost of attacking one password. This is the point of the salt.
A "secret salt" is not a salt, it is called a "key", and it means that you are no longer computing a hash, but a Message Authentication Code (MAC). Computing MAC is tricky business (much trickier than simply slapping together a key and a value into a hash function) and it is a very different subject altogether.

The salt must be random for every instance in which it is used. This ensures that an attacker has to attack every salted hash separately.
If you rely on your salt (or salting algorithm) being secret, you enter the realms of Security Through Obscurity (won't work). Most probably, you do not get additional security from the salt secrecy; you just get the warm fuzzy feeling of security. So instead of making your system more secure, it just distracts you from reality.


So, why does the salt have to be random?

Technically, the salt should be unique. The point of the salt is to be distinct for each hashed password. This is meant worldwide. Since there is no central organization which distributes unique salts on demand, we have to rely on the next best thing, which is random selection with an unpredictable random generator, preferably within a salt space large enough to make collisions improbable (two instances using the same salt value).

It is tempting to try to derive a salt from some data which is "presumably unique", such as the user ID, but such schemes often fail due to some nasty details:

  1. If you use for example the user ID, some bad guys, attacking distinct systems, may just pool their resources and create precomputed tables for user IDs 1 to 50. A user ID is unique system-wide but not worldwide.

  2. The same applies to the username: there is one "root" per Unix system, but there are many roots in the world. A rainbow table for "root" would be worth the effort, since it could be applied to millions of systems. Worse yet, there are also many "bob" out there, and many do not have sysadmin training: their passwords could be quite weak.

  3. Uniqueness is also temporal. Sometimes, users change their password. For each new password, a new salt must be selected. Otherwise, an attacker obtained the hash of the old password and the hash of the new could try to attack both simultaneously.

Using a random salt obtained from a cryptographically secure, unpredictable PRNG may be some kind of overkill, but at least it provably protects you against all those hazards. It's not about preventing the attacker from knowing what an individual salt is, it's about not giving them the big, fat target that will be used on a substantial number of potential targets. Random selection makes the targets as thin as is practical.


In conclusion:

Use a random, evenly distributed, high entropy salt. Use a new salt whenever you create a new password or change a password. Store the salt along with the hashed password. Favor big salts (at least 10 bytes, preferably 16 or more).

A salt does not turn a bad password into a good password. It just makes sure that the attacker will at least pay the dictionary attack price for each bad password he breaks.


Usefull sources:
.com: Non-random salt for password hashes
Bruce Schneier: Practical Cryptography (book)
Matasano Security: Enough with the Rainbow Tables
usenix.org: Unix crypt used salt since 1976
owasp.org: Why add salt
openwall.com: Salts

Disclaimer:
I'm not a security expert. (Although this answer was reviewed by Thomas Pornin)
If any of the security professionals out there find something wrong, please do comment or edit this wiki answer.


As for what seems to be a good source for your random salt
Also read: What is the most secure seed for random number generation?
In the absence of dedicated, hardware based, random generators, the best way of obtaining random data is to ask the operating system (on Linux, this is called /dev/random or /dev/urandom [both have advantages and problems, choose your poison]; on Windows, call CryptGenRandom())

If for some reason you do not have access to the above mentioned sources of random, in PHP you could use the following function:
From the source of phpass v0.3

<?php
/**
 * Generate pseudo random bits
 * @copyright: public domain
 * @link http://www.openwall.com/phpass/
 * @param int $length number of bits to generate
 * @return string A string with the hexadecimal number
 * @note don't try to improve this, you will likely just ruin it
 */
function random_bits($entropy) {
    $entropy /= 8;
    $state = uniqid();
    $str = '';
    for ($i = 0; $i < $entropy; $i += 16) {
        $state = md5(microtime().$state);
        $str .= md5($state, true);
    }
    $str = unpack('H*', substr($str, 0, $entropy));
    // for some weird reason, on some machines 32 bits binary data comes out as 65! hex characters!?
    // so, added the substr
    return substr(str_pad($str[1], $entropy*2, '0'), 0, $entropy*2);
}
?>
Thursday, November 17, 2022
2

Actually this is kind of strange, with the string conversions - which the membership provider does to put them into config files. Hashes and salts are binary blobs, you don't need to convert them to strings unless you want to put them into text files.

In my book, Beginning ASP.NET Security, (oh finally, an excuse to pimp the book) I do the following

static byte[] GenerateSaltedHash(byte[] plainText, byte[] salt)
{
  HashAlgorithm algorithm = new SHA256Managed();

  byte[] plainTextWithSaltBytes = 
    new byte[plainText.Length + salt.Length];

  for (int i = 0; i < plainText.Length; i++)
  {
    plainTextWithSaltBytes[i] = plainText[i];
  }
  for (int i = 0; i < salt.Length; i++)
  {
    plainTextWithSaltBytes[plainText.Length + i] = salt[i];
  }

  return algorithm.ComputeHash(plainTextWithSaltBytes);            
}

The salt generation is as the example in the question. You can convert text to byte arrays using Encoding.UTF8.GetBytes(string). If you must convert a hash to its string representation you can use Convert.ToBase64String and Convert.FromBase64String to convert it back.

You should note that you cannot use the equality operator on byte arrays, it checks references and so you should simply loop through both arrays checking each byte thus

public static bool CompareByteArrays(byte[] array1, byte[] array2)
{
  if (array1.Length != array2.Length)
  {
    return false;
  }

  for (int i = 0; i < array1.Length; i++)
  {
    if (array1[i] != array2[i])
    {
      return false;
    }
  }

  return true;
}

Always use a new salt per password. Salts do not have to be kept secret and can be stored alongside the hash itself.

Friday, August 5, 2022
 
joshmc
 
5

If you are hashing passwords, you should use a modern hashing algorithm that does not require you to generate your own salt. Using weak hashing algorithms presents a danger to both you and your users. My original answer was written eight years ago. Times have changed, and password hashing is a lot easier now.

You should always use built in functions to hash/check passwords. Using your own algorithms at any point introduces a huge amount of unnecessary risk.

For PHP, consider using password_hash(), with the PASSWORD_BCRYPT algorithm. There is no need to provide your own salt.

Below is my original answer, for posterity:


Warning: The following implementation does not produce an unpredictable salt, as per the documentation for uniqid.

From the php sha1 page:

$salt = uniqid(mt_rand(), true);

This looks simpler, and more effective (since each is unique) than what you have proposed.

Wednesday, November 23, 2022
 
gke
 
gke
1

Instead of using SHA family methods, you can use the crypt() function to salt it for you.

Here is an example script (save and login) using PDO.

Save password in DB

<?php
// Set the password
$password = 'mypassword';

// Get the hash, letting the salt be automatically generated
$hash = crypt($password);

echo $hash; // for testing purposes only

$mysql_username = 'username'; // for DB
$mysql_password = 'password'; // for DB

$dbh = new PDO('mysql:host=localhost;dbname=database_name', $mysql_username, $mysql_password);

$stmt = $dbh->prepare("INSERT INTO table_name (name,pass) VALUES (:name,:pass)");
$stmt->bindParam(':name', $name);
$stmt->bindParam(':pass', $pass);

// insert rows
// $name = $_POST['name'];
// $name = $_POST['pass'];

$name = "username";
$pass = $hash;
$stmt->execute();

Login script

<?php
$mysql_username = 'username'; // for DB
$mysql_password = 'password'; // for DB

$dbh = new PDO('mysql:host=localhost;dbname=database_name', $mysql_username, $mysql_password);

/*
$username = $_POST['username'];
$password = $_POST['password'];
*/

$username = "username";
$password = "mypassword";

$sql = "SELECT * FROM table_name WHERE name=:username";
$statement = $dbh->prepare($sql);
$statement->bindValue(':username',$username,PDO::PARAM_STR);

if($statement->execute())
{
    if($statement->rowCount() == 1)
    {
        $row = $statement->fetch(PDO::FETCH_ASSOC);

 if (crypt($password, $row['pass']) === $row['pass'])

        {
            $username = $row['name'];
            $email = $row['email'];

echo "Stage 1";

echo "<hr noshade size="1">";

echo "Hello " .$username;

            exit;
        }
        else
        {
            // include "error_login.php";

echo "Stage 2 - ERROR";

        }
    }
    else
    {
       // include "error_login.php";

echo "Stage 3 error";
    }
}
Thursday, December 8, 2022
 
robertg
 
2

Probably best bet would be to use a time-series database. Looks like Warp 10 has already been mentioned (https://www.warp10.io).

The benefit of something like warp is the ability to query on the time component of your database. I believe firebase only has simple greater/lesser than queries available for time.

Tuesday, September 27, 2022
 
scrwtp
 
Only authorized users can answer the search term. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :