0

I have a website where I do the following in order to let people log in:

jQuery: - using the $.md5() plugin and the $.cookie() plugin (for CodeIgniter CSRF protection cookie for post data)

$('#login').on('submit', function(e){
  e.preventDefault();
  $.get('/salt/', function(salt){
  // get / set salt and salt flashdata
    $.post('/login/', {
      'username' : $('#l_username').val(),
      'password' : $.md5('' + salt['m'] + $.md5($('#l_password').val()) + salt['s']),
      //set to string (in case MD5 hash of password is an integer)hash password, add salt, hash salted password. 
      '_token' : $.cookie('_cookie')
    }, function(r){
      // *r* == 0: unknown user; 1: wrong password; 2: logged in; 3: database error; 4: session error;
      // perform action depending on *r* value
    })
  })
});

PHP (CodeIgniter): - rout file forwards /salt/ and /login/ to /process/salt/ and /process/login/ - 'session' class is autoloaded

class Process extends CI_Controller {

  public function __construct() {
    parent::__construct();
    $this->load->model('login'); // loads model with login functions ()
  }

  public function login() {
    $data = array();
    $data['username'] = $this->input->post('username');
    $data['password'] = $this->input->post('password');
    if ($data['username'] && $data['password']) {
    //if user and password are set
      $user = $this->login->getUser($data['username']);
      if ($user->num_rows() === 1) {
      // if the username is in the database
        $user = $user->row();
        $salt = $this->session->flashdata('s');
        //flashdata set by ajax call to '/salt/'
        $pass = $salt->m . $user->password . $salt->s;
        //password is already hashed in database
        if ($data['password'] === $pass){
          // update logged_in database table then set login session
          echo 2; // logged in; if database / session error, echo 3 / 4
        } else {
          echo 1; // wrong password
        }
      } else {
        echo 0; //unknown user
          }
    }
  }

  public function salt() {
    // set salt values based in minute and second time
    $salt = (object) array('m' => intval(date('i')), 's' => intval(date('s')));
    //intval() removes leading 0
    $this->session->set_flashdata('s', $salt);
    // flashdata only valid for the next server request (which is the login);
    header('Content-type: application/json');
    //return value as json string
    echo json_encode($salt);
  }
}

My question is this: exactly how secure is this log in method? Are there any potential loopholes in the script? I want to make sure that the log in process is as secure as possible without https for the moment.

Andrew Willis
  • 2,289
  • 3
  • 26
  • 53

1 Answers1

0

If I'm guessing correctly, you are storing a md5-hashed version of the password in your database?

Would it not be better to store a hash of the password with a global salt and pass that global salt as well to the javascript? This way you don't have a hash in your database which is pretty easy to decifer if it's an easy password.

Even better would be to use a personal salt for each user, which you can get at the same time as the other salts.

'password' : $.md5('' + salt['m'] + $.md5($('#l_password').val() + salt['personal']) + salt['s']),

Another advice would be to use a stronger hashing function than md5 since that isn't so secure anymore.

This is all however in case your database gets leaked. The transfer of the password and username looks pretty secure and obfuscated though.

I think you still have a bug, since you don't make a md5 hash of your salted password on the php-side when comparing it to the received value. I think it should be:

//flashdata set by ajax call to '/salt/'
$pass = md5($salt->m . $user->password . $salt->s);

For hashing on the serverside could you use the spark-version of phpass, which everyone advices to use. Or you could just install it as a library.

Zombaya
  • 2,230
  • 23
  • 30
  • Yeah, I noticed and fixed that hash problem in php just after I posted the question. Since the Username is a unique element, do you think it would be ok to use that as the personal salt? or the user ID (which is an integer)? I was thinking about using SHA256 rather than MD5 for everything to be honest, I just said MD5 to avoid the 'which hash algorithm' argument which would boil on for centuries! – Andrew Willis May 05 '12 at 16:45
  • I think the userID isn't such a good idea since that wouldn't be a very long string, probably. [Salts](http://en.wikipedia.org/wiki/Salt_(cryptography)) are generally longer strings to make it harder to crack by [brute force](http://en.wikipedia.org/wiki/Key_stretching). – Zombaya May 05 '12 at 17:02
  • Also check out this other question on salting: http://stackoverflow.com/questions/401656/secure-hash-and-salt-for-php-passwords – Zombaya May 05 '12 at 17:40
  • Do you think creating a function to hash a password with SHA256 1000 times (as seems to be suggested) with it hashed initially with a random salt and encryption key would be better? Then use that function to hash incoming passwords? It would have to be initially encrypted before the salt is added, of course. – Andrew Willis May 06 '12 at 00:15
  • I am actually concerned about sending the salt to JavaScript via AJAX as anybody with a mind to hack the site would be able to easily find the salt, which apparently makes it easier to hack. I am tempted to double / triple hash the password in the JavaScript using SHA256 then salt it, hash again and send this to the server for processing... Or maybe SHA512... nothing wrong with being extra-cautious! – Andrew Willis May 06 '12 at 00:26
  • 3
    If you're hashing in JS, anyone stepping through the source will be able to see your algorithm. I don't think there's any reason to manipulate the password at all in the front end - send it to the server side as is and then perform your hashing method of choice there. Bcrypt will amongst your best bets as far as security goes and it's easy to implement. – ObjectiveCat May 06 '12 at 08:44
  • ObjectiveCat is correct about that. If you remove the dynamic hashes generated by `date()`, you could still keep hashing on the clientside and hash it once more on the serverside and then compare it to the database version. Just using SSL would still be a lot easier :-). I googled a bit and saw that you can get free ssl-certicates from startssl.com, if you are possible to install it on your server. – Zombaya May 06 '12 at 17:33