2010-08-25 14 views
7

Quiero ofuscar un parámetro de cadena de consulta en ASP.NET. El sitio tendrá un gran volumen de solicitudes, por lo que el algoritmo no debería ser demasiado lento.parámetro de cadena de consulta ofuscación

Mi problema es que todos los algoritmos que se han encontrado resultados en caracteres no deseados (como +/=)

Aquí es un ejemplo de lo que quiero lograr:

www.domain.com/?id=1844 

a

www.domain.com/?id=3GQ5DTL3oVd91WsGj74gcQ 

El parámetro ofuscado solo debe incluir az y AZ y 0-9 caracteres.

Sé que puedo encriptar usando base64, pero esto generará caracteres no deseados como / o = o +.

¿Alguna idea de qué algoritmo se puede utilizar?

Actualización: Conozco UrlEncoding, quiero evitar la codificación de la cadena. porque eso generará caracteres como% F2 o% B2 en la url.

+1

¿Por qué quiere encriptar este valor? ¿Evitando adivinar u oscurecer? –

+1

evitando algún tipo de abuso. – RuSh

+1

Base64 es la codificación, y la codificación no es el cifrado – Aillyn

Respuesta

5

Puede usar triple DES para codificar el valor usando un cifrado de bloque narow.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Security.Cryptography; 

namespace ConsoleApplication1 { 
    class Program { 
     static string ToHex(byte[] value) { 
      StringBuilder sb = new StringBuilder(); 
      foreach (byte b in value) 
       sb.AppendFormat("{0:x2}", b); 
      return sb.ToString(); 
     } 
     static string Encode(long value, byte[] key) { 
      byte[] InputBuffer = new byte[8]; 
      byte[] OutputBuffer; 
      unsafe { 
       fixed (byte* pInputBuffer = InputBuffer) { 
        ((long*)pInputBuffer)[0] = value; 
       } 
      } 
      TripleDESCryptoServiceProvider TDes = new TripleDESCryptoServiceProvider(); 
      TDes.Mode = CipherMode.ECB; 
      TDes.Padding = PaddingMode.None; 
      TDes.Key = key; 

      using (ICryptoTransform Encryptor = TDes.CreateEncryptor()) { 
       OutputBuffer = Encryptor.TransformFinalBlock(InputBuffer, 0, 8); 
      } 
      TDes.Clear(); 

      return ToHex(OutputBuffer); 
     } 
     static long Decode(string value, byte[] key) { 
      byte[] InputBuffer = new byte[8]; 
      byte[] OutputBuffer; 

      for (int i = 0; i < 8; i++) { 
       InputBuffer[i] = Convert.ToByte(value.Substring(i * 2, 2), 16); 
      } 

      TripleDESCryptoServiceProvider TDes = new TripleDESCryptoServiceProvider(); 
      TDes.Mode = CipherMode.ECB; 
      TDes.Padding = PaddingMode.None; 
      TDes.Key = key; 

      using (ICryptoTransform Decryptor = TDes.CreateDecryptor()) { 
       OutputBuffer = Decryptor.TransformFinalBlock(InputBuffer, 0, 8); 
      } 
      TDes.Clear(); 

      unsafe { 
       fixed (byte* pOutputBuffer = OutputBuffer) { 
        return ((long*)pOutputBuffer)[0]; 
       } 
      } 
     } 
     static void Main(string[] args) { 
      long NumberToEncode = (new Random()).Next(); 
      Console.WriteLine("Number to encode = {0}.", NumberToEncode); 
      byte[] Key = new byte[24]; 
      (new RNGCryptoServiceProvider()).GetBytes(Key); 
      Console.WriteLine("Key to encode with is {0}.", ToHex(Key)); 
      string EncodedValue = Encode(NumberToEncode, Key); 
      Console.WriteLine("The encoded value is {0}.", EncodedValue); 
      long DecodedValue = Decode(EncodedValue, Key); 
      Console.WriteLine("The decoded result is {0}.", DecodedValue); 
     } 
    } 
} 

