The times that hashing with salts being a barrier to crackers are long since gone. Rainbow tables are equally outdated. These days GPUs and software are so adept at bruteforcing billions of passwords per second, in patterns that most humans use, that even a fair amount of 10 character passwords are now ‘reversable’ within a few hours. The Linkedin leak of 6 million hash-salted passwords showed how weak this approach actually was. Within a day 95% of the passwords were revealed, including passwords with length up to 14 chars.
The problem is that hashing algorithms are designed to be fast. This makes them vulnerable to intelligent bruteforcing attacks, going through all permutations of substituting certain characters with numbers, assuming passwords typically starting with an uppercase character and ending with one or 2 digits or 4 (in the range 1900…2000) and special characters. That, combined with word lists and lists of previously found passwords.
You should make sure that calculating the salted-hash takes about 1s on modern hardware, so when using hashes like SHA1 (instead of bcrypt) that means:
// laughable
passhash = hash(hash(password) +"::"+ hash(usersalt)));
// practically uncrackable
temp = hash(hash(password) +"::"+ hash(usersalt)));
for(int i=0; i<100000; i++) {
temp = hash(hash(temp) +"::"+ hash(i)));
}
passhash = temp;
Luckily this hashing happens clientside, so it won’t grind your server to a halt.
A good short read: http://codahale.com/how-to-safely-store-a-password/
(note that we added the work-factor in the hash approach)
A good long read: http://arstechnica.com/security/2013/05/how-crackers-make-minced-meat-out-of-your-passwords/
(on how a password like qeadzcwrsfxv1331 and 98% of other ‘hard’ passwords are cracked)
Login
Logging in by sending your passhash is susceptible for man-in-the-middle attacks (even when using SSL). You should use a hash-handshake, so that a login cannot be played back later. In practical terms the server generates a random string of chars, and asks the users: what should be the hashpass if you were too take the hash of it and this piece of data.
// Server sends to client:
String challenge = generateRandomString(1024 /*length*/);
String salt = // retrieve from database
// Client sends to server:
String saltedpasshash = hash(hash(salt) +"::"+ passhash);
String answer = hash(saltedpasshash +"::"+ hash(challenge));
// Server verifies answer
Access
As for access tokens (or cookies providing session-ids) you have to guard against stealing such tokens. It’s much better to calculate nonces, so that every request to the server has a unique access token, that only the server and client can calculate, This sequence of access tokens should be consecutive, and once the server receives 1 access token it wasn’t expecting, it should logout the user immediately. These sessions should not be undefinitely valid. Creating a new session about every hour is advisable.
nonce = hash(
hash(loginChallenge) +"::"+
hash(passhash) +"::"+
hash(requestCounter++)
);