EllisLab text mark
Advanced Search
1 of 2
1
   
Best way to handle passwords using CI library?
Posted: 14 February 2008 10:01 PM
Avatar
Joined: 2007-03-29
37 posts

I read this page and it shows some options: http://ellislab.com/codeigniter/user-guide/libraries/encryption.html Which one is the recommended way to handle passwords, encrypting + salt, and then comparing the supplied pass with the one in the database, thanks in advance.

 Signature 

Code is poetry.

 
Posted: 15 February 2008 02:47 AM   [ # 1 ]   [ Rating: 0 ]
Joined: 2006-07-14
4237 posts

i use following method to check if the login is valid

function valid_login($username,$password)
{
    $query 
$this->db->select('password')->from('users')->where('username',$username)->get();
    if(
$query->num_rows() == 0)
    
{
        
return false;
    
}
    
else
    
{
       $this
->load->library('encrypt');
       foreach(
$query->result as $row)
       
{
          
if($this->encrypt->decode($password) == $row->password)
          
{
             
return true;
          
}
       }
       
return false;
    
}

note : this works only in php5. for php4 you need to split the building of the query statement and use result_array in the loop.

 
Posted: 15 February 2008 02:53 AM   [ # 2 ]   [ Rating: 0 ]
Avatar
Joined: 2007-06-10
2937 posts

Mine is almost identical to xwero

function try_login($attempt)    //$attempt is an object
{        
    
if ($attempt->password)    
    
{
        
//find user, check password & create cookie if valid
        
if ($user $this->users->findBy("`username` = '{$attempt->username}'") AND $attempt->password == $this->encrypt->decode($user->password))    
        
{
            set_cookie
('ci_user'$user->uid0); //cookie expires on browser close
            
if ($attempt->rememberset_cookie('ci_login'$user->uid86500);
            return 
TRUE;
        
}
    }
    
return FALSE;
 Signature 

URI Language Identifier | Modular Extensions - HMVC | View Object | Widget plugin | Access Control library

 
Posted: 15 February 2008 07:44 AM   [ # 3 ]   [ Rating: 0 ]
Avatar
Joined: 2006-03-26
169 posts

Hey guys…

Just wanted to make a few comments regarding passwords and security.

The ideal situation is to have a password saved in the database that CANNOT be converted back to plain text under any circumstance.

So, the best practice would be to do something like this:

First of all, set an encryption key in your config.php…
and make it nice and random using a random string generator like this one (make it 32 characters, upper and lower case, including numbers. (as recommended by the user guide)

$config['encryption_key'"NeO5C88iv7uo09U2E20iJF0iUiz8R9zm"

Now write this down, and put it somewhere safe… and I mean physically write it down… like analogue, on paper….
Because all your passwords are going to use this, and if ever you lose your config.php file, you must restore it and put in the same key.

Now, let’s look at the library.

function _prep_password($password)
{
     
return sha1($password.$this->config->item('encryption_key');
}

function login($username$password)
{
     $this
->db->where('username'$username)l
     $this
->db->where('password'$this->_prep_password($password));

     
$query $this->db->get('users'1);

     if ( 
$query->num_rows() == 1)
     
{
           
// set your cookies and sessions etc here
           
return true;
     
}

     
return false;

Using the ‘encryption_key’ at the end of the password means that your generated sha1 string will not be the same as the sha1 string for just the password on its own. (which stops dictionary sha1 attacks).

In the ‘good old days’, when the famous ‘PHPNuke’ was extremely popular, people would find SQL injection holes to run their own queries:

$search $_POST['search'];

$query 'select * from articles where title='$search'; 

But, if you post the search term:

'; select * from users where id = '

the final sql would be:

select from articles where title=''select from users where id '1'

Then, by finding the password, (which was only encrypted using MD5($password) without a salt, the intruder could use a dictionary attack like:

$stolen_md5 'ainofdifdofij'// Yes, I know an MD5 is only 0-9 and a-f chars.

foreach ($dictionary as $word)
{
    
if (MD5($word) == $stolen_md5)
    
{
        
echo 'password is: '.$word;
        exit;
    
}

So, by using a salt (the encryption key tagged on the end of your password)... the result sha1 is nothing like the sha1 for the password on its own. Stopping dictionary attacks.
And, the password, even if retrieved from the DB could not be converted back to a ‘real’ string, because sha1 is a 1-way hashing algorithm.

Simply using $this->encrypt->encode() is dangerous, because if someone gained your key, they have the means to convert your passwords back to plain text. (granted, it’s not easy… but still possible).

when you insert a user:

$data = array('username' => $username'password' => $this->_prep_password($password));
$this->db->insert('users'$data); 

So, there’s a little example of a ‘very good’ practice when it comes to passwords and databases…
With an explanation into ‘why’ do it this way.

Hope this is useful.

 Signature 

On the first day, God created CodeIgniter… Then he could really get some work done!

Elliot Haughin CodeIgniter Blog |
Twitter

 
Posted: 15 February 2008 08:03 AM   [ # 4 ]   [ Rating: 0 ]
Avatar
Joined: 2007-06-10
2937 posts

Very clever thanks Elliot.

Personally I do use the encryption salt. (Thanks to Micheal Wales comments a while back)

But I still like to display a string of * equivalent to the length of the real user password in the user account editor, this requires encrypt->decode to work.

$users_account->hidden_pwd str_repeat('*'strlen($this->encrypt->decode($users_account->password))); 

I’m sure I could use your idea and still provide that functionality.

 Signature 

URI Language Identifier | Modular Extensions - HMVC | View Object | Widget plugin | Access Control library

 
Posted: 15 February 2008 08:16 AM   [ # 5 ]   [ Rating: 0 ]
Avatar
Joined: 2006-03-26
169 posts

But… that’s not how you should do it…

Keep with the 40 char super strong sha1 in the db, and let your interface suffer a little for the sakes of a huge security increase.

Just have:

Old Password:
New Password
New Password (again)

Text boxes…. And have them all blank as default.
This is how many of the ‘big players’ do it.

NEVER sacrifice security for usability or design…
It’s false economy, your site wont look and feel as nice if it gets hacked.

 Signature 

On the first day, God created CodeIgniter… Then he could really get some work done!

Elliot Haughin CodeIgniter Blog |
Twitter

 
Posted: 15 February 2008 08:20 AM   [ # 6 ]   [ Rating: 0 ]
Avatar
Joined: 2007-06-11
2987 posts

Well the two are slightly different, I preffer the second. Putting both checks in the DB makes things alot quicker and reduces the code alot. Why not chuck in some insensitivity checks?

"LOWER(username)` = '".strtolower($attempt->username)."'"
 Signature 

————————
Blog | Twitter | GitHub | BitBucket
————————-
PyroCMS - open source modular CMS built with CodeIgniter
PancakeApp - Simple, hosted invoicing/w project management

 
Posted: 15 February 2008 08:25 AM   [ # 7 ]   [ Rating: 0 ]
Joined: 2006-07-14
4237 posts

I understand the security risks but from most clients we get the request to see the passwords from the users.
Because a lot of people just don’t want to go through the whole request new password -> validate profile -> generate new password routine they just email the site admin to send their password.

It are real world situations that make you not use the most secure methods.

 
Posted: 15 February 2008 08:27 AM   [ # 8 ]   [ Rating: 0 ]
Avatar
Joined: 2006-03-26
169 posts

Case insensitivity is a good idea…

But instead of doing:

"LOWER(username)` = '".strtolower($attempt->username)."'"

Just do:

$this->db->where('username'strtolower($username)); 

Then make your ‘insert’ convert the username into lowercase before it goes into the db.
That way, you know that all usernames in the DB are lowercase and you dont have to run LOWER() on every select request.

 Signature 

On the first day, God created CodeIgniter… Then he could really get some work done!

Elliot Haughin CodeIgniter Blog |
Twitter

 
Posted: 15 February 2008 08:29 AM   [ # 9 ]   [ Rating: 0 ]
Avatar
Joined: 2007-06-11
2987 posts
Elliot Haughin - 15 February 2008 01:16 PM

But… that’s not how you should do it…

Keep with the 40 char super strong sha1 in the db, and let your interface suffer a little for the sakes of a huge security increase.

Just have:

Old Password:
New Password
New Password (again)

Text boxes…. And have them all blank as default.
This is how many of the ‘big players’ do it.

NEVER sacrifice security for usability or design…
It’s false economy, your site wont look and feel as nice if it gets hacked.

This is the same internal dialog I was having. CI’s built in encode and decode functions seemed insecure to me, so I switched BambooInvoice to using SHA1, but then after a little while I realised they were actually just as secure. Yes the encode()‘s strings can be decoded, but not if they are using an encryption key. With a key in your CI config file the encode/decode functions are JUST as secure as using SHA1, with extra benefits like being able to show the length of a password for the interface.

You can save yourself the prep_password() function by using the built in $this->encrypt->sha1() password, I believe that automatically includes the encyption key.

 Signature 

————————
Blog | Twitter | GitHub | BitBucket
————————-
PyroCMS - open source modular CMS built with CodeIgniter
PancakeApp - Simple, hosted invoicing/w project management

 
Posted: 15 February 2008 08:48 AM   [ # 10 ]   [ Rating: 0 ]
Avatar
Joined: 2006-03-26
169 posts

so I switched BambooInvoice to using SHA1, but then after a little while I realised they were actually just as secure. Yes the encode()’s strings can be decoded, but not if they are using an encryption key.

The fact of the matter is, if one method can be converted back to plain text ‘some how’ (with the encryption key), and the other method can’t be converted back to plain text, then it is by very nature more secure.

The reason I used the _prep_password function is because you could mix it up a bit and build your own ‘special’ crazy salting method to make it extremely secure:

return sha1($password.$this->config->item('encryption_key');

// OR
return sha1($this->config->item('encryption_key').$password);

// OR 
// Build a salty beast like

$password 'password_magic';

$password_chars str_split($password);
$key_chars str_split($this->config->item('encryption_key'));


foreach(
$key_chars as $key => $char):

     
$salted_up .= $char;
     
$salted_up .= (!empty($password_chars[$key])) ? $password_chars[$key] '';

endforeach;

// output would be: NpeaOs5sCw8o8ridv_7muaog0i9cU2E20iJF0iUiz8R9zm 

The more ‘bespoke’ you can make your hashing algorithm, the better.
The above, which merges the characters, assumes that the encryption_key is always longer than the password.

 Signature 

On the first day, God created CodeIgniter… Then he could really get some work done!

Elliot Haughin CodeIgniter Blog |
Twitter

 
Posted: 15 February 2008 09:06 AM   [ # 11 ]   [ Rating: 0 ]
Joined: 2006-11-29
52 posts
wiredesignz - 15 February 2008 01:03 PM

Very clever thanks Elliot.
But I still like to display a string of * equivalent to the length of the real user password in the user account editor, this requires encrypt->decode to work.

No it doesn’t. Store an extra field that records the string length of the user’s password, and display that. Update it whenever the user changes their password.

Always encrypt passwords with a salt, ALWAYS. This shouldn’t even be debatable!

 
Posted: 15 February 2008 09:07 AM   [ # 12 ]   [ Rating: 0 ]
Avatar
Joined: 2006-08-11
103 posts

A small contribution of a different secure alternative that has worked for me so far: Using PUBLIC/PRIVATE keys

It’s not hard to obtain the packets that are sent through a network connection so, if the password “as is” is being sent, then anyone listening to that connection can obtain it.
Hashing the password before it’s sent is not sufficient either because the hash will always look the same and the server will allways be expecting the same SHA1 (or md5) string.

So my solution is:
1. When the page is generated, create a public key and store it as flash data in the session. It has to be randomly generated in the moment and can have any length. The public key is sent to the browser (in javascript code or similar)
2. When the user enters his login information, before sending the password (the private key) on the net, append the public key at the end of the password and then MD5 the resulting string.
3. On the server, retrieve (and decrypt) the user’s password, then append the public key to it and MD5 it. If both MD5s look the same, then the login is succesful
4. If the login is not succesful, generate a new public key and discard the previous one.

I think this is the most secure way

 Signature 

Security Code Review

 
Posted: 15 February 2008 09:25 AM   [ # 13 ]   [ Rating: 0 ]
Joined: 2006-07-14
4237 posts
barbazul - 15 February 2008 02:07 PM

It’s not hard to obtain the packets that are sent through a network connection so, if the password “as is” is being sent, then anyone listening to that connection can obtain it.

I think the best solution for this security risk is using an https connection?

 
Posted: 15 February 2008 09:31 AM   [ # 14 ]   [ Rating: 0 ]
Avatar
Joined: 2006-03-26
169 posts

No it doesn’t. Store an extra field that records the string length of the user’s password, and display that. Update it whenever the user changes their password.

Always encrypt passwords with a salt, ALWAYS. This shouldn’t even be debatable

Your dead right… I wouldn’t even bother storing the length of the password, since it’s a another ‘clue’ stored away…
Just give the user a blank box… it doesn’t matter if it’s got 9 stars in it or 8, or none…
The user isn’t going to sit and count the number of stars in the box, in-fact, they probably don’t care.

A small contribution of a different secure alternative that has worked for me so far: Using PUBLIC/PRIVATE keys

I think there’s definitely a good point there.
But, this can all be done just using SSL, which means that all data sent back and forth from the client and server is automatically encrypted/decrypted.

The main problem is, if you do some form of hashing client-side on the password with a public key appended, it needs to be a 2-way encryption method so on the server side you can ‘decrypt’ the password/key hash to then test it against your secure _prep_password($password) database stored password.

And, if the encryption is performed client-side, a hacker could look at the hashing algorithm (because it has to be a js function or something), and work out how the ‘hashed’ transmitted password is built.
With this information, they can take the ‘hashed’ transmitted password, and reverse-code the clientside hashing script to produce the original transmitted password.

Which is where SSL really comes into its own… its fully encrypted transmission of requests is only possible to encode/decode with the SSL certificates located on your server, and married to your unique domain name.

Edit: xwero beat me to the SSL!

Ah, well, I hope this post explains why SSL is much better than any client-side hashing.

 Signature 

On the first day, God created CodeIgniter… Then he could really get some work done!

Elliot Haughin CodeIgniter Blog |
Twitter

 
Posted: 15 February 2008 09:52 AM   [ # 15 ]   [ Rating: 0 ]
Avatar
Joined: 2007-06-11
2987 posts
Elliot Haughin - 15 February 2008 01:48 PM

The fact of the matter is, if one method can be converted back to plain text ‘some how’ (with the encryption key), and the other method can’t be converted back to plain text, then it is by very nature more secure.

The point of an extra field storing the length is of course a good idea, but I will continue with my earlier point. If you consider yourself a hacker and you are writing a zombie script that will try to guess someones password. Think of the difference in the code that would be required… there are hardly any!

Hacking a SHA1 you would just guess words or autoincrement two strings that would check the pass and the key against the SHA1 hash… right? Thats pretty identical to autoincrementing a string to decrypt and see if it makes an actual word instead of random gibbery, even then you will get many false returns.

Cant see there being any logical difference there, but im open to suggestions.

 Signature 

————————
Blog | Twitter | GitHub | BitBucket
————————-
PyroCMS - open source modular CMS built with CodeIgniter
PancakeApp - Simple, hosted invoicing/w project management

 
1 of 2
1