Friday, 30 January 2009

Creating an Encryption routine with Classic ASP

Introduction

If you've ever had the task of designing a password-protected website, an important consideration is how to store the user's password information securely.

The most common method that users adopt is to store the user's information as clear text and password-protect the database. Whilst this method does give a certain degree of security, what if the database password was compromised? Should this happen, all of the users information would be compromised, which violates their privacy.

To combat this, it would be a good idea to encrypt the user's passwords. If the database were compromised, it would still be impossible to determine the users passwords without knowing the encryption process used. There are plenty of commercial encryption components available today in a choice of languages. I'm going to show you how to build your own, for nothing! There's little in life that costs less!

Requirements

We will be using VBScript as our server-side scripting language. We will make our own function that makes use of the Len, Mid, Asc, and Chr functions, and the Xor and Mod Operators. Familiarity with these functions and operators will make this tutorial easier to follow, but a quick summary is provided.

Summary of VBScript Functions used in this Tutorial

Len: Returns the number of characters in a string.
Len("Juicy Studio") ' Returns 12

Mid: Returns a specified number of characters from a string. The function takes three parameters; the string, the start position within the string, and the length of the sub-string.
Mid("Juicy Studio", 1, 1) ' Returns "J"

Asc: Returns ANSI character code corresponding to the first letter in a string.
Asc("A") ' Returns 65

Chr: Returns a character associated with the specified ANSI character code.
Chr(65) ' Returns "A"

Xor: Performs a bitwise exclusive or operation, described in more detail later in the tutorial.
Result = 83 Xor 49 ' Returns 98

Mod: Divides two numbers and returns the remainder as a whole number. If number_1 is not divisible by number_2 it returns number_1, as the result would be zero remainder number_1.
Result = 11 Mod 2 ' Returns 1
Result = 10 Mod 2 ' Returns 0
Result = 10 Mod 20 ' Returns 10

The Encryption Function

The function works by encrypting a string (strTextToEncrypt) using an encryption key (strEncryptionKey), the length of the encryption key determines the number of times the string is encrypted, whilst the content of the encryption key affects how the string will be encrypted. During the encryption process, the encryption key is changed several times, the theory behind this encryption function, is that the more times the encryption key changes, the harder it will be to decrypt the password.

Private Function EncryptText(ByVal strEncryptionKey, ByVal strTextToEncrypt)
' Declare variables
Dim outer, inner, Key, strTemp

' For each character in strEncryptionKey
For outer = 1 To Len(strEncryptionKey)

' Get a character to use as our encryption
' key in this iteration of the OUTER loop
key = Asc(Mid(strEncryptionKey, outer, 1))

' For each character in strTextToEncrypt
For inner = 1 To Len(strTextToEncrypt)

' Update our encrypted text
strTemp = strTemp & Chr(Asc(Mid(strTextToEncrypt, inner, 1)) Xor key)

' Change our encryption key to mix things up in the INNER loop.
key = (key + Len(strEncryptionKey)) Mod 256

Next

' Update the strTextToEncrypt variable before
' the next iteration of the OUTER loop
strTextToEncrypt = strTemp

' Reset strTemp for the next iteration of the OUTER loop.
strTemp = ""

Next

' Assign the value of the encrypted text to the function name
' so we can return the value to the caller
EncryptText = strTextToEncrypt
End Function

To encrypt a user's password, we need to pass two arguments to our function and assign the result to a variable:
' Encrypt the password
strEncryptedPassword = EncryptText(strUsername, strPassword)

How The Encryption Function Works

Throughout the description of how the function works, the following values will be used:
strUsername = "123"
strPassword = "soup"

All functions start with a Function definition:
Private Function EncryptText(ByVal strEncryptionKey, ByVal strTextToEncrypt)

The function has been declared Private, meaning that it is only accessible to the script where it is defined. The function requires 2 arguments to be passed to it. The arguments are passed by value, so any changes we make are not reflected back to where the function is called.

We begin our function by declaring the variables that will be used. The variables, inner, and outer, are used as loop counters. The variable, Key, will store the ANSI character code of a single character of the encryption key (strEncryptionKey). The variable strTemp will store our encrypted string until each character in strTextToEncrypt has been encrypted.
' Declare variables
Dim outer, inner, Key, strTemp

I mentioned earlier that the length of the encryption key determines the number of times the string is encrypted. This is due to our function using each character of strEncryptionKey as an encryption key.
' For each character in strEncryptionKey
For outer = 1 To Len(strEncryptionKey)

