2010-08-25 174 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,整體而言,它沒有識別任何內容)。