Encriptar Querystring en ASP.NET

Hoy quiero publicar un pequeño proyecto que permita agregar de manera transparente en sus sitios una forma de encriptar el contenido del QueryString en aplicaciones Web con ASP.NET. Vamos a utilizar el Visual Studio 2012 y vamos a desarrollar el código en C#. En esta primera publicación quiero mostrarlo en sin utilizar el paradigma de MVC, pero también quiero aplicar la misma idea para poder encriptar con MVC.

Normalmente nuestras aplicaciones tendrían este QueryString:
foto 1

De esta manera podríamos estar pasando elementos importantes de nuestra base de datos como por ejemplo el ID de algún campo.

Voy a Encapsular el funcionamiento del encriptado/desencriptado en una librería para que se pueda utilizar en cualquier proyecto. Adicionalmente dejo el proyecto de DLL en C# y en VB.NET para que pueda estar en ambos lenguajes. El código simplemente toma la colección de clave/valor que tiene HttpContext.Current.Request.Url.AbsoluteUri y de esa manera obtiene todos los valores del QueryString:

 

Ejemplo C#:

public static QueryString FromCurrent() {
        return FromUrl(HttpContext.Current.Request.Url.AbsoluteUri);
    }

Ejemplo VB.NET:

  Public Shared Function FromCurrent() As QueryString
            Return FromUrl(HttpContext.Current.Request.Url.AbsoluteUri)
        End Function

El proyecto consta de 2 clases:

  • Encryption
  • QueryString

Una genera la encriptación/desencriptación, utilizando MD5 en el modo CBC y la otra genera una cadena encriptada que contiene un array con la clave/valor correspondiente.

Desarrollo el método de Encriptado/Desencriptado siguiendo este ejemplo que modifique un poco para adaptarlo a un formato de URL que no derive en problemas, mas específicamente no lo devuelvo en Base64 como el ejemplo sino que lo hago en hexadecimal.

Ejemplo C#

 static string saltValue = "s@1tValue";//can be any string
        static string hashAlgorithm = "MD5"; //can be "MD5"  "SHA1"
        static int passwordIterations = 1;
        static string initVector = "@1B2c3D4e5F6g7H8"; //must be 16 bytes
        static int keySize = 192;        //  can be 192 or 128