La salida debería ser algo como esto:

Number to encode = 873435734. 
Key to encode with is 38137b6a7aa49cc6040c4297064fdb4461c79a895f40b4d1. 
The encoded value is 43ba3fb809a47b2f. 
The decoded result is 873435734. 

Tenga en cuenta que el valor codificado es sólo 16 caracteres de ancho.

Si realmente está consiente sobre el abuso, entonces AES se puede usar de manera similar. En el siguiente ejemplo, cambio en AES y escribo el número de identificación de 64 bits en ambos lados del bloque. Si no decodifica con el mismo valor en ambos lados, entonces es rechazado. Esto puede evitar que las personas escriban en números aleatorios.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Security.Cryptography; 

namespace ConsoleApplication1 { 
    class Program { 
     static string ToHex(byte[] value) { 
      StringBuilder sb = new StringBuilder(); 
      foreach (byte b in value) 
       sb.AppendFormat("{0:x2}", b); 
      return sb.ToString(); 
     } 
     static string Encode(long value, byte[] key) { 
      byte[] InputBuffer = new byte[16]; 
      byte[] OutputBuffer; 
      unsafe { 
       fixed (byte* pInputBuffer = InputBuffer) { 
        ((long*)pInputBuffer)[0] = value; 
        ((long*)pInputBuffer)[1] = value; 
       } 
      } 
      AesCryptoServiceProvider Aes = new AesCryptoServiceProvider(); 
      Aes.Mode = CipherMode.ECB; 
      Aes.Padding = PaddingMode.None; 
      Aes.Key = key; 

      using (ICryptoTransform Encryptor = Aes.CreateEncryptor()) { 
       OutputBuffer = Encryptor.TransformFinalBlock(InputBuffer, 0, 16); 
      } 
      Aes.Clear(); 

      return ToHex(OutputBuffer); 
     } 
     static bool TryDecode(string value, byte[] key, out long result) { 
      byte[] InputBuffer = new byte[16]; 
      byte[] OutputBuffer; 

      for (int i = 0; i < 16; i++) { 
       InputBuffer[i] = Convert.ToByte(value.Substring(i * 2, 2), 16); 
      } 

      AesCryptoServiceProvider Aes = new AesCryptoServiceProvider(); 
      Aes.Mode = CipherMode.ECB; 
      Aes.Padding = PaddingMode.None; 
      Aes.Key = key; 

      using (ICryptoTransform Decryptor = Aes.CreateDecryptor()) { 
       OutputBuffer = Decryptor.TransformFinalBlock(InputBuffer, 0, 16); 
      } 
      Aes.Clear(); 

      unsafe { 
       fixed (byte* pOutputBuffer = OutputBuffer) { 
        //return ((long*)pOutputBuffer)[0]; 
        if (((long*)pOutputBuffer)[0] == ((long*)pOutputBuffer)[1]) { 
         result = ((long*)pOutputBuffer)[0]; 
         return true; 
        } 
        else { 
         result = 0; 
         return false; 
        } 
       } 
      } 
     } 
     static void Main(string[] args) { 
      long NumberToEncode = (new Random()).Next(); 
      Console.WriteLine("Number to encode = {0}.", NumberToEncode); 
      byte[] Key = new byte[24]; 
      (new RNGCryptoServiceProvider()).GetBytes(Key); 
      Console.WriteLine("Key to encode with is {0}.", ToHex(Key)); 
      string EncodedValue = Encode(NumberToEncode, Key); 
      Console.WriteLine("The encoded value is {0}.", EncodedValue); 
      long DecodedValue; 
      bool Success = TryDecode(EncodedValue, Key, out DecodedValue); 
      if (Success) { 
       Console.WriteLine("Successfully decoded the encoded value."); 
       Console.WriteLine("The decoded result is {0}.", DecodedValue); 
      } 
      else 
       Console.WriteLine("Failed to decode encoded value. Invalid result."); 
     } 
    } 
} 