Assuming that strEncryptionKey had a value of "123", we would encrypt the string 3 times. In the first loop our encryption key would be "1", in the second loop it would be "2" and in the third loop it would be "3". I also said that the content of the key would affect how the string was encrypted.
' Get a character to use as our encryption key
' in this iteration of the loop
key = Asc(Mid(strEncryptionKey, outer, 1))

It should be obvious that encrypting a string using different keys will return different results. Note that we don't actually assign Key with a character, we assign the Key with the characters ANSI character code instead using the Asc function. This is required so that we can use it with the Xor operator later.

Outer Loop Values
Character UsedASCII Value for Key
"1"49
"2"50
"3"51

Once the Key variable has a value, we encrypt the whole of the string one character at a time, we do this by using another loop.
' For each character in strTextToEncrypt,
For inner = 1 To Len(strTextToEncrypt)

Because we're encrypting a string one character at a time, we have to use a variable that will store the characters after they have been encrypted at the end of each INNER loop iteration. We shall use the variable, strTemp, for this purpose.
' Update our encrypted text
strTemp = strTemp & Chr(Asc(Mid(strTextToEncrypt, inner, 1)) Xor key)

Before we can encrypt a character using the Xor operator, we have to convert it into its ANSI character code using the Asc function. The following shows the ASCII character codes for the characters in "soup".

ASCII representation of 'soup'
Character to EncryptASCII Character Code
s115
o111
u117
p112

We then use the Xor operator to encrypt the character with the key.

Working with Bytes
To understand how the Xor operator is used in this tutorial, we need to look at the ASCII character set and understand how Bytes are represented. A Byte is composed of 8 bits, each bit can either have a binary value of 0 or 1. With 8 bits in a Byte you can represent 256 decimal values ranging from 0 to 255.

Sample 8-Bit Numbers
Decimal ValueBinary Value
000000000
100000001
200000010
300000011
......
24411111110
25511111111

Bytes are used to store a single character in memory or on disk. If you were to open Notepad and type "Juicy Studio", notepad would consume 12 bytes of memory (1 byte for each character, including the space), likewise, if you saved the text file it would consume 12 bytes of disk space. In the ASCII character set, each decimal value between 0 and 127 is given a specific character. The ANSI character set extends the ASCII character set to use the full range of 256 characters available in a Byte. The upper 128 characters (128-255) are used to represent additional special, mathematical, graphic, and foreign characters. If you look at the ASCII Character code for "JUICY STUDIO", you get the following.

ASCII Codes for JUICY STUDIO
CharacterASCII Character Code
J74
U85
I73
C67
Y89

32
S83
T84
U85
D68
I73
O79

Bitwise Operators

Bitwise operators allow you to manipulate integer variables at bit level. The basic bitwise operators available in VBScript are And, Or, Xor, and NOT. The following truth tables give an overview of each operator.

The Bitwise And Operator

The bitwise And operator compares two bits. It returns true (1) if both bits are set, otherwise false (0).

And Truth Table
xyResult (X AND y)
000
010
100
111

The Bitwise Or Operator

The bitwise Or operator compares two bits. It returns true (1) if either bit is set, otherwise false (0).

Or Truth Table
xyResult (X Or y)
000
011
101
111

The Bitwise Exclusive Or Operator

The bitwise Exclusive Or (Xor) operator compares two bits. It returns true (1) if either bit is set, but not both (hence exclusive), otherwise false (0).

Xor Truth Table
xyResult (X Xor y)
000
011
101
110

The Bitwise Not Operator

The bitwise Not operator has been included for completeness. The operator inverts the bit, setting it to 1 if it is 0, otherwise setting it to 0.

Not Truth Table
xResult (Not x)
01
00

Working with more than 1 bit

When working with more than a single bit, the bitwise operators are applied to the corresponding bits of the operands. The ASCII value for "s" is 115, and the ASCII value for "1" is 49. The following is an example of a bit-by-bit Xor operation on the two values.

0 1 1 1 0 0 1 1 "s"
Xor 0 0 1 1 0 0 0 1 "1"
________________
0 1 0 0 0 0 1 0 "B"


The result is 66, which is the ASCII code for the character "B".

An interesting observation of the ASCII character set, is that bit 5 determines if the character is uppercase of lowercase. If the bit is set, the character will be a lowercase character, otherwise an uppercase character. Therefore, Xor'ing any single ASCII character with the value of 32 (2 to the 5th power) will toggle the case of that character.

Observations of the Exclusive OR Operator

