Jan Willem
Jan Willem

Reputation: 1320

Is my Bcrypt encryption safe enough?

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:

  1. 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
  2. This salt is being hashed into a sha512 key.
  3. After the salt has been created, the following string is being hashed using Bcrypt: password + sha512 salt (worklevel 10, ($2a$10...)).
  4. 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

Upvotes: 1

Views: 4631

Answers (3)

Jan Willem
Jan Willem

Reputation: 1320

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:

  1. 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));

  2. At second, a salt is created:

    $salt = strtr(base64_encode(openssl_random_pseudo_bytes(50)), '+', '.');

  3. 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.

  4. 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));
    
     }
         ?>
    
  5. Then we store this password in the default user table.

  6. 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

Upvotes: 0

sparticvs
sparticvs

Reputation: 592

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.

  1. Randomly Generate (don't base it on anything) a 128-bit random number for the Salt
  2. 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.
  3. 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.
  4. 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.
  5. 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).

Upvotes: 0

deceze
deceze

Reputation: 522510

  1. 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.
  2. 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.
  3. 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".

Upvotes: 3

Related Questions