El resultado debería verse algo así:

Number to encode = 1795789891. 
Key to encode with is 6c90323644c841a00d40d4407e23dbb2ab56530e1a4bae43. 
The encoded value is 731fceec2af2fcc2790883f2b79e9a01. 
Successfully decoded the encoded value. 
The decoded result is 1795789891. 

También tenga en cuenta que, dado que ahora hemos utilizado un bloque más amplio cifrado el valor codificado es ahora de 32 caracteres de ancho.

+0

gracias por la respuesta, se ve muy bien, lo intentaré. alguna idea sobre el rendimiento? ¿Es esto una gran sobrecarga? – RuSh

+0

Esto no debería generar gastos indirectos muy significativos. Si está utilizando HTTPS, esta operación exacta se realiza unos cientos de veces solo para una sola página HTML, por lo que esto debería ser trivial. –

+0

¿es obligatorio utilizar punteros y marcadores inseguros? esos no están realmente en mi menú diario :) – RuSh

1

¿Has probado URL encoding el texto de la cadena de consulta? Es parte de la clase HttpUtility cuales:

proporciona métodos para codificar y decodificar URLs al procesar peticiones web .

y debería permitirle pasar su texto codificado en base64 en la cadena de consulta.

+0

Gracias por responder, simplemente no quiero personajes como% F2 o% B2 en la url. – RuSh

0

Haga su encriptación y luego use HttpServerUtility.UrlTokenEncode() para codificar la matriz de bytes.

+0

lo probé, la url aún incluirá caracteres "-" y "_". – RuSh

+0

Lo siento, me perdí el requisito adicional de que todos los caracteres tenían que ser alfanuméricos. Si eso no es un requisito absoluto, es seguro incluir el "-" y "_" en un parámetro url ... – Peter

4

Este es un ejemplo de trabajo que preparo a partir de algunos ejemplos diferentes que toman una identificación entera y la convierten en una cadena encriptada con formato hexadecimal. Esta cadena cifrada no debe incluir caracteres hostiles para URL y tampoco incluirá caracteres escapados.

Aquí está toda la aplicación de la consola en funcionamiento. Tenga en cuenta que se trata de un prototipo y definitivamente no de producción; esto solo ilustra una solución y definitivamente necesita ser refactorizado.

Cuando se ejecuta el código, la salida debería ser la siguiente:

1234 get encrypted as ZaB5GE/bWMJcNaeY/xJ6PQ== 
ZaB5GE/bWMJcNaeY/xJ6PQ== encrypted is this in hex 5a61423547452f62574d4a634e6165592f784a3650513d3d 
5a61423547452f62574d4a634e6165592f784a3650513d3d gets dehexed as ZaB5GE/bWMJcNaeY/xJ6PQ== 
ZaB5GE/bWMJcNaeY/xJ6PQ== got decrypted as 1234 

Fuentes:
bytes al artículo hexagonal en SO: Encryption to alphanumeric in System.Security.Cryptography
Crypto clase de ayuda: Encrypt and decrypt a string (cuarta respuesta)

Program2.cs

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 

using System.Security.Cryptography; 
using System.IO; 

