Challenge/Response Login Architecture not Functioning
Posted: Sun Aug 31, 2008 12:17 am
Well, I'm ashamed to bring it here, as I'm under the impression it's something small I'm overlooking. But I've spent the past two days slaving over this (relatively) simple code. It's based on (and almost exactly) the code in this wonderful tutorial. I put it into two separate classes that are called based on whether we're processing the login, or the form page. The returned challenge on the challenge method is then put into a hidden field called challenge. Basically, exactly as that tutorial demonstrates.
I really did my best to find this problem on my own. I've tested every step of it with various var_dumps, and other techniques. I just can't find out what's wrong! The response and the challenges do not match, even though I've confirmed that the same challenge is being used, that the same password is being used, and that the same username is being used. I've checked all the queries, all the variables, everything. I have no idea what's wrong.
The wackiest thing of all, is that sometimes, it does work. At least, at the beginning, it worked perfectly. I don't recall changing anything--and even as I'm testing it, every once in a while, it'll log me in! Then I clear cookies, and try again as if I'm a new user, and it's wrong again.
Any help would really be appreciated.
Library page:
Javascript function that is called onsubmit:
I really did my best to find this problem on my own. I've tested every step of it with various var_dumps, and other techniques. I just can't find out what's wrong! The response and the challenges do not match, even though I've confirmed that the same challenge is being used, that the same password is being used, and that the same username is being used. I've checked all the queries, all the variables, everything. I have no idea what's wrong.
The wackiest thing of all, is that sometimes, it does work. At least, at the beginning, it worked perfectly. I don't recall changing anything--and even as I'm testing it, every once in a while, it'll log me in! Then I clear cookies, and try again as if I'm a new user, and it's wrong again.
Any help would really be appreciated.
Library page:
Code: Select all
public function Challenge(){
/*
We will use feyd's SHA256 PHP implementation to support SHA256 (does not require mcrypt enabled).
Depending on where you get your version ensure no echo() statements are left uncommented out
*/
require_once('sha256.inc.php');
//Create the challenge, baby.
$challenge = SHA256::hash(uniqid(mt_rand(), true));
//Use extended class to connect to db
$dbh = $this->Connect();
//We want our challenges to last only 5 minutes. Anything older, with the same Session ID, mind you, must be killed.
$timeDate = time();
//Delete the challenges from the same session ID and from 360 seconds ago
$prepared = "DELETE FROM challenge WHERE timeDate < " .$timeDate ." OR sessID = '" .session_id() ."'";
$dbh->exec($prepared);
//Store that sucker in the database. We give it's timestamp an exgtra 360 seconds, so as to provide those extra minutes before we screw it
$time = time() + 360;
$prepared = "INSERT INTO challenge SET challenge = :challenge, sessID = :sessID, timeDate = :timeDate";
$insert = $dbh->prepare($prepared);
$insert->bindParam('challenge',$challenge);
$insert->bindParam('timeDate',$time);
$insert->bindParam('sessID', session_id());
$insert->execute();
return $challenge;
}
public function Auth(){
//Include Feth's SHA256 class
require_once('sha256.inc.php');
//Connect to DB
$dbh = $this->Connect();
//Basic validation
if(isset($_POST['response']) && !empty($_POST['response']) && (!ctype_alnum($_POST['username']) || !ctype_alnum($_POST['response'])))
{
// we may log bad data, or make the user walk the plank for their trouble!
die('Bad Input: Response or username are not alphanumeric!');
}
if(isset($_POST['password']) && !empty($_POST['password']) && (!ctype_alnum($_POST['username']) || !ctype_alnum($_POST['password'])))
{
// log or keel-haul the swabbies!
die('Bad Input: Password or username are not alphanumeric!');
}
//Okay, if we're still alive, we get the challenge.
$prepared= "SELECT challenge FROM challenge WHERE sessID = :sessID AND timeDate > :timeDate";
$select = $dbh->prepare($prepared);
$select->bindParam('sessID',session_id());
$select->bindParam('timeDate', time());
$count = $select->execute();
if($count == 0){
//We timed out, deal with appropriately
}
//So, now we have our array. Assign the challenge
$selected = $select->fetch();
$challenge = $selected['challenge'];
//Now, we have to prepare what the expected response
//Select the user info.
$prepared = "SELECT * FROM user_accounts WHERE username = :username";
$select = $dbh->prepare($prepared);
$select->bindParam('username',$_POST['username']);
$count = $select->execute();
$selected = $select->fetch();
//Ensure the user exists
if($count == 0){
///user does not exist, deal with appropriately.
}
//Assign the array appropriately
$userInfo = $selected;
//Okay, so, we already have the hashed copy of the user password. We also have the username from the posted array, and we have the challenge from the db
//Let's generate that response, baby!
$responseString = strtolower($userInfo['username'].':'.$userInfo['password'].':'.$challenge);
$expectedResponse = SHA256::hash($responseString);
//So, here's the poing of it all. We're now checking to see if the hashes are the same.
if($_POST['response'] == $expectedResponse)
{
$_SESSION['authenticated'] = 1;
$_SESSION['userid'] = $userInfo['userid'];
$_SESSION['username'] = $userInfo['username'];
header('Location: ../index.php?login=successful');
}
elseif(isset($_POST['userpass']) && !empty($_POST['userpass']))
{
//So, this is if the user had javascript disabled. So, we hash their password, because the javascript didn't do it. Then compare it to our hashed password in the db
if(SHA256::hash($_POST['userpass']) == $userInfo['password'])
{
/*
Submitted plain text password from non-js client, when hashed, agrees to database stored password hash
We authenticate the User
*/
$_SESSION['authenticated'] = 1;
$_SESSION['userid'] = $userInfo['userid'];
header('Location: ../index.php?login=successful');
}
else
{
//So, we still are in the conditional statement saying that we have a nonempty password field, but it doesn't match.
//This means that the user has the wrong password, javscript or not.
$_SESSION['authenticated'] = 0;
header('Location: login.php?error=pass');
}
}
else
{
/*
At this point:
- The client Response does not agree with the server generated Expected Response
This login attempt has failed - we should direct user to try again.
*/
header('Location: login.php?error=response');
}
}
}Code: Select all
function doChallengeResponse() {
str = document.login_form.username.value.toLowerCase() + ":" +
sha256_digest(document.login_form.userpass.value) + ":" +
document.login_form.challenge.value;
document.login_form.userpass.value = "";
document.login_form.challenge.value = "";
document.login_form.response.value = sha256_digest(str);
return false;
}