2010-08-25 255 views
7

我想混淆ASP.NET中的一个查询字符串参数。该网站将有大量的请求,所以算法不应该太慢。查询字符串参数混淆

我的问题是,所有的算法我发现结果中不想要的字符(如+/=)

这里是什么,我想实现一个例子:

www.domain.com/?id=1844 

www.domain.com/?id=3GQ5DTL3oVd91WsGj74gcQ 

混淆的参数应该只包括az和AZ和0-9个字符。

我知道我可以使用base64进行加密,但这会产生不需要的字符,如/=+

任何想法可以使用什么算法?

更新: 我知道UrlEncoding,我想避免编码字符串。 ,因为这会在网址中生成像%F2或%B2这样的字符。

+1

为什么要加密这个值?避免猜测或模糊? – 2010-08-25 20:07:41

+1

避免某种形式的滥用。 – RuSh 2010-08-25 20:10:03

+1

Base64是编码,而编码不是加密 – Aillyn 2010-08-25 20:10:42

回答

5

您可以使用三重DES使用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); 
     } 
    } 
} 

输出应该是这样的:

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

注意,编码值宽度仅为16个字符。

如果你真的关于滥用问题,那么AES可以以类似的方式使用。在下一个示例中,我切换到AES并将64位ID编号写入块的两侧。如果它在两侧都没有用相同的值进行解码,那么它会被拒绝。这可以防止人们用随机数字写作。

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."); 
     } 
    } 
} 

结果现在应该是这个样子:

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. 

还要注意,由于我们现在已经使用了更广泛的分组密码编码值现在是宽32个字符。

+0

感谢您的回答,看起来不错,生病了试试看。 有关性能的任何想法?这是一个很大的超载? – RuSh 2010-08-26 16:09:46

+0

这不应该导致非常显着的开销。如果你使用的是HTTPS,那么这个确切的操作仅仅为单个HTML页面执行几百次,所以这应该是微不足道的。 – 2010-08-27 01:07:19

+0

是不是必须使用不安全的指针?这些不是真的在我的日常菜单:) – RuSh 2010-08-28 13:55:59

1

您是否试过URL encoding查询字符串文本?这是HttpUtility类的一部分:

处理Web 请求时提供对编码和解码 网址的方法。

并且应该允许您在查询字符串中传递base64编码文本。

+0

thx为答案,我只是不想要像%F2或%B2在URL中的字符。 – RuSh 2010-08-25 20:17:40

0

进行加密,然后使用HttpServerUtility.UrlTokenEncode()对字节数组进行编码。

+0

试过了,网址仍然会包含“ - ”和“_”字符。 – RuSh 2010-08-25 20:34:12

+0

对不起,我错过了所有字符必须是字母数字的额外要求。如果这不是一个绝对的要求,那么在url参数中包含“ - ”和“_”是安全的...... – Peter 2010-08-25 20:45:47

4

所以这里有一个工作的例子,我从几个不同的例子中拿出一个整数ID并将其转换为十六进制格式的加密字符串。此加密的字符串不应包含URL不友好的字符,也不会包含转义字符。

这是整个工作控制台应用程序。请注意,这是一个原型,绝对不是生产 - 这只是一个解决方案,肯定需要重构。

当您运行的代码,你的输出应该是这样的:

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 

来源:
字节十六进制文章SO:Encryption to alphanumeric in System.Security.Cryptography
加密辅助类:Encrypt and decrypt a string(4日回答)

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

很酷的工作,只是编码的字符串非常长。 – RuSh 2010-08-26 16:08:50

+0

很好用!不幸的是,由于可以使用有限的字母数字字符,所以它很长。 :) – 2010-08-26 16:23:51

3

混淆id的问题是您需要一种去混淆的方式。这要求:

  1. Fullblown加密,如果它是好的将需要一个相当大的值。
  2. 存储值与id号一起,所以它成为一个替代标识符。
  3. 依赖于安全性的东西。

或者,保持ID清晰,但也使用支票。

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; 
} 

降低给定的值越高,结果越小(直到在6时它继续生成空字符串)。低值(或0)提供更好的安全性,但需要更长的URI。

字符串"This is an arbitrary salt"需要保密才能获得最佳安全性。它可以在某些用途中进行硬编码,但希望从其他用户的安全来源获得。

以上所述,15的id和3的reduce产生05469B1E的结果。然后,我们可以用这个为:

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

在处理程序,将查找任何15,我们再次做同样的事情,如果结果不同的是,以05469B1E我们可以返回一个403禁止或可以说是更合理的404 Not Found(基于我们收到了一个URI,整体而言,它没有识别任何内容)。