namespace ConsoleApplication1 
{ 
    class Program2 
    { 
     static void Main(string[] args) 
     { 
      int theId = 1234; //the ID that's being manipulated 
      byte[] byteArray; //the byte array that stores 

      //convert the ID to an encrypted string using a Crypto helper class 
      string encryptedString = Crypto.EncryptStringAES(theId.ToString(), "mysecret"); 
      Console.WriteLine("{0} get encrypted as {1}", theId.ToString(), encryptedString); 

      //convert the encrypted string to byte array 
      byteArray = ASCIIEncoding.Default.GetBytes(encryptedString); 
      StringBuilder result = new StringBuilder(); 

      //convert each byte to hex and append to a stringbuilder 
      foreach (byte outputByte in byteArray) 
      { 
       result.Append(outputByte.ToString("x2")); 
      } 

      Console.WriteLine("{0} encrypted is this in hex {1}", encryptedString, result.ToString()); 

      //now reverse the process, and start with converting each char in string to byte 
      int stringLength = result.Length; 
      byte[] bytes = new byte[stringLength/2]; 

      for (int i = 0; i < stringLength; i += 2) 
      { 
       bytes[i/2] = System.Convert.ToByte(result.ToString().Substring(i, 2), 16); 
      } 

      //convert the byte array to de-"hexed" string 
      string dehexedString = ASCIIEncoding.Default.GetString(bytes); 

      Console.WriteLine("{0} gets dehexed as {1}", result, dehexedString); 

      //decrypt the de-"hexed" string using Crypto helper class 
      string decryptedString = Crypto.DecryptStringAES(dehexedString, "mysecret"); 
      Console.WriteLine("{0} got decrypted as {1}", dehexedString, decryptedString); 

      Console.ReadLine(); 
     } 
    } 

    public class Crypto 
    { 
     private static byte[] _salt = Encoding.ASCII.GetBytes("o6806642kbM7c5"); 

     /// <summary> 
     /// Encrypt the given string using AES. The string can be decrypted using 
     /// DecryptStringAES(). The sharedSecret parameters must match. 
     /// </summary> 
     /// <param name="plainText">The text to encrypt.</param> 
     /// <param name="sharedSecret">A password used to generate a key for encryption.</param> 
     public static string EncryptStringAES(string plainText, string sharedSecret) 
     { 
      if (string.IsNullOrEmpty(plainText)) 
       throw new ArgumentNullException("plainText"); 
      if (string.IsNullOrEmpty(sharedSecret)) 
       throw new ArgumentNullException("sharedSecret"); 

      string outStr = null;      // Encrypted string to return 
      RijndaelManaged aesAlg = null;    // RijndaelManaged object used to encrypt the data. 

      try 
      { 
       // generate the key from the shared secret and the salt 
       Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(sharedSecret, _salt); 

       // Create a RijndaelManaged object 
       // with the specified key and IV. 
       aesAlg = new RijndaelManaged(); 
       aesAlg.Key = key.GetBytes(aesAlg.KeySize/8); 
       aesAlg.IV = key.GetBytes(aesAlg.BlockSize/8); 

       // Create a decrytor to perform the stream transform. 
       ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV); 

       // Create the streams used for encryption. 
       using (MemoryStream msEncrypt = new MemoryStream()) 
       { 
        using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) 
        { 
         using (StreamWriter swEncrypt = new StreamWriter(csEncrypt)) 
         { 

          //Write all data to the stream. 
          swEncrypt.Write(plainText); 
         } 
        } 
        outStr = Convert.ToBase64String(msEncrypt.ToArray()); 
       } 
      } 
      finally 
      { 
       // Clear the RijndaelManaged object. 
       if (aesAlg != null) 
        aesAlg.Clear(); 
      } 

