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);
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.
In MySQL this can be done using UNION:
SELECT password_fields FROM users WHERE user=? AND hash=? UNION SELECT '12345789' as salt, 'ffffffffffffffffffffffff' as hash, 'fake' as user LIMIT 1;
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).
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".
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: