Selvatech.com : in the jungle of financial I.T.

Tuesday, May 31, 2005

Passing ID with querystring made secure

Sometimes you would love to pass some parameters as querystring to your web page : for example, you want to send an email with a link to a specific page with parameters, or you want to make sure that your page can be added to the Favorites.
But security concerns will refrain from using this simple technic...


Do you really want to reveal your secrets ?

The main reason to avoid passing parameters as querystring is that your web site users will be able to guess other parameters value based on what they see. The worst example would be to make a database user id visible in the URL. Changing the URL, and setting a new id would probably allow to impersonate another user... Of course there are many ways to check the user id in the destination web page, but wouldn't it be nice to refrain the "I-have-always-dreamed-of-being-a-hacker" temptation ?
Don't feel either to confident in passing parameters as POST : if you really want to sniff the communication with the server, you will find the information as clear text.

The only solution : Encryption !

Using encryption, it will be almost impossible for the user to "understand" the crypted text passed as parameters, but you (and only you) will still be able to decrypt it and retrieve the correct parameter. We would like to translate http://www.selvatech.com/user_id=194 into http://www.selvatech.com/user_id=AZRZERVBFVF945FFERF566T3 (anyone will admit that guessing another user id based on AZRZERVBFVF945FFERF566T3 is harder than on 194!). Isn't it what we want ?

Let's take a second to talk about cryptology, and more precisely .Net cryptology.

All native .Net cryptology classes are in the System.Security.Cryptography namespace.
Many articles deal will the security aspect in .Net, so I will make it short and give you the link to the best articles at the end of this post.

There are various available methods in the .Net Framework to encrypt piece of plain text, the most famous are Rijndael (RijndaelManaged) , DES (DESCryptoServiceProvider), Triple Des (TripleDESCryptoServiceProvider), RC2 (RC2CryptoServiceProvider). All this classes have some caracteristics in common :

- They use a key with a determined length (expressed in bits) to encrypt and decrypt a piece of text,
- They can use an Initial Vector (IV) , different Padding modes, Cipher modes and Block Size,
- The Array of Bytes is the best type to use within the encryption classes.

In this post, we will only deal with symmetric encryption which uses a secret key value to encrypt and decrypt the data. Both the sender and receiver need the same key in order to encrypt or decrypt. In our case the program which sends the email containing the link, and the destination web page must access the secret key, and use exactly the same parameters for encryption and decryption.

After reading Using encryption in .Net by Steve Johnson, I think it's good to rewrite the principles exposed in his conclusion :

  • Always use proven, public technology
  • Use Rijndael, if possible
  • Use 256-bit keys and blocks
  • Take care how you derive keys
  • Use PKCS7 padding in .NET 1.1, ISO10126 in .NET 2.0
  • Don't use ECB mode unless you have a good reason and know what you're doing
  • Always use a unique IV for each message
  • Take the time to be explicit in your code for important details, even when you're using defaults

In the rest of this post, I will try to put in practice Steve principles (except the "Always use a unique IV for each message" which I think is not adapted to symmetric encryption because both the sender and the receiver must know the Initial Vector).

Now let's see some VB.Net coding in action !

  • The first problem we have to solve is : how do I generate a 256-bits key ?

This is a very good question ! 256 bits means 32 bytes (1 byte = 8 bits) to provide to the encryption algorithm. As the key is all you need to know to "understand" your secrets (well you need to know the encryption method, the IV used, the padding too ...), it's a good practice to think twice before choosing.

We could try to use a "simple password" like one of my favorite : iloveselvatech and generate a 256-bits key from it. How can we do this ?

So simply : we only need to use some hashing methods included in the .Net Framework. Hashing is the transformation of a string of characters into a fixed-length value or key that represents the original string. In other words a hash is like a fingertip : it identifies uniquely the string from which it has been generated. Different hashing algorithms exist in .Net : the most famous are MD5 and SHA. MD5 produces 128-bits hash, whereas SHA can produce 160, 256, 384 and 512-bits hash. The good point in using hashing with encryption in .Net is that both use array of bytes as native type.

In our example, we can use a SHA256 hash from our iloveselvatech password. This is our simple hash-SHA256 class (the whole thing is in the ComputeHash method) :

Public Class hash_SHA256

Private _dhashSHA256 As System.Security.Cryptography.SHA256Managed
Public Sub New()
_dhashSHA256 = New System.Security.Cryptography.SHA256Managed
End Sub

Public Function ComputeHash(ByVal data As String) As Byte()
Return _dhashSHA256.ComputeHash(string2array(data))
End Function

Public Function string2array(ByVal data As String) As Byte()
Return System.Text.Encoding.Default.GetBytes(data)
End Function

Public Function array2string(ByVal data As Byte()) As string
Return System.Text.Encoding.Default.GetString(data)
End Function

End Class

Then, we will generate our 256-bits length message :

Private Function Generate256bitskey (byval str as string) as byte()
dim hashclass As hash_SHA256
hashclass = New hash_SHA256
return hashclass.ComputeHash(str)

End Function

In our case, a SHA256 hash of iloveselvatech will be : {246 , 70 , 154 , 164 , 75 , 50 , 110 , 43 , 92 , 82 , 207 , 70 , 192 , 125 , 121 , 100 , 102 , 222 , 109 , 9 , 192 , 236 , 14 , 131 , 139 , 146 , 110 , 243 , 65 , 126 , 149 , 192} as an array of bytes.

But, even if you can find this key difficult to read, it's easy to break if you guess that you are using a SHA256 algorithm. Using a brute force strategy, it is relatively easy (but time consuming) to find the original 14 letters code which produce this unreadable key.

