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.
Character Used | ASCII 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".
Character to Encrypt | ASCII Character Code |
---|---|
s | 115 |
o | 111 |
u | 117 |
p | 112 |
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.
Decimal Value | Binary Value |
---|---|
0 | 00000000 |
1 | 00000001 |
2 | 00000010 |
3 | 00000011 |
... | ... |
244 | 11111110 |
255 | 11111111 |
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.
Character | ASCII Character Code |
---|---|
J | 74 |
U | 85 |
I | 73 |
C | 67 |
Y | 89 |
32 | |
S | 83 |
T | 84 |
U | 85 |
D | 68 |
I | 73 |
O | 79 |
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).
x | y | Result (X AND y) |
---|---|---|
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
The Bitwise Or Operator
The bitwise Or operator compares two bits. It returns true (1) if either bit is set, otherwise false (0).
x | y | Result (X Or y) |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 1 |
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).
x | y | Result (X Xor y) |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
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.
x | Result (Not x) |
---|---|
0 | 1 |
0 | 0 |
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.
Inner Loop | Key | Character | Xor Result | Resulting Character |
---|---|---|---|---|
1 | 49 | s | 66 | B |
2 | 52 | o | 91 | [ |
3 | 55 | u | 66 | B |
4 | 58 | p | 74 | J |
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.
Inner Loop | Key | Character | Xor Result | Resulting Character |
---|---|---|---|---|
1 | 50 | B | 112 | p |
2 | 53 | [ | 110 | n |
3 | 56 | B | 122 | z |
4 | 59 | J | 113 | q |
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.
Inner Loop | Key | Character | Xor Result | Resulting Character |
---|---|---|---|---|
1 | 51 | p | 67 | C |
2 | 54 | n | 88 | X |
3 | 57 | z | 67 | C |
4 | 60 | q | 77 | M |
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.
Inner Loop | Key | Character | Xor Result | Resulting Character |
---|---|---|---|---|
1 | 51 | C | 114 | r |
2 | 52 | X | 108 | l |
3 | 55 | C | 116 | t |
4 | 58 | M | 119 | w |
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.
Inner Loop | Key | Character | Xor Result | Resulting Character |
---|---|---|---|---|
1 | 50 | r | 64 | @ |
2 | 53 | l | 89 | Y |
3 | 56 | t | 76 | L |
4 | 59 | w | 76 | L |
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.
Inner Loop | Key | Character | Xor Result | Resulting Character |
---|---|---|---|---|
1 | 51 | @ | 115 | s |
2 | 54 | Y | 111 | o |
3 | 57 | L | 117 | u |
4 | 60 | L | 112 | p |
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:
Post a Comment