Update old stored md5 passwords in PHP to increase security

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.

    链接地址: http://www.djcxy.com/p/28936.html

    上一篇: 使用PHP加密和解密密码的最佳方法是什么?

    下一篇: 在PHP中更新旧的存储的md5密码以提高安全性