Another technic, and better approach, is to generate your own 256-bits key. This is the same approach I will use for generating the Initial Vector later. Here is my code :

Private Function Generate256bitsKey() As Byte()
Dim table() As Byte
Dim str As String
dim i as integer
str="WWW.SELVATECH.COM"
For i=1 To 100
str = str & Chr(3 * i Mod 255)
Next
table=System.Text.Encoding.Default.GetBytes(str)
Dim tabiv(32) As Byte
Array.Copy(table, tabiv, 31)
return tabiv
End Function

In this case, I generate a long string with a determined, but difficult to find algorithm, that I convert into a 32-bytes long array of byte (remember that 32-bytes means 256 bits...)

  • How do I build my encrypting class ?

We choosed to use a Rijndael 256-bits key and blocks algorithm, using PKCS7 padding and not ECB cipher mode. I won't go into details with these specifications, that's what MSDN is here for ! Happily, .Net classes are smart enough, to instanciate easily our object :

Public Class crypto_Rijndael

Private _rijndael As System.Security.Cryptography.RijndaelManaged

Public Sub New()
_rijndael = New System.Security.Cryptography.RijndaelManaged
_rijndael.Mode = System.Security.Cryptography.CipherMode.CBC
_rijndael.Padding= System.Security.Cryptography.PaddingMode.PKCS7
_rijndael.BlockSize=256
_rijndael.KeySize=256
_rijndael.IV=GenerateIV()
_rijndael.Key=GenerateCryptoKey()
End Sub

End Class

Where GenerateIV() and GenerateCryptoKey() produce 32-bytes-length array as seens above.

Then we need 2 methods : Encrypt and Decrypt.

Public Function Encrypt(ByVal data As String) As Byte()
Dim rijEncrypt As System.Security.Cryptography.ICryptoTransform = _rijndael.CreateEncryptor()
Dim buff() As Byte = string2array(data)
Return rijEncrypt.TransformFinalBlock(buff, 0, buff.Length)
End Function

Public Function Decrypt(ByVal sEncryptedData As String) As Byte()
Dim rijEncrypt As System.Security.Cryptography.ICryptoTransform = _rijndael.CreateDecryptor()
Dim buff() As Byte = Convert.FromBase64String(sEncryptedData)
Return rijEncrypt.TransformFinalBlock(buff, 0, buff.Length)
End Function

As you can see, these methods are very simple, however, there is an important point we have to speak about : encoding. This point will make clear the use of Convert.FromBase64String in the Decrypt method.

  • What is the problem with encoding ?
We want to cipher a string into another string that can be used in a querystring. This point should help us to focus on Base64 and HEX formats.

An HEX-formated text will look like : 414F664141644D383962336874425933714 whereas Base64-formated text will look like : AOfAAdM89b3htBY3qAehu5omm0QvLXLrr0YHo/tUAfo=.
Base64 encoding format is about 33% more efficient than HEX encoding, however, passing Base64-encoded information withtin the URL can generate problem because of URLencoding mechanism, for example, + in the URL is re-encoded as ' '. This problem doesn't exist with HEX-encoding.

We will first transform the array of bytes which is returned by the Encrypt method as a Base64-string, and we will translate this string into a HEX-formated string before writing it to the querystring (there is no simple way to convert an array of byte into a HEX-string).

Our method for writing the querysting will be :

url = string_TO_hexa(Convert.ToBase64String(crypto_Rijndael.Encrypt(encryptedtext)))

with

Private Function string_TO_hexa(ByVal data As String) As String
Dim i As Integer
string_TO_hexa = ""
For i = 1 To Len(data)
string_TO_hexa = string_TO_hexa & Strings.Right("0" + Hex(Asc(Mid(data, i, 1))), 2)
Next
End Function


Then, at the destination page, we get the querystring parameter HEX-encoded.

secrettext = array2string(crypto_Rijndael.Decrypt(hexa_TO_string(querystring)))

with

Public Function hexa_TO_string(ByVal data As String) As String
Dim i As Integer
For i = 1 To Len(data) Step 2
hexa_TO_string = hexa_TO_string & Chr(CType("&H" & Mid(data, i, 2), Integer))
Next
End Function

With the function above, we translate the HEX-formated text back into a Base64-string. And during the Decrypt process, we use :

Dim buff() As Byte = Convert.FromBase64String(sEncryptedData)

which translates a Base64-string into an array of Bytes.

Conclusion

First of all, I spent a long time fighting with Cipher.Mode when I built my instances of Rijndael, the reason is if you don't pick up ECB mode (less secure, cf articles), don't forget to initialise the IV property of the
RijndaelManaged class (otherwise, it will generate each time you instanciate the class a random new IV, and in this case, you won't be able to decrypt your text).

The .Net classes related to Cryptography (at least for the symmetric encryption) are very user friendly so there's no point of not using them !

Some interesting articles about cryptology and .Net:
An Overview of Cryptography : long article about cryptology in general
Cryptography in .NET : the use of private and public keys in .Net
Using Symmetric Cryptography in an ASP.NET Web Page : using cryptology in an ASP.Net web page
Cryptography Simplified in Microsoft .NET : Microsoft article about basic cryptology (hashing, keys...) with .Net
Keeping Secrets: A Guide to VB .NET Cryptography : A guide for private key generation using vectors
Encrypting QueryStrings with .NET : Exactly what we want to do, but if the article is good, I've found some formating problems in the code
Performance Comparison: Security Design Choices : Compare hashing and encrypting techniques performances
Using Encryption in .NET : Excellent article on .Net encryption (keys, encryption algorithm, detailled study of Rijndael Provider)

0 Comments:

Post a Comment

<< Home