user1763295 user1763295 - 1 year ago 53
PHP Question

PHP Strongest one way encryption/hashing method

I have a site on which people can sign up and I need to encrypt their password. I've researched it but I can't find any methods that won't be beat by the power of a modern GPU.

So I have come to the good people of StackOverflow to ask, what is the strongest method of encryption possible, I have done my best to stop people getting their hands on the database but I want to be as sure as possible that their data would be fine if the database was somehow stolen.

Other things I wonder are, would be it be safer to just randomize the characters in the password somehow but in a way where you would be able to randomize them again for login?

I have used the implementation of bcrypt by Andrew Moore (How do you use bcrypt for hashing passwords in PHP?) and come up with this:

public static function Encrypt($Str,$Salt)
$bcrypt = new \bcrypt();
return $bcrypt->hash(SERVER_SALT . md5($Str) . $Salt);

if anyone sees anything wrong with it or any weaknesses please tell me.

Answer Source

For passwords, you can't beat bcrypt. Here is a link on SO: How do you use bcrypt for hashing passwords in PHP? . Its main advantage: it is inherently slow (as well as secure). While a normal user will use it once, and will not appreciate the difference between one tenth and one millionth of a second, a cracker will discover that instead of finding the password in four days, he now needs twelve centuries (except that forcing Blowfish even at one million attempts per second does not require four days: excepting implementation errors and as yet unforeseen crypto breakthroughs, the heat death of the Universe will still come first).

For data, I'd rely on the database engine itself; MySQL supports AES, and that's quite good. Yes, with enough GPUs one could beat it, and since to crack the code within the century one would need some four billions of them, he'd probably get a good quantity discount too. But I wouldn't worry.

As for the "randomization" of the password, it would serve no purpose. If someone were to resort to bruteforcing your password, he would have the same probability of finding the randomized sequence as of finding the non-randomized one. It might be of some help if one were to use a dictionary attack (i.e., not trying everything from AAAAA to ZZZZZ, but trying ABRAHAM, ACCORDION, ... up to, say, ZYGOTE). But even then, an attack against the server would change nothing (the server receives the nonrandomized password!), and an attack against the stored hash would be better nullified by salting, which bcrypt automatically does (i.e. storing the hash of $76X.J:MICKEYMOUSE instead of the hash of MICKEYMOUSE, together with the needed overhead to handle the extra '$76X.J:' on input... and while the randomization would have to be fixed, the '$76X.J:' sequence is completely different on every write -- good luck figuring that out!).


If you implement salting yourself, you can do it by generating a unique random sequence and storing it either in the password field or in a second field. For example

user    password
alice   d9c06f88:6c14d6d313d7cbcb132b5c63650682c4

Then, upon receipt of the password by Alice ("mickeymouse"), you would look in the database to see whether a user called alice exists. If it does, recover the salt (here d9c06f88) and the hash. If it does not, set a "BAD" flag and fetch a fixed salt and hash (e.g. 12345678:0000000...).

In MySQL this can be done using UNION:

SELECT password_fields FROM users WHERE user=? AND hash=?
    '12345789' as salt,
    'ffffffffffffffffffffffff' as hash,
    'fake' as user

will retrieve either the correct data or an incorrect set in roughly the same time.

Then concatenate the salt and the password, and generate the hash of d9c06f88:mickeymouse. If it does not match, or the "BAD" flag is set, reject the password (instead of the bad flag you can repeat the test for user match: you can initialize the user name with invalid characters to ensure it will never match a real user).

Security through noninformation

The added twist of selecting a fixed string is useful because you want the three cases, "user does not exist", "user exists but password is incorrect" and "user exists and password is correct" to be as similar (same complexity, same expense in calculations) as possible.

This way, an attacker will be less likely to tell what happened: was the user incorrect? Was the password wrong? And so on. If the times were different enough (say two queries for valid users, one for invalid users), an attacker with care, time and a good stopwatch could determine statistically whether a given username is present or not.

For the same reason, you will not return two different errors for "Bad user" and "Bad password", but always a "Bad user or password"; maybe with the option, for the user, to receive an email to his/her registered email to remind him/her of the username. And of course, you want the system to not send such an email before 24 hours have passed from sending a similar email to the same user, to prevent the system from being exploited to harass someone with spurious "recovery emails".

There would have been a time for such a password

A convenient way to improve server security against bruteforcing is to implement a delay on password authentication, and maybe (if you're really paranoid) a CAPTCHA-ed lockout after some X wrong attempts.

You don't want to CAPTCHA the first attempts because users take a dim view of CAPTCHAs.

Delayed lockout is often implemented with sleep (or equivalent), or using a "lock out until manually reset by the admin" strategy. Both methods are bad. The lockout feature can be used to create a denial of service attack, either by locking out an user, or creating lots of server threads stopped in "password authentication delay" state (they won't use CPU, but they will still use memory, sockets and other resources). It is best to move the burden on the client:

(very pseudo code)
login = FAIL
if in SECURITY LOCKOUT MODE for this account
    if a session is open and contains a last-attempt time
        if at least DELAY seconds have elapsed since last-attempt
            check the password
            if it is correct
                login = OK
                zero password counter, exit lockout mode.
            # "Early knocker". Either a bruteforcing robot
            # or a too clever user in a hurry. But we can't
            # tell them apart.
        # No session. Either a sessionless bruteforcing robot
        # or a unaware, innocent user. Again we can't tell them
        # apart. So we bounce both.

        # But a genuine user will assume he has mistyped the password,
        # or anyway will read the warning page, and will login after ONE
        # round of delay.

        # Users with password saved in browser will just click
        # "login" again and be logged in.

        # A robot will find itself delayed and ALL ITS PASSWORDS IGNORED
        # every single time. Even if it finds the right password... it will
        # not work.
    check the password
    if it is correct
        # Good user, first attempt, fast login.
        login = OK
        # Beginning to doubt this is a good user...
        increase password counter
        if it is > MAX_ATTEMPTS
            enter SECURITY LOCKOUT MODE for this account
if login is not OK
    generate a page with HTTP_REFRESH time of DELAY+1 seconds
    and a session ID, saying "User or password unknown,
    or you tried to login before HH:MM:SS (DELAY seconds).
    The page might also contain a Javascript timer, just in
    case. The delay is 1s more than necessary as a safety