The method that I have come up with is to use SHA-256 with multiple rounds and a random salt...
salt = generate random salt
hash = sha256(password)
for (i = 0 to rounds)
hash = sha256(hash + salt)
This encrypts the password in such a way that only the user knows what the password is. Both salt and hash are stored in the database. I have also implemented this on the client side using a server generated random string.
server_data = {
algorithm,
rounds,
salt,
string
}
hash = hash(server_data.algorithm, password)
for (i = 0 to server_data.rounds)
hash = hash(server_data.algorithm, hash + salt)
hash = hash(server_data.algorithm, hash + server_data.string)
In crypto class, we were taught that the attacker can know the algorithm, salt, rounds, etc.... But in this case, the actual hashed password is never sent. The server knows what the random string that it sent was, so the full hash is generated on the client, and the final piece is to hash that result with the random string. That's the result that's sent to the server. The result is effectively a one-time password that is useless on replay attacks. The user's password is never sent in the clear, and if the attacker gained the password hash, it would be useless to them because the server will not accept it in that form. The hacker MAY be able to compute a rainbow table for the salt and the number of rounds to try and brute force the password, but any sufficintly complex password using ASCII symbols 32-126 of at least 8 characters long will usually take a long time to crack...and that's for EACH password. The sites that I write for can have passwords up to 128 characters in length.
As for session hijacking, that risk can be reasonably mitigated by regenerating the session ID every 30 seconds or so. I also include a token that's embedded in the HTML of the page itself. On the server side, some of the client information such as the client's IP address and user agent string are hashed as well and stored in the user's session data.
So on each request, the server gets the cookie session ID and the token. The token is compared to what is in the session data. The other information such as the IP address and user agent are hashed and compared to what's in the session as well. If it all matches, then there's a very high probability that this is the real user and not an intruder.
The only way that I can see this happening is if the attacker has the embedded token, the same IP address, and the same user agent. It's all about probabilities. Besides, any time you are dealing with crypto, it can be hard to get right. So as others have said, don't do it yourself.