Building a CHAP Login System: An Object-Oriented Approach - Putting the pieces together: integrating the “ChallengeGenerator” class
(Page 4 of 4 )
Definitely, coupling the PHP class to the existing CHAP script is pretty straightforward, since I’ll only replace the pieces of procedural code with the class itself. Considering that the previous login system is quite familiar to you, below is the complete code that implements the system, this time utilizing the “ChallengeGenerator” class that you just saw:
<?php
/*
* Begin of server-side processing
*/
class ChallengeGenerator{
// constructor
function ChallengeGenerator($clearSession=true){
if($clearSession){
$this->clearVars();
}
session_start();
}
// public method clearVars()
function clearVars(){
// destroy existing session
session_start();
session_unset();
session_destroy();
}
// public method setChallengeVar()
function setChallengeVar($name='challenge'){
if(!is_string($name)||!$name){
trigger_error('Invalid variable name');
exit();
}
// register session variable
$_SESSION[$name]=$this->getRandomString();
}
// public method getSessionVar()
function getChallengeVar($name){
if(!$_SESSION[$name]){
trigger_error('Invalid variable name');
exit();
}
return $_SESSION[$name];
}
function deleteChallengeVar($name){
if(!$_SESSION[$name]){
trigger_error('Invalid variable name');
exit();
}
unset($_SESSION[$name]);
}
// private method "getRandomString()"
function getRandomString($length=40){
if(!is_int($length)||$length<1){
trigger_error('Invalid length for random string');
exit();
}
$chars=
"abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
$randstring='';
$maxvalue=strlen($chars)-1;
for($i=0;$i<$length;$i++){
$randstring.=substr($chars,rand(0,$maxvalue),1);
}
return $randstring;
}
}
// instantiate a ChallengeGenerator object
$chlgen=&new ChallengeGenerator();
// register challenge variable
$chlgen->setChallengeVar();
/*
* End of server-side processing
*/
?>
<!doctype html public "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<title>CHAP LOGIN SYSTEM EXAMPLE</title>
<script language="javascript" src="md5.js"></script>
<script language="javascript">
/*
* validate form fields &
* implement the Challenge Handshaking Authentication Protocol
*/
function checkForm(){
valid=true;
// get 'userid' field
var usrid=document.getElementById('userid');
if(!usrid){return};
if(!usrid.value){showError(usrid,'Enter your ID')};
// get 'password' field
var psw=document.getElementById('passwd');
if(!psw){return};
if(!psw.value){showError(psw,'Enter your password')};
// get 'challenge' field
var chlng=document.getElementById('challenge');
if(!chlng){return};
// make MD5 hash of password and concatenate challenge value
// next calculate MD5 hash of combined values
chlng.value=MD5(MD5(psw.value)+'<?php echo $chlgen->getChallengeVar
'challenge')?>');
// clear password field
psw.value='';
return valid;
}
/*
* display error messages
*/
function showError(obj,message){
if(!obj.errorNode){
obj.onchange=hideError;
var p=document.createElement('p');
p.appendChild(document.createTextNode(message));
obj.parentNode.appendChild(p);
obj.errorNode=p;
}
valid=false;
return
}
/*
* hide error messages
*/
function hideError(){
this.parentNode.removeChild(this.errorNode);
this.errorNode=null;
this.onchange=null;
}
/*
* execute 'checkForm()' function when page is loaded
*/
window.onload=function(){
var W3CDOM=document.getElementById&&document.
getElementsByTagName&&document.createElement;
// check if browser is W3CDOM compatible
if(W3CDOM){
document.getElementsByTagName('form')
[0].onsubmit=function(){
return checkForm();
}
}
}
</script>
</head>
<body>
<!-- login form -->
<form method="post" action="processlogin.php">
User ID <input type="text" name="userid" id="userid"/><br />
Password <input type="password" name="passwd" id="passwd"/><br />
<input type="hidden" name="challenge" id="challenge" />
<input type="submit" name="login" value="Log In" />
</form>
</body>
</html>
With reference to the above snippet, you can appreciate that the programming logic is basically the same. I’ve only included the corresponding class for obtaining server-provided random seeds, but the rest of the script remains nearly identical. The only different piece of PHP code embedded into the markup involves calling to the “getChallengeVar()” method, illustrated in the following line:
chlng.value=MD5(MD5(psw.value)+'<?php echo $chlgen-
>getChallengeVar('challenge')?>');
To complete the example, the PHP script that runs on the server for authenticating the client might be written in the following way:
// include ‘challenge generator class’
require_once('challengeclass.php');
// include ‘MySQL abstraction class’
require_once('mysqlclass.php');
// instantiate a ChallengeGenerator object
$chlgen=&new ChallengeGenerator(false);
// get challenge variable
$challenge=$chlgen->getChallengeVar('challenge');
// get a "fresh" version of the page
header('Cache-control:must-revalidate');
// connect to MySQL
$db=new MySQLarray('host'=>'host','user'=>'user','password'=>'password',
'database'=>'database'));
// run query to obtain user data
$result=$db->query("SELECT userid,password FROM users WHERE
userid='".$_POST['userid']."'");
$row=$result->fetchRow();
// check to see if user credentials are valid
if(md5($row['password'].$challenge)==$_POST['challenge']&&$row
['userid']==$_POST['userid']){
echo '<html><head><title>Login Successful</title><body><h1>Thank you for logging in!
</h1></body></html>';
}
else{
echo '<html><head><title>Access denied!</title><h1>Access
denied!</h1></body></html>';
}
// delete session data and destroy session
$chlgen->clearVars();
Using an object-oriented approach, the authenticating script is much simpler to read and understand. First, I’ve included the “ChallengeGenerator” class along with a “MySQL” wrapping class, useful for performing database-related tasks. Then, a new “ChallengeGenerator” object has been instantiated, in order to get the “challenge” session variable previously registered on the login script. These steps are performed with the code listed below:
// instantiate a ChallengeGenerator object
$chlgen=&new ChallengeGenerator(false);
// get challenge variable
$challenge=$chlgen->getChallengeVar('challenge');
Next, the script connects to MySQL, fetches the password and user ID that matches the ID entered by the user, and finally performs the client’s authentication by comparing the MD5 hashed values, in a manner similar to the process implemented in the procedural script. The following code is responsible for authenticating the client:
if(md5($row['password'].$challenge)==$_POST['challenge']&&$row
['userid']==$_POST['userid']){
echo '<html><head><title>Login
Successful</title><body><h1>Thank you for logging in!
</h1></body></html>';
}
else{
echo '<html><head><title>Access denied!</title><h1>Access
denied!</h1></body></html>';
}
At the end of the script, all of the session data is deleted and the session itself is destroyed:
// delete session data and destroy session
$chlgen->clearVars();
Now, you have an object-oriented script that uses a CHAP system for logging into a web-based application. Of course, it’s just a matter of taste and particular needs what approach you’ll use within your programs. In either case, the system’s effectiveness is the same, since in all cases passwords are never transmitted as plain text. Even if you’re not well versed in cryptographic techniques, you can still use their benefits for improving the overall security of your website’s login forms.
Conclusion
Over this series, I’ve explained the advantages of implementing CHAP systems on login forms, as an efficient and easy-to-use technique to raise the security level for transmitting sensitive data, and particularly passwords, during the execution of web-based programs.
Ranging from a procedural approach to an object-oriented method, you’ve been provided with enough code to play with, and start using the examples for your own convenience and purpose. Of course, I must thank Paul Johnston, who gently allowed me to use his excellent JavaScript MD5 library, throughout the examples developed in this series. Happy encrypting!
| DISCLAIMER: The content provided in this article is not warranted or guaranteed by Developer Shed, Inc. The content provided is intended for entertainment and/or educational purposes in order to introduce to the reader key ideas, concepts, and/or product reviews. As such it is incumbent upon the reader to employ real-world tactics for security and implementation of best practices. We are not liable for any negative consequences that may result from implementing any information covered in our articles or tutorials. If this is a hardware review, it is not recommended to open and/or modify your hardware. |