One of the questions which gets asked a lot is how do I store passwords for connections to the IBM i. If I am doing internal development (does not require security to be too strict) I will usually store the password in plain text because for anyone to get to the passwords they would have to be able to access the IBM i IFS and look through the correct directory to find the appropriate session data to get to the password. Then they have to link that up with an appropriate profile! That is not something anyone in our company will spend the time doing or in most instances have the knowledge to do.
In a production application which could be exposed to outside influences we would need to harden up the security somewhat. Normal users may still have problems getting to the data but a determined hacker will not be phased by having to troll through the session variables.. So we have to come up with a way to store the data in an encrypted manner and which requires a number of session data variables to be combined to make it work. We would still strongly advise those of you who are walking down this route to consider additional security measures such as locking down the directory where the session variables are stored to PUBLIC *EXCLUDE, as a minimum.
PHP provides a number of encryption modules which have to be enabled for the following code to work, if you need help on setting this up there are plenty of links in Google to help you and it is beyond the scope of this post to add here. Once the modules are enabled and the servers re-started you can start to code up something similar to the following.
First of all we need a few functions which will be called to do the encryption for us. We have 3 functions defined, one to generate the initialization value and the unique key value we will use to encrypt and decrypt the data, another one to encrypt the plain text password and another to decrypt it.
Here is the code for the unique value generation.
/*
* function create hex value
* Used to create initialization variables and unique keys
* @parms
*
*/
function get_unique_key($len) {
// create a unique value in the string for the specified number of bytes
$data = '';
do {
$data .= hash('sha256', uniqid(mt_rand(), TRUE), TRUE) ;
} while ( strlen($data) < $len ) ;
return $data;
}
All it is doing is creating a unique string to the length passed in and returning that string to the calling function. The PHP manual gives you the information on the functions used and plenty of examples. We are passing in a random number as a prefix to the uniqid() function which generates a 23 character string that is hashed and appended to the previous request until the required minimum length is reached (in fact it will normally be exceeded unless the required length is an exact multiple of 23).
The next function will do the encryption.
/*
* function to encrypt password
*/
function e_pwd($pwd) {
$alg = MCRYPT_RIJNDAEL_256 ;
$m = MCRYPT_MODE_CBC ;
// get the length the initialization value
$_SESSION['i_len'] = mcrypt_get_iv_size($alg, $m);
$_SESSION['init_val'] = substr(get_unique_key($i_len), 0, $i_len) ;
// get the length the key needs to be for the encryption
$_SESSION['key_len'] = mcrypt_get_key_size($alg, $m);
// the returned value will be padded with 0's so strip off
$_SESSION['key'] = substr(get_unique_key($_SESSION['key_len']),0,$_SESSION['key_len']);
$_SESSION['pwd'] = mcrypt_encrypt($alg, $_SESSION['key'], $pwd, $m, $_SESSION['init_val']);
return;
}
The above code is using session variables to store the keys and lengths plus the encrypted password. The above function receives the plaintext password via the login script that is called when the form submit button is pressed. The password is passed to the script in a POST variable called 'pwd'. We store the various initialization and key values as SESSION variables in the above module so we can use them in the decrypt function.
Here is the decrypt function, as you can see it is very simple. We could store the algorithm and mode as well outside of the script but for this test it was not important. All we have to do is pass in the session variables to the decrypt() function and return the results.
/*
* function to de-encrypt password
*/
function d_pwd() {
$alg = MCRYPT_RIJNDAEL_256 ;
$m = MCRYPT_MODE_CBC ;
return mcrypt_decrypt($alg, $_SESSION['key'], $_SESSION['pwd'], $m, $_SESSION['init_val']);
}
So based on the above functions here is how we used them in our pages. First when a user logs in we have a form which has a password field and user field defined. This is only part of the script to generate the page, before this we have a check to see if the user is already signed on. We only show this bit so you can see where we manipulate the password data.
<h3>Sign On</h3>
<form name=login method="post" action="scripts/login.php">
<table width="20%" align="center" border="1" cellpadding="1">
<tr><td><label>User ID :</label></td><td><input type="text" name="usr" /></td></tr>
<tr><td>Password:</td><td><input type="password" name="pwd" /></td></tr>
<tr><td colspan="2" align=center><input type="submit" value="Log in" /></td></tr><?php
if(isset($_SESSION['Pwd_Err'])) {
if($_SESSION['Pwd_Err'] == 1) { ?>
<tr><td colspan="2" align=center>Sorry the credentials were rejected by the <?php echo($_SESSION['sys']); ?> System</td></tr><?php
$_SESSION['Pwd_Err'] = 0;
}
} ?>
</table>
</form>
Here is part of the login script that encrypts the password and stores the user name etc.
// store the user and password(encrypted)
$_SESSION['usr'] = $_POST['usr'];
e_pwd($_POST['pwd']);
$_SESSION['server'] = "ServerName";
// if failed to connect set the $_SESSION variables to empty
if(connect($conn) == -1) {
$_SESSION['Pwd_Err'] = 1;
$_SESSION['usr'] = "";
$_SESSION['pwd'] = "";
header('Location: /index.php');
exit(0);
}
And finally this is a function which does the connection.
function connect(&$conn) {
// connect to the i5
$conId = 0;
if (isset($_SESSION['ConnectionID'])) {
$conId = $_SESSION['ConnectionID'];
}
$server = $_SESSION['server'];
// options array for the private connection
$options = array(I5_OPTIONS_PRIVATE_CONNECTION => $conId,
I5_OPTIONS_IDLE_TIMEOUT => $_SESSION['timeout'],
I5_OPTIONS_JOBNAME => 'PHPTSTSVR');
// connect to the system
$conn = i5_pconnect($server,$_SESSION['usr'],d_pwd($_SESSION['pwd']),$options);
// if connect failed
if(is_bool($conn) && $conn == FALSE) {
$errorTab = i5_error();
if ($errorTab['cat'] == 9 && $errorTab['num'] == 285){
$_SESSION['ConnectionID'] = 0;
$_SESSION['Err_Msg'] = "Failed to connect";
return -1;
}
else {
//set the error message
$_SESSION['Err_Msg'] = "Connection Failed " .i5_errormsg();
// send back to the sign on screen
$_SESSION['ConnectionID'] = 0;
return -1;
}
}
return 1;
}
That's it, now when a user gets past the login screen the data is stored in session variables that can be retrieved and decrypted. We have used the i5_toolkit functions to do the connections because we use them for all data and object collection from the IBM i but, you could use the same principle for other connections as well. To force the user to sign back in you can use the session_destroy() function to remove all of the session variables at once.
Hope you find this interesting, if you have any questions or suggestions for improving the code let us know.
Chris...