      // Return the encrypted bytes from the memory stream. 
      return outStr; 
     } 

     /// <summary> 
     /// Decrypt the given string. Assumes the string was encrypted using 
     /// EncryptStringAES(), using an identical sharedSecret. 
     /// </summary> 
     /// <param name="cipherText">The text to decrypt.</param> 
     /// <param name="sharedSecret">A password used to generate a key for decryption.</param> 
     public static string DecryptStringAES(string cipherText, string sharedSecret) 
     { 
      if (string.IsNullOrEmpty(cipherText)) 
       throw new ArgumentNullException("cipherText"); 
      if (string.IsNullOrEmpty(sharedSecret)) 
       throw new ArgumentNullException("sharedSecret"); 

      // Declare the RijndaelManaged object 
      // used to decrypt the data. 
      RijndaelManaged aesAlg = null; 

      // Declare the string used to hold 
      // the decrypted text. 
      string plaintext = null; 

      try 
      { 
       // generate the key from the shared secret and the salt 
       Rfc2898DeriveBytes key = new Rfc2898DeriveBytes(sharedSecret, _salt); 

       // Create a RijndaelManaged object 
       // with the specified key and IV. 
       aesAlg = new RijndaelManaged(); 
       aesAlg.Key = key.GetBytes(aesAlg.KeySize/8); 
       aesAlg.IV = key.GetBytes(aesAlg.BlockSize/8); 

       // Create a decrytor to perform the stream transform. 
       ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV); 
       // Create the streams used for decryption.     
       byte[] bytes = Convert.FromBase64String(cipherText); 
       using (MemoryStream msDecrypt = new MemoryStream(bytes)) 
       { 
        using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) 
        { 
         using (StreamReader srDecrypt = new StreamReader(csDecrypt)) 

          // Read the decrypted bytes from the decrypting stream 
          // and place them in a string. 
          plaintext = srDecrypt.ReadToEnd(); 
        } 
       } 
      } 
      finally 
      { 
       // Clear the RijndaelManaged object. 
       if (aesAlg != null) 
        aesAlg.Clear(); 
      } 

      return plaintext; 
     } 
    } 

} 
+0

genial esto funciona, solo la cadena codificada es realmente larga. – RuSh

+0

¡Me alegro de que funcione! Desafortunadamente es largo debido a los caracteres alfanuméricos limitados que se pueden usar. :) –

3

El problema con ofuscar la identificación, es que necesita una forma de desactivar la ofuscación. Esto requiere:

  1. Encriptación completa, que si es buena requerirá un valor bastante grande.
  2. Almacenando el valor junto con el número de identificación, por lo que se convierte en un identificador alternativo.
  3. Algo que depende de la seguridad por oscuridad.

De forma alternativa, mantenga la identificación clara, pero use también un cheque.

public static String ChkSumStr(int id, int reduce) 
{ 
    return string.Concat(ReduceStrength(ChkSum(id), reduce).Select(b => b.ToString("X2")).ToArray()); 
} 
public static byte[] ChkSum(int id) 
{ 
    byte[] idBytes = Encoding.UTF8.GetBytes("This is an arbitrary salt" + id); 
    return SHA256.Create().ComputeHash(idBytes); 
} 
private static byte[] ReduceStrength(byte[] src, int reduce) 
{ 
    byte[] ret = null; 
    for(int i = 0; i != reduce; ++i) 
    { 
    ret = new byte[src.Length/2]; 
    for(int j = 0; j != ret.Length; ++j) 
    { 
     ret[j] = (byte)(src[j * 2]^src[j * 2 + 1]); 
    } 
    src = ret; 
    } 
    return src; 
} 

Cuanto mayor sea el valor dado para reducir, menor será el resultado (hasta al 6 se sigue produciendo la cadena vacía). Un valor bajo (o 0) proporciona una mayor seguridad, a costa de un URI más largo.

La cadena "This is an arbitrary salt" debe ser secreta para la mejor seguridad. Puede ser codificado en algunos usos, pero desearía obtenerse de una fuente segura para otros.

Con lo anterior, un id de 15 y un reduce de 3 produce un resultado de 05469B1E. entonces podemos utilizar esto como:

www.domain.com/?id=15&chk=05469B1E

En el controlador que se vería a lo 15 es, hacemos lo mismo otra vez, y si el resultado es diferente a 05469B1E podemos o bien devolver un 403 Forbidden o podría decirse que es más razonable 404 No encontrado (sobre la base de que hemos recibido un URI que en su conjunto no identifica nada).

Cuestiones relacionadas