This tutorial is intended for those PHP developers who want a password encryption routine that is reversible - i.e. the encrypted password can be decrypted back into plain text. The reason for wanting such a routine could be that your web hosting service does not include an acceptable encryption module in his PHP build, or perhaps you want a routine that allows a certain amount of customisation that makes it totally different from the encryption routines used by others.
A working PHP development environment. Expert knowledge of PHP is not required, although an understanding of classes and OOP (Object Oriented Programming) would be useful.
Many different methods of encryption have been used over the years, some are simple and can be broken easily while others are more complex and therefore more difficult to break.
In this method each character is converted into a number, another number is added to it, then it is converted back into a different character from the same character set. This is how the ROT13 system works as it rotates each character 13 positions. This system is easy to break.
A better method is to use two different character sets - one for obtaining the index number of the source character (STRING1), and a second for converting this index number into a different target character (STRING2). The advantage is that there is no fixed rotation between one character and the next, which would make it difficult for any potential hacker. The disadvantage is that each source character will always be converted to the same target character, so once a source and target pair have been found they will always remain the same. This is not good.
This involves an additional step in between converting a character into an index number using STRING1 and converting that index number into a character from STRING2. This additional step adjusts the index number at runtime according to some fixed rules. The rules must be fixed so that the same source string will always result in the same target string, otherwise it will not be possible to verify a password with one that is held on file. This method means that if the same character appears in the input string more than once that it will not generate the same corresponding character in the output string, thus making the hacker's job that much more difficult.
A common method is to use a separate character string as a 'key'. This key is reduced to an array of numbers, and it is this array of numbers that is used to adjust the index number between STRING1 and STRING2. The method I use is to take the first number from the array, add it to the index number, then move this number from the beginning to the end of the array in a rotation movement.
The beauty of this method is that even if you know the exact encryption algorithm being used it is totally useless without the key.
By this I mean taking a routine that is freely available, such as this one, and customising it so that it produces totally different results from any other implementation. This customisation may be done at two levels:
The first method is something that should be left to someone who knows what he's doing as making a mistake can result in a broken algorithm. The second method is as simple as supplying the routine with the values that you want it to use.
In order to demonstrate that this routine does in fact encrypt a text string and decrypt it back into the original text I have created a test harness which looks like the following:
The fields labelled 'key' and 'password' should be self-explanatory. The 'password length' field is there to ensure that a shorter password is padded out with spaces so that the length of the encrypted string does not give away the length of the original string. Notice how each of the trailing spaces in the above example has been converted into a totally different character.
'Adjustment' is a value that is added to the value obtained from the 'key' array in each processing cycle. By allowing decimal places it means that the resulting number may be rounded either up or down, which causes the number to be incremented again on occasions.
'Modulus' is another value that is used to adjust the index number between STRING1 and STRING2. If the value can be divided by 'modulus' with no remainder then it is multiplied by -1 which reverses the sign. This effectively means that when picking out characters from the string you may sometimes start from the beginning and search forwards, while at other times you start from the end and search backwards.
You can run the test harness by clicking here. You can view the source for the test harness by clicking here.
This routine is provided in the form of a class with properties (variables) and methods (functions). Below are commented highlights, but the entire class file can be viewed here.
This first section of code defines the class name and identifies the variables that are available throughout the class.
class encryption_class { var $scramble1; // 1st string of ASCII characters var $scramble2; // 2nd string of ASCII characters var $errors; // array of error messages var $adj; // 1st adjustment value (optional) var $mod; // 2nd adjustment value (optional)
This function is automatically called whenever an object is created from this class. I use this to set up the contents of the two strings which I call scramble1
and scramble2
. Each of these strings contains exactly the same characters, but in a different order. In this case I am using all 95 printable characters from the ASCII character set EXCEPT single quote, double and backslash as these have special meaning in PHP which can sometimes lead to problems.
I also use the class constructor to define default values for adj
and mod
.
function __construct () { // Each of these two strings must contain the same characters, but in a different order. // Use only printable characters from the ASCII table. // Do not use single quote, double quote or backslash as these have special meanings in PHP. // Each character can only appear once in each string. $this->scramble1 = '! #$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $this->scramble2 = 'f^jAE]okIOzU[2&q1{3`h5w_794p@6s8?BgP>dFV=m D<TcS%Ze|r:lGK/uCy.Jx)HiQ!#$~(;Lt-R}Ma,NvW+Ynb*0X'; if (strlen($this->scramble1) <> strlen($this->scramble2)) { trigger_error('** SCRAMBLE1 is not same length as SCRAMBLE2 **', E_USER_ERROR); } // if $this->adj = 1.75; // this value is added to the rolling fudgefactors $this->mod = 3; // if divisible by this the adjustment is made negative } // __construct
This function will take a plain text string and encrypt it using the supplied key. Notice that the string length defaults to zero.
function encrypt ($key, $source, $sourcelen=0) {
The first step is to convert the key into an array of numbers which I call $fudgefactor
. This array is used to 'fudge' or 'adjust' the index number obtained from the first character string before it is used on the second character string. The contents of the _convertkey
function can be viewed here.
$this->errors = array(); $fudgefactor = $this->_convertKey($key); if ($this->errors) return;
This next piece of code checks that a source string has actually been supplied.
if (empty($source)) { $this->errors[] = 'No value has been supplied for encryption'; return; } // if
Next we pad the input string with spaces up the specified length.
while (strlen($source) < $sourcelen) { $source .= ' '; } // while
Here we are setting up a for
loop to process each character from $source
.
$target = NULL; $factor2 = 0; for ($i = 0; $i < strlen($source); $i++) {
Here we extract the next character from $source
and identify its position in $scramble1
. Note that we cannot continue processing if the character cannot be found.
$char1 = substr($source, $i, 1); $num1 = strpos($this->scramble1, $char1); if ($num1 === false) { $this->errors[] = "Source string contains an invalid character ($char1)"; return; } // if
Next we obtain the adjustment value from the $fudgefactor
array and accumulate it in $factor1
along with the previous contents of $factor2
. The contents of the _applyFudgeFactor
function can be viewed here.
$adj = $this->_applyFudgeFactor($fudgefactor); $factor1 = $factor2 + $adj; // accumulate in $factor1
Here we add $factor1
to the offset from the $scramble1
string ($num1
) to give us the offset into the $scramble2
string ($num2
). Note that factor may contain decimal digits, so it has to be rounded in order to supply a whole number.
$num2 = round($factor1) + $num1; // generate offset for $scramble2
The value at this point may be a negative number or even a large positive number, so we have to check that it can actually point to a character in the $scramble2
string. The contents of the _checkRange
function can be viewed here.
$num2 = $this->_checkRange($num2); // check range
As an added complication to confuse potential hackers we are also accumulating the value of $num2
and $factor1
in $factor2
.
$factor2 = $factor1 + $num2; // accumulate in $factor
Here we extract a character from $scramble2
using the value in $num2
and append it to the output string.
$char2 = substr($this->scramble2, $num2, 1); $target .= $char2;
Finally we close the for
loop and return the encrypted string.
} // for return $target; } // encrypt
This function converts the $key
string into an array of numbers. The first check is to ensure that a value has actually been supplied.
function _convertKey ($key) { if (empty($key)) { $this->errors[] = 'No value has been supplied for the encryption key'; return; } // if
The first entry in the array is the length of the $key
string.
$array[] = strlen($key);
Next we set up a for
loop to examine every character in the $key
string.
$tot = 0; for ($i = 0; $i < strlen($key); $i++) {
Here we extract the next character from $key
and identify its position in $scramble1
. Note that we cannot continue processing if the character cannot be found.
$char = substr($key, $i, 1); $num = strpos($this->scramble1, $char); if ($num === false) { $this->errors[] = "Key contains an invalid character ($char)"; return; } // if
He we append the number to the output array and accumulate the total for later.
$array[] = $num; $tot = $tot + $num;
At the end of the for
loop we add the accumulated total to the end of the array and return the array to the calling process.
} // for $array[] = $tot; return $array; } // _convertKey
This function will return an adjustment value using the contents of the $fudgefactor
array. Note that $fudgefactor
is passed by reference so that it can be modified
function _applyFudgeFactor (&$fudgefactor)
{
Here we extract the first entry in the array and remove it from the array.
$fudge = array_shift($fudgefactor);
Next we add in the optional $adj
value and put the result back into the end of the array.
$fudge = $fudge + $this->adj; $fudgefactor[] = $fudge;
If a $modulus
value has been supplied we use it and possibly reverse the sign on the output value.
if (!empty($this->mod)) { // if modifier has been supplied if ($fudge % $this->mod == 0) { // if it is divisible by modifier $fudge = $fudge * -1; // reverse then sign } // if } // if
There is no more 'fudging' left to do, so we can return the value to the calling process.
return $fudge; } // _applyFudgeFactor
This function checks that the value in $num
can actually be used as a pointer to an entry in $scramble1
.
function _checkRange ($num)
{
First we must round up to the nearest whole number.
$num = round($num);
We use the length of the scramble string as the limit.
$limit = strlen($this->scramble1);
If the value is too high we must reduce it.
while ($num >= $limit) { $num = $num - $limit; } // while
If the value is too low we must increase it.
while ($num < 0) { $num = $num + $limit; } // while
We can now return a valid pointer back to the calling process.
return $num; } // _checkRange
This function will take an encrypted string and turn it into plain text using the supplied key. Note that this must be exactly the same key that was used to encrypt the string in the first place.
function decrypt ($key, $source) {
The first step is to convert the key into an array of numbers which I call $fudgefactor
. The contents of the _convertkey
function can be viewed here.
$this->errors = array(); $fudgefactor = $this->_convertKey($key); if ($this->errors) return;
This next piece of code checks that a source string has actually been supplied.
if (empty($source)) { $this->errors[] = 'No value has been supplied for decryption'; return; } // if
Here we are setting up a for
loop to process each character from $source
.
$target = NULL; $factor2 = 0; for ($i = 0; $i < strlen($source); $i++) {
Here we extract the next character from $source
and identify its position in $scramble2
. Note that we cannot continue processing if the character cannot be found.
$char2 = substr($source, $i, 1); $num2 = strpos($this->scramble2, $char2); if ($num2 === false) { $this->errors[] = "Source string contains an invalid character ($char2)"; return; } // if
Next we obtain the adjustment value from the $fudgefactor
array and accumulate it in $factor1
along with the previous contents of $factor2
. The contents of the _applyFudgeFactor
function can be viewed here.
$adj = $this->_applyFudgeFactor($fudgefactor); $factor1 = $factor2 + $adj;
Here we add $factor1
to the offset from the $scramble2
string ($num2
) to give us the offset into the $scramble1
string ($num1
). Note that factor may contain decimal digits, so it has to be rounded in order to supply a whole number.
$num1 = $num2 - round($factor1); // generate offset for $scramble1
The value at this point may be a negative number or even a large positive number, so we have to check that it can actually point to a character in the $scramble1
string. The contents of the _checkRange
function can be viewed here.
$num1 = $this->_checkRange($num1); // check range
As an added complication to confuse potential hackers we are also accumulating the value of $num2
and $factor1
in $factor2
.
$factor2 = $factor1 + $num2; // accumulate in $factor2
Here we extract a character from $scramble1
using the value in $num1
and append it to the output string.
$char1 = substr($this->scramble1, $num1, 1); $target .= $char1;
Finally we close the for
loop, return the decrypted string, and close the class.
} // for return rtrim($target); } // decrypt } // end encryption_class
In order to use this class you must first include/require the file containing the class definition and then create an object or instance of the class.
require 'std.encryption.class.inc'; $crypt = new encryption_class;
To encrypt a string you must supply the string and a key. The string length is optional. Note that before you write this to your database you should use the addslashes() command to deal with any special characters.
$encrypt_result = $crypt->encrypt($key, $password, $pswdlen); $errors = $crypt->errors;
To decrypt a string you must supply the string and the key that was used to encrypt it.
$decrypt_result = $crypt->decrypt($key, $password); $errors = $crypt->errors;
As you can see it is not impossible to create a reasonable encryption function of your own using standard PHP functions. This way you can have your own customisable routine which does not rely on any 3rd party modules which your web hosting service may not feel inclined to provide.
16 June 2007 | Removed the single quote, double quote and backslash characters from the two scramble strings as these have special meaning in PHP, and can sometimes cause problems.
Removed the need to have the first character of each scramble string duplicated at the end. My thanks to Miroslav Kolar for supplying a fix. |
27 August 2004 | Amended the decrypt() function so that if $num2 (the offset into $scramble2) points to the first character it is switched to point to the last character, which is a duplicate. For some reason the value zero has a peculiar effect. |
14 July 2004 | Amended the strings in $scramble1 and $scramble2 so that the first character in each string is also duplicated at the end of that string. This gets round a bijou problemette when the first character of the password is also the first character in $scramble1. |