private static string Encrypt(string plainText, string passPhrase)
        {
            byte[] initVectorBytes;
            initVectorBytes = Encoding.ASCII.GetBytes(initVector);
            byte[] saltValueBytes;
            saltValueBytes = Encoding.ASCII.GetBytes(saltValue);
            byte[] plainTextBytes;
            plainTextBytes = Encoding.UTF8.GetBytes(plainText);
            PasswordDeriveBytes password;
            password = new PasswordDeriveBytes(passPhrase, saltValueBytes, hashAlgorithm, passwordIterations);
            byte[] keyBytes;
            keyBytes = password.GetBytes((keySize / 8));
            RijndaelManaged symmetricKey;
            symmetricKey = new RijndaelManaged();
            symmetricKey.Padding = PaddingMode.Zeros;
            //  It is reasonable to set encryption mode to Cipher Block Chaining
            //  (CBC). Use default options for other symmetric key parameters.
            symmetricKey.Mode = CipherMode.CBC;
            ICryptoTransform encryptor;
            encryptor = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes);
            MemoryStream memoryStream;
            memoryStream = new MemoryStream();
            CryptoStream cryptoStream;
            cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write);

            //  Start encrypting.
            cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
            //  Finish encrypting.
            cryptoStream.FlushFinalBlock();


            byte[] cipherTextBytes;
            cipherTextBytes = memoryStream.ToArray();
            
            //  Close both streams.
            cryptoStream.Close();
            memoryStream.Close();

            // Get the data back from the memory stream, and into a string
            StringBuilder ret = new StringBuilder();
            
            byte[] b = cipherTextBytes;
            
            int I;
            for (I = 0; (I <= b.Length - 1); I++)
            {
                // Format as hex
                ret.AppendFormat("{0:X2}", b[I]);
            }
            return ret.ToString();

        }
   private static string DecryptString(string cipherText, string passPhrase)
        {

            //  Convert strings defining encryption key characteristics into byte
            //  arrays. Let us assume that strings only contain ASCII codes.
            //  If strings include Unicode characters, use Unicode, UTF7, or UTF8
            //  encoding.
            if ((cipherText == String.Empty))
            {
                return "";
            }
            else
            {
                byte[] initVectorBytes;
                initVectorBytes = Encoding.ASCII.GetBytes(initVector);
                byte[] saltValueBytes;
                saltValueBytes = Encoding.ASCII.GetBytes(saltValue);
                //  Convert our ciphertext into a byte array.
                // Dim cipherTextBytes As Byte()
                // cipherTextBytes = Convert.FromBase64String(cipherText)
                // Dim cipherTextBytes() As Byte = Encoding.UTF8.GetBytes(cipherText)
                byte[] cipherTextBytes = new byte[Convert.ToInt32(cipherText.Length / 2 )];
                // = Encoding.UTF8.GetBytes(InputString)
                int X;
                for (X = 0; (X <= (cipherTextBytes.Length - 1)); X++)
                {
                    Int32 IJ = Convert.ToInt32(cipherText.Substring((X * 2), 2), 16);
                    System.ComponentModel.ByteConverter BT = new System.ComponentModel.ByteConverter();
                    cipherTextBytes[X] = new byte();
                    cipherTextBytes[X] = ((byte)(BT.ConvertTo(IJ, typeof(byte))));
                }
                //  First, we must create a password, from which the key will be 
                //  derived. This password will be generated from the specified 
                //  passphrase and salt value. The password will be created using
                //  the specified hash algorithm. Password creation can be done in
                //  several iterations.
                PasswordDeriveBytes password;
                password = new PasswordDeriveBytes(passPhrase, saltValueBytes, hashAlgorithm, passwordIterations);

                //  Use the password to generate pseudo-random bytes for the encryption
                //  key. Specify the size of the key in bytes (instead of bits).
                byte[] keyBytes;
                keyBytes = password.GetBytes((keySize / 8));
                
                //  Create uninitialized Rijndael encryption object.
                RijndaelManaged symmetricKey;
                symmetricKey = new RijndaelManaged();
                symmetricKey.Padding = PaddingMode.Zeros;
                //  It is reasonable to set encryption mode to Cipher Block Chaining
                //  (CBC). Use default options for other symmetric key parameters.
                symmetricKey.Mode = CipherMode.CBC;
                //  Generate decryptor from the existing key bytes and initialization 
                //  vector. Key size will be defined based on the number of the key 
                //  bytes.
                ICryptoTransform decryptor;
                decryptor = symmetricKey.CreateDecryptor(keyBytes, initVectorBytes);
                //  Define memory stream which will be used to hold encrypted data.
                MemoryStream memoryStream;
                memoryStream = new MemoryStream(cipherTextBytes);
                //  Define memory stream which will be used to hold encrypted data.
                CryptoStream cryptoStream;
                cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read);
                
                //  Since at this point we don't know what the size of decrypted data
                //  will be, allocate the buffer long enough to hold ciphertext;
                //  plaintext is never longer than ciphertext.
                byte[] plainTextBytes=new byte[cipherTextBytes.Length];

                int decryptedByteCount;
                decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);


                StringBuilder ret = new StringBuilder();
                byte[] B = memoryStream.ToArray();
                //  Close both streams.
                memoryStream.Close();
                cryptoStream.Close();
              
                //  Convert decrypted data into a string. 
                //  Let us assume that the original plaintext string was UTF8-encoded.
                string plainText;
                plainText = Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
                //  Return decrypted string.
                return plainText;
            }
        }

 