There are two important observations we make about the exclusive or operator. The first observation is that if both operands have the same value, the result will be zero. This is because all bits that have the same value will be reset.


0 1 1 1 0 0 1 1 "s"
Xor 0 1 1 1 0 0 1 1 "s"
________________
0 0 0 0 0 0 0 0 0


The second observation is that if the result is xor'd with either of the operands, the new result is the other operand. For example, if the result is xor'd with the second operand, the new result is the first operand. The next example uses a key with a value of 7 to Xor "S".

0 1 1 1 0 0 1 1 "s"
Xor 0 0 0 0 0 1 1 1 7 (key)
________________
0 1 1 1 0 1 0 0 116 ("t")


If we now Xor the result (116) with the key (7), we end up with the original value of "s" (115).

0 1 1 1 0 1 0 0 Result ("t")
Xor 0 0 0 0 0 1 1 1 7 (key)
________________
0 1 1 1 0 0 1 1 115 ("s")


This is an important observation, as it provides a means for us to get back to our original value using some key.

Back to the Function

To mix things up, after a single character has been encrypted, we change the value of Key. This will result in each character of the string being encrypted using a different key. To change the value, we use the Mod operator to return the remainder of the division of the current key value + the length of strEncryptionKey by 256 (the decimal range of the ANSI character set) to keep the value of Key within the ANSI character sets range.
' Change our encryption key to mix things up in the INNER loop.
key = (key + Len(strEncryptionKey)) Mod 256

Before we move onto the next iteration of our OUTER loop, we update the value of strTextToEncrypt to its new encrypted value. This means that in our next iteration, we will be encrypting an already-encrypted version of our password.
' Update the strTextToEncrypt variable before the next loop
strTextToEncrypt = strTemp

We then reset strTemp to a null string. If we didn't do this, the length of our encrypted password would keep on growing with each iteration of a loop.
' Reset strTemp
strTemp = ""

After we have completed the encryption process, we assign the value of strTextToEncrypt to function name so that we can return the encrypted password back to the caller.
' Assign the value of the encrypted text to the function name
' so we can return the value to the caller
EncryptText = strTextToEncrypt

Worked Example
The following works through the example of a password of "soup", and a username of "123". The username has three characters, meaning there will be three passes at encrypting the password.

First Pass
Inner LoopKeyCharacterXor Result Resulting Character
149s66B
252o91[
355u66B
458p74J

At the end of our first pass, strTextToEncrypt has the value, "B[BJ". This is now fed back into the encryption routine for the second pass.

Second Pass
Inner LoopKeyCharacterXor Result Resulting Character
150B112p
253[110n
356B122z
459J113q

At the end of our second pass, strTextToEncrypt has the value, "pnzq". This is now fed back into the encryption routine for our third and final pass.

Third Pass
Inner LoopKeyCharacter Xor ResultResulting Character
151p67C
254n88X
357z67C
460q77M

At the end of our third and final pass, strTextToEncrypt has the value, "CXCM".

Resolving the Password

In this tutorial, the password doesn't require resolving. When the username and password are entered, the password is encrypted using the username as a key. This is checked with the encrypted version stored in the database. If they match, we let them have access. But what if you wanted to reveal the stored password, for example when the user has forgotten their password? How do you get back to the original password? This is when our second observation of the Xor operator comes in. You simply encrypt the encrypted password with the username, and you've got the original password.

First Pass
Inner LoopKeyCharacterXor Result Resulting Character
151C114r
252X108l
355C116t
458M119w

At the end of our first pass, strTextToEncrypt has the value, "rltw". This is now fed back into the encryption routine for the second pass.

Second Pass
Inner LoopKeyCharacterXor ResultResulting Character
150r64@
253l89Y
356t76L
459w76L

At the end of our second pass, strTextToEncrypt has the value, "@YLL". This is now fed back into the encryption routine for our third and final pass.

Third Pass
Inner LoopKeyCharacterXor ResultResulting Character
151@115s
254Y111o
357L117u
460L112p

At the end of our third and final pass, strTextToEncrypt has been resolved back to its original value, "soup".

Summary

We've managed to encrypt "soup" into "CXCM" using the Xor operator. If someone were able to access your database, your passwords would still be secure as they would need to be decrypted before they could be used. When it comes to verifying user's credentials in a login process, all you need to do is encrypt the password they provide with their username, and see if it matches the encrypted password stored in the database. If you need to decrypt the password, you simply use the encryption routine with the encrypted password, and it's decrypted.

No comments: