查询字符串参数混淆

时间:2010-08-25 20:03:46

标签: c# .net asp.net encryption cryptography

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

我的问题是我发现的所有算法都会产生不需要的字符(比如+ / =)

这是我想要实现的一个例子:

www.domain.com/?id=1844

www.domain.com/?id=3GQ5DTL3oVd91WsGj74gcQ

混淆的参数应该只包含 a-z和A-Z以及0-9 字符。

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

知道可以使用什么算法吗?

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

6 个答案:

答案 0 :(得分:5)

您可以使用HttpServerUtility.UrlTokenEncodeHttpServerUtility.UrlTokenDecode

Encode使用base64编码,但替换URL不友好的字符。

以前的SO question中有类似的答案。请参阅接受的答案。

答案 1 :(得分: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个字符宽。

答案 2 :(得分: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(第四回答)

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

}

答案 3 :(得分:3)

混淆id的问题是,你需要一种去混淆的方法。这需要:

  1. Fullblown加密,如果它有任何好处将需要相当大的值。
  2. 将值与ID号一起存储,因此它将成为替代标识符。
  3. 依赖于安全隐患的东西。
  4. 或者,保持身份清楚,但也要使用支票。

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

    为reduce指定的值越高,结果越小(直到6处它继续产生空字符串)。较低的值(或0)可以提供更好的安全性,代价是更长的URI。

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

    如上所述,id为15,reduce为3,结果为05469B1E。然后我们可以将其用作:

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

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

答案 4 :(得分:1)

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

  

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

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

答案 5 :(得分:0)

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