Ejemplo VB.NET

  Const saltValue As String = "s@1tValue"         ' can be any string
    Const hashAlgorithm As String = "MD5"          ' can be "MD5"
    Const passwordIterations As Integer = 1          ' can be any number
    Const initVector As String = "@1B2c3D4e5F6g7H8" ' must be 16 bytes
    Const keySize As Integer = 192                   ' can be 192 or 128


  Private Shared Function Encrypt(ByVal plainText As String, ByVal passPhrase As String) As String

        ' Convert strings into byte arrays.
        ' Let us assume that strings only contain ASCII codes.
        ' If strings include Unicode characters, use Unicode, UTF7, or UTF8 
        ' encoding.

        Dim initVectorBytes As Byte()
        initVectorBytes = Encoding.ASCII.GetBytes(initVector)

        Dim saltValueBytes As Byte()
        saltValueBytes = Encoding.ASCII.GetBytes(saltValue)

        ' Convert our plaintext into a byte array.
        ' Let us assume that plaintext contains UTF8-encoded characters.
        Dim plainTextBytes As Byte()
        plainTextBytes = Encoding.UTF8.GetBytes(plainText)

        ' First, we must create a password, from which the key will be derived.
        ' This password will be generated from the specified passphrase and 
        ' salt value. The password will be created using the specified hash 
        ' algorithm. Password creation can be done in several iterations.
        Dim password As PasswordDeriveBytes

        password = New PasswordDeriveBytes(passPhrase, saltValueBytes, hashAlgorithm, passwordIterations)

        ' Use the password to generate pseudo-random bytes for the encryption
        ' key. Specify the size of the key in bytes (instead of bits).
        Dim keyBytes As Byte()
        keyBytes = password.GetBytes(keySize / 8)

        ' Create uninitialized Rijndael encryption object.
        Dim symmetricKey As RijndaelManaged
        symmetricKey = New RijndaelManaged()

        symmetricKey.Padding = PaddingMode.Zeros

        ' It is reasonable to set encryption mode to Cipher Block Chaining
        ' (CBC). Use default options for other symmetric key parameters.
        symmetricKey.Mode = CipherMode.CBC

        ' Generate encryptor from the existing key bytes and initialization 
        ' vector. Key size will be defined based on the number of the key 
        ' bytes.
        Dim encryptor As ICryptoTransform
        encryptor = symmetricKey.CreateEncryptor(keyBytes, initVectorBytes)

        ' Define memory stream which will be used to hold encrypted data.
        Dim memoryStream As MemoryStream
        memoryStream = New MemoryStream()

        ' Define cryptographic stream (always use Write mode for encryption).
        Dim cryptoStream As CryptoStream
        cryptoStream = New CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)
        ' Start encrypting.
        cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length)

        ' Finish encrypting.
        cryptoStream.FlushFinalBlock()

        ' Convert our encrypted data from a memory stream into a byte array.
        Dim cipherTextBytes As Byte()
        cipherTextBytes = memoryStream.ToArray()

        ' Close both streams.
        memoryStream.Close()
        cryptoStream.Close()



        ' Convert encrypted data into a base64-encoded string.
        'Dim cipherText As String = Convert.ToBase64String(cipherTextBytes)
        'Return cipherText

        Dim ret As StringBuilder = New StringBuilder
        Dim B() As Byte = cipherTextBytes
        Dim I As Integer
        For I = 0 To B.Length - 1
            ret.AppendFormat("{0:X2}", B(I))
        Next

        Return ret.ToString()
    End Function

 

    Public Shared Function Decrypt(ByVal cipherText As String, ByVal passPhrase As String) As String
        ' Convert strings defining encryption key characteristics into byte
        ' arrays. Let us assume that strings only contain ASCII codes.
        ' If strings include Unicode characters, use Unicode, UTF7, or UTF8
        ' encoding.
        If cipherText = String.Empty Then
            Return ""
        Else
            Dim initVectorBytes As Byte()
            initVectorBytes = Encoding.ASCII.GetBytes(initVector)

            Dim saltValueBytes As Byte()
            saltValueBytes = Encoding.ASCII.GetBytes(saltValue)

            ' Convert our ciphertext into a byte array.
            'Dim cipherTextBytes As Byte()
            'cipherTextBytes = Convert.FromBase64String(cipherText)
            'Dim cipherTextBytes() As Byte = Encoding.UTF8.GetBytes(cipherText)

            Dim cipherTextBytes(Convert.ToInt32(cipherText.Length / 2 - 1)) As Byte '= Encoding.UTF8.GetBytes(InputString)
            Dim X As Integer

            For X = 0 To cipherTextBytes.Length - 1
                Dim IJ As Int32 = (Convert.ToInt32(cipherText.Substring(X * 2, 2), 16))
                Dim BT As New ComponentModel.ByteConverter
                cipherTextBytes(X) = New Byte
                cipherTextBytes(X) = CType(BT.ConvertTo(IJ, GetType(Byte)), Byte)
            Next



            ' First, we must create a password, from which the key will be 
            ' derived. This password will be generated from the specified 
            ' passphrase and salt value. The password will be created using
            ' the specified hash algorithm. Password creation can be done in
            ' several iterations.
            Dim password As PasswordDeriveBytes
            password = New PasswordDeriveBytes(passPhrase, _
                                                saltValueBytes, _
                                                hashAlgorithm, _
                                                passwordIterations)

            ' Use the password to generate pseudo-random bytes for the encryption
            ' key. Specify the size of the key in bytes (instead of bits).
            Dim keyBytes As Byte()
            keyBytes = password.GetBytes(keySize / 8)

            ' Create uninitialized Rijndael encryption object.
            Dim symmetricKey As RijndaelManaged
            symmetricKey = New RijndaelManaged()

            symmetricKey.Padding = PaddingMode.Zeros

            ' It is reasonable to set encryption mode to Cipher Block Chaining
            ' (CBC). Use default options for other symmetric key parameters.
            symmetricKey.Mode = CipherMode.CBC

            ' Generate decryptor from the existing key bytes and initialization 
            ' vector. Key size will be defined based on the number of the key 
            ' bytes.
            Dim decryptor As ICryptoTransform
            decryptor = symmetricKey.CreateDecryptor(keyBytes, initVectorBytes)

            ' Define memory stream which will be used to hold encrypted data.
            Dim memoryStream As MemoryStream
            memoryStream = New MemoryStream(cipherTextBytes)

            ' Define memory stream which will be used to hold encrypted data.
            Dim cryptoStream As CryptoStream
            cryptoStream = New CryptoStream(memoryStream, _
                                            decryptor, _
                                            CryptoStreamMode.Read)

            ' Since at this point we don't know what the size of decrypted data
            ' will be, allocate the buffer long enough to hold ciphertext;
            ' plaintext is never longer than ciphertext.
            Dim plainTextBytes(cipherTextBytes.Length) As Byte

            ' Start decrypting.
            Dim decryptedByteCount As Integer
            decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length)

            Dim ret As StringBuilder = New StringBuilder
            Dim B() As Byte = memoryStream.ToArray

            ' Close both streams.
            memoryStream.Close()
            cryptoStream.Close()


            'Dim I As Integer
            'For I = 0 To UBound(B)
            '    ret.Append(Chr(B(I)))
            'Next

            'Return ret.ToString()



            ' Convert decrypted data into a string. 
            ' Let us assume that the original plaintext string was UTF8-encoded.
            Dim plainText As String
            plainText = Encoding.UTF8.GetString(plainTextBytes, _
                                                  0, _
                                                  decryptedByteCount)
            ' Return decrypted string.
            Decrypt = plainText
        End If
    End Function

 

