【问题标题】:query string parameter obfuscation查询字符串参数混淆
【发布时间】:2011-04-03 22:38:21
【问题描述】:

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

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

这是我想要实现的示例:

www.domain.com/?id=1844

www.domain.com/?id=3GQ5DTL3oVd91WsGj74gcQ

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

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

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

更新: 我知道 UrlEncoding ,我想避免对字符串进行编码。 因为这会在 url 中生成 %F2 或 %B2 之类的字符。

【问题讨论】:

  • 为什么要加密这个值?避免猜测或模糊?
  • Base64是编码,编码不是加密
  • @sharru:我认为您的意思可能是“混淆”而不是“加密”。我建议您相应地编辑您的标签、标题和问题。如果你真的想加密,那么 base64 不是这样做的。
  • 我只是想避免用户输入 id=999 ,然后输入 id=1000。我希望网址是“干净的”(只是字母和数字)。这不可能吗?
  • 如果您依靠混淆来防止滥用,那么您已经失败了。相反,请更好地设计您的应用程序 - 例如,通过应用访问控制,这样人们就无法访问他们不应该访问的内容,或者通过为每条记录分配一个 GUID,这样您就有了一个长而难以猜测的标识符。

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


【解决方案1】:

您可以使用三重 DES 使用窄分组密码对值进行编码。

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 个字符宽。

【讨论】:

  • 感谢您的回答,看起来不错,我试试看。关于性能的任何想法?这是一个很大的超载吗?
  • 这不会导致非常大的开销。如果您使用的是 HTTPS,那么对于单个 HTML 页面,这个确切的操作会执行几百次,所以这应该是微不足道的。
  • 是否必须使用 unsafe 和指针?这些都不是我的日常菜单:)
  • 不,您可以删除指针,但您必须编写一个 for 循环将整数复制到字节数组中/从字节数组中复制。
  • @Lunatic, @sharru: Neither a loop nor unsafe block is necessary, System.BitConverter 会做得很好。
【解决方案2】:

您可以使用HttpServerUtility.UrlTokenEncodeHttpServerUtility.UrlTokenDecode

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

在之前的SO question 中也有类似的答案。查看接受的答案。

【讨论】:

    【解决方案3】:

    所以这是一个工作示例,我从几个不同的示例中组合在一起,它采用整数 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;
            }
        }
    
    }
    

    【讨论】:

    • 很高兴它有效!不幸的是,由于可以使用的字母数字字符有限,它很长。 :)
    【解决方案4】:

    混淆 id 的问题在于您需要一种去混淆的方法。这需要:

    1. 成熟的加密,如果它有任何好处,将需要相当大的价值。
    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;
    }
    

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

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

    根据上述情况,id 为 15 和reduce 为 3 会产生 05469B1E 的结果。然后我们可以将其用作:

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

    在查找 15 的处理程序中,我们再次执行相同的操作,如果结果与 05469B1E 不同,我们可以返回 403 Forbidden 或更合理的 404 Not Found(基于我们收到了一个整体上不识别任何内容的 URI)。

    【讨论】:

      【解决方案5】:

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

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

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

      【讨论】:

      • 感谢回答,我只是不想在网址中使用 %F2 或 %B2 之类的字符。
      【解决方案6】:

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

      【讨论】:

      • 抱歉,我错过了所有字符必须是字母数字的附加要求。如果这不是绝对要求,在 url 参数中包含“-”和“_”是安全的...
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-14
      • 2013-11-28
      • 1970-01-01
      相关资源
      最近更新 更多