NEVER store passwords. If a website (or site administrator) can tell you what your password is, the site is insecure. All you should be storing in your "users" table is an auto-incremented primary key, the user name and/or user email address, a permissions value, a salted and hashed version of the password and the salt value, or "nonce", which should be different for every user*. Having the user register with an email address guarantees a unique value, and lets you confirm registration using a confirmation link in an email. (It also gives you a way to send a temporary password or "set a new password" link from your "forgot password" script.)
The "nonce" can be any unique value. The timestamp of the account creation is as good as any. You would then concatenate the nonce with the password and hash the value. Don't use MD5; its value space (the number of unique plaintext values that will generate unique hashes) is too small, and the function is too fast. Together, that means a brute-force attack is workable if anyone discovers the values you have stored in the database for even one user. Use at least SHA 256 (some advocate using the horribly inefficient bcrypt because of its inefficiency, but it might run you afoul of the maximum CPU burst usage on a free hosting site), and run the hash function at least a thousand times. (RSA's PBKDF2 will do wonderfully well.)
Code:
/** PBKDF2 Implementation (as described in RFC 2898);
*
* @param string p password
* @param string s salt
* @param int c iteration count (use 1000 or higher)
* @param int kl derived key length
* @param string a hash algorithm
*
* @return string derived key
*/
public function pbkdf2( $p, $s, $c, $kl, $a = 'sha256' ) {
$hl = strlen(hash($a, null, true)); # Hash length
$kb = ceil($kl / $hl); # Key blocks to compute
$dk = ''; # Derived key
# Create key
for ( $block = 1; $block <= $kb; $block ++ ) {
# Initial hash for this block
$ib = $b = hash_hmac($a, $s . pack('N', $block), $p, true);
# Perform block iterations
for ( $i = 1; $i < $c; $i ++ )
# XOR each iterate
$ib ^= ($b = hash_hmac($a, $b, $p, true));
$dk .= $ib; # Append iterated block
}
# Return derived key of correct length
return substr($dk, 0, $kl);
}
Most of the login scripts (and tutorials) I've seen on the web are naive and dangerous. They may be okay for your model airplane club site, but if you really need to make sure that your users are your users, if your site contains any confidential information, or if you need to protect yourself from legal liability for cyberstalking or cyberbullying, then you need something a whole lot better. You can take what the tutorials have to say at face value, but please, please use a salted hash.
*If you are using email confirmations and password resets, you'll want to include a column for the "pending action" flag value as well.