Esto lo genera a través de su método ToString():
Ejemplo C#:

        public string ToString()
        {
            string[] parts = new string[this.Count];
            string[] keys = this.AllKeys;
            int i = 0;
            while ((i < keys.Length))
            {
                parts[i] = keys[i] + "=" + HttpContext.Current.Server.UrlEncode(this[keys[i]]);
                // Devuelve el menor de dos enteros de 16 bits con signo. 
                // Proporciona operaciones at�micas para las variables compartidas por varios subprocesos. 
                // http://msdn.microsoft.com/es-ar/library/system.threading.interlocked.aspx
                System.Math.Min(System.Threading.Interlocked.Increment(ref i), (i - 1));
            }
            string url = string.Join("&", parts);
            if (((!(url == null)
                        || !(url == String.Empty))
                        && !url.StartsWith("?")))
            {
                url = ("?" + url);
            }
            // If False Then
            //     url = Me._document + url
            // End If
            return url;
        }

Ejemplo VB.NET:

Public Overloads Function ToString() As String
            Dim parts(Me.Count) As String
            Dim keys As String() = Me.AllKeys
            Dim i As Integer = 0
            While i < keys.Length
                parts(i) = keys(i) + "=" + HttpContext.Current.Server.UrlEncode(Me(keys(i)))

                'Devuelve el menor de dos enteros de 16 bits con signo. 
                'Proporciona operaciones atómicas para las variables compartidas por varios subprocesos. 
                'http://msdn.microsoft.com/es-ar/library/system.threading.interlocked.aspx
                System.Math.Min(System.Threading.Interlocked.Increment(i), i - 1)
            End While
            Dim url As String = String.Join("&", parts)
            If (Not (url Is Nothing) OrElse Not (url = String.Empty)) AndAlso Not url.StartsWith("?") Then
                url = "?" + url
            End If
            'If False Then
            '    url = Me._document + url
            'End If
            Return url
        End Function

