Intro
Logins are one of the first things people want to learn, however seldom do they expand on it.
Authentication mechanisms should be very well protected, you are only as strong as your weakest link.
A lot more things go into authentication than just comparing strings. Ideally you should be implementing
each of the following in your login scheme.
- Safe Errors
- Injection Protection
- Encryption
- Correct Permissions
- Safe Data Stores
- IP Locking
- User Locking
- Conditional CAPTCHA
- Forgot Pass
- Connection
I'll Go over each of them next.
Safe Errors
This is usually the culprit of newer developers, or devs who are
trying to keep things professional but go too far for security's sake.
If a user attempts to login and there username matches a record but the password
does not, you do not want to give an error saying the password is wrong. Your errors for all
failed login attempts should be EXACTLY the same. This prevents people/scripts from enumerating
users on your site. The best way is to have a configuration, and set a variable for the error string.
Injection Protection
SQL injection, I'll say it a million times is the most dangerous vulnerability out there.
Using an injection, an attacker can login as any user, or the top user in the returned recordset, or even
compromise your server alltogether. Always use the escape function for your DBMS , such as mysql_real_escape string
for mysql. Use this on EVERY variable that goes into your querys, no matter what medium they came from.
Encryption
Never, Never,Never store sensitive data in your database without encryption. There is a rule for encryption.
Will you ever need to retrieve the data in plaintext, such as credit card #'s, ss#'s, etcs
If so then you need to use an encryption scheme, try using AES first, Tripple DES (3DES) second, both
can be implemented using php's mcrypt() function.
If you only need a string for comparison, and do not need retrieval, such as passwords (Note: even using forgot
password functions, you never need to retrieve original pass, you should always make user generate a new one), you
will want to use a Hashing function. Hashing is a one-way encryption that cannot be decrypted, only bruteforced.
For hashing you will want to use SHA512/SHA1 first and md5 last. These can be done with the following functions
hash() sha1() md5()
Also you will need to specify a Key, or Salt. Always do the longest key possible if using a key. A salt is a
word that is added to the plaintext before encryption. This can be any word you want, just concatenate it onto
the plaintext before encryption. Don't forget to use it again when comparing hashes, or decrypting encryptions.
A salt protects you if an attacker is able to get the hashes from your database, then it would be very difficult for him
to brrute force without it.
Correct Permissions
This is very common for people to forget file system protection. Due to php being interpreted, not compiled, if someone gained control of your server where your files are hosted, they would have access to your php files, so
all that hashing, those keys/salts can be retrieved, and even code could be altered to log data in plaintext format.
Moral of this story is make sure your files are behind web root if possible, and make sure folders have correct file system permissions.
Safe Data Stores
Don't use flat files for storing sensitive information. Nor should you be passing around user's sensitive data in cookies. Use a database management system such as MYSQL, and use Sessions for user info, but never store passwords or sensitive data in a persistent state like sessions , only keep those in one place, your guarded database.
IP Locking
Whenever a certain IP attempts to login and fails too many times, say more than 3-10 depending on how sensitive info your site contains 3 being a bank 10 being a forum, you want to lock that IP from logging in for a while. The length, again, depends on your situation, say an 30minutes - hour for a forum to a day for a bank.
User Locking
If someone attempts to login as a user and fails a number of times, no matter from what IP, you should lock that username for a period of time. Use the above template as a guide for user locking as well.
Conditional CAPTCHA
It is very annoying to have to enter a CAPTCHA for everything all the time. And the more difficult we make them for OCR engines to decrypt the harder it is for people to get them right. Here is my solution.
If a user or ip attempts to login and fails more than twice, you should have them enter a CAPTCHA to insure they are human. This way, coupled with the locking above, you can make the CAPTCHA nice and easy to read.
Forgot Password
If a user forgets their password, never give them the exact password back, if you are following my guidelines that should be impossible anyway, have them create a new one.
Also, just don't send a request to their email. Require them to enter details to have it sent, like birth day, secret question etc.
Any failed attempts? Then follow the locking standard above.
You also want to log the IP from which the request was made, and send a confirmation to the user's email saying someone tried to request a password.
Connection
If possible, get a certificate, and use HTTPS for logins, this can help prevent sniffing, and man in the middle attacks, although will not stop them altogether, but it is much much safer. This is not required, but if it is possible is nice to use.
Creating the Module
Now you didn't think I would leave you hanging. Lets go through and create an OO interface for authentication.
Start off by making your class
CODE
class userAuth {
}
Now lets add our error string definition
CODE
class userAuth{
private $login_error_message = 'Login Failed.';
}
Lets add our escape wrapper
CODE
class userAuth{
private $login_error_message = 'Login Failed.';
public function escape($var){
// Assumes mysql connection
// Change if necessary
if(is_array($var){
foreach($var as $key=>$val){
$return[$key] = mysql_real_escape_string($val);
}
} else {
$return = mysql_real_escape_string($var);
}
return $return;
}
}
Now our hashing, encrpytion functions. When creating our encryption functions, we must return values as base64 encoded, this way all the NPC (non printable characters) are not compromised. In the encrypt function
we return encrypted and encoded string, in our decrypt function first we decode the string then attempt to decode it.
These functions are for encrypting/decrypting with AES, and hashing with SHA512, using $this->_salt for a key.
CODE
class userAuth{
private $login_error_message = 'Login Failed.';
private $_salt = 'This is my salt';
function encrypt($var){
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_256, '', 'ecb', '');
$iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
mcrypt_generic_init($td, $this->_salt, $iv);
$encrypted_data = mcrypt_generic($td, $var);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
$encoded_64=base64_encode($encrypted_data);
return $encoded_64;
}
function decrypt($encoded_64){
$decoded_64=base64_decode($encoded_64);
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_256, '', 'ecb', '');
$iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
mcrypt_generic_init($td, $this->_salt, $iv);
$decrypted_data = mdecrypt_generic($td, $decoded_64);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
return $decrypted_data;
}
public function hash($var){
$var = $this->_salt . $var;
return hash('sha512',$var);
}
public function escape($var){
// Assumes mysql connection
// Change if necessary
if(is_array($var)){
foreach($var as $key=>$val){
$return[$key] = mysql_real_escape_string($val);
}
} else {
$return = mysql_real_escape_string($var);
}
return $return;
}
}
This completes Part1, next time we will continue with our blocking and
CAPTCHA system. Stay Syndicated for more info!
If you are unable to read this fluently, or extend this, or are confused, you need to read up on some tutorials on here and good on Object Oriented Programming.
Also some of these function require other libraries, depending
on your version of php, see the following.
http://www.php.net/mcrypt
http://www.php.net/hash