Aquí muestro como generar nuestro par Clave/Valor de QueryString encriptado

Ejemplo en C#:

 public static QueryString EncryptQueryString(QueryString queryString)
        {
            QueryString newQueryString = new QueryString();
            string nm = String.Empty;
            string val = String.Empty;
            foreach (string name in queryString)
            {
                nm = name;
                val = System.Web.HttpUtility.UrlEncode(queryString[name]);
                newQueryString.Add(Encryption.Encrypt(nm, DateTime.Today.ToString()), Encryption.Encrypt(val, DateTime.Today.ToString()));
            }
            return newQueryString;
        }


        public static QueryString DecryptQueryString(QueryString queryString)
        {
            QueryString newQueryString = new QueryString();
            string nm;
            string val;
            foreach (string name in queryString)
            {
                nm = Encryption.DecryptString(name, DateTime.Today.ToString());
                val = System.Web.HttpUtility.UrlDecode(Encryption.DecryptString(queryString[name], DateTime.Today.ToString()));
                newQueryString.Add(nm, val);
            }
            return newQueryString;
        }

Ejemplo en VB:NET:

 
Public Shared Function EncryptQueryString(ByVal queryString As QueryString) As QueryString
        Dim newQueryString As QueryString = New QueryString
        Dim nm As String = String.Empty
        Dim val As String = String.Empty
        For Each name As String In queryString
            nm = name
            val = System.Web.HttpUtility.UrlEncode(queryString(name))
            newQueryString.Add(Encryption.Encrypt(nm, Date.Today.ToString), Encryption.Encrypt(val, Date.Today.ToString))
        Next
        Return newQueryString
    End Function

  Public Shared Function DecryptQueryString(ByVal queryString As QueryString) As QueryString
        Dim newQueryString As QueryString = New QueryString
        Dim nm As String
        Dim val As String
        For Each name As String In queryString
            nm = Encryption.Decrypt(name, Date.Today.ToString)
            val = System.Web.HttpUtility.UrlDecode(Encryption.Decrypt(queryString(name), Date.Today.ToString))
            newQueryString.Add(nm, val)
        Next
        Return newQueryString
    End Function
 

El proyecto web compilado de ejemplo me queda asi:
foto2

foto3

En esta imagen puede verse el resultado final y un poco de depuración para disfrutar…..
foto6

foto7

Este es el primer valor a Encriptar…..

foto8

Este es el resultado cuando sale de la función…..

foto9

 

Y finalmente como queda nuestro QueryString en pantalla, que es lo que le llegará al cliente….

foto10

Es interesante, aunque en ingles, leer este articulo http://blog.codinghorror.com/why-isnt-my-encryption-encrypting/ que nos dice porque NO usar el modo ECB o BCE . Basicamente dice:

El modo de Electronic Codebook (BCE) encripta cada bloque individual. Esto significa que cualquier bloque de texto plano que son idénticos y están en el mismo mensaje o en otro mensaje cifrado con la misma clave, se transforman en bloques de texto cifrado idénticos. Si el texto plano a cifrar contiene repetición sustancial, es factible que el texto cifrado que se rompe un bloque a la vez.

Los 2 Proyectos están aquí  para poder DESCARGAR desde google Drive

Espero que esto les dé una sugerencia de como poder implementarlo en sus sistemas y darle mayor seguridad. Saludos

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s