Code Copied

分享1种加密解密方式

在Web应用中,通过HTTP GET的方式去请求其他页面是常有的事情,在发起HTTP GET请求时常会伴随一些请求参数。
在请求参数中可能会包含一些敏感信息,为了不让用户在知道敏感信息的情况下,模拟出非用户权限内的请求,我们常用的一种做法是对请求参数进行加密。
下面分享1种我在实际项目中用到的1种加密方式。

1. 加密方式说明

No. 加密步骤 处理 备注
1 时间戳编码 [原始字符串][yyyy][mm][dd][hh][MM][ss]
【例】
原始字符串:17001234567A001
加上时间戳后的字符串:17001234567A00120130102112233
原始字符串有15个字符,时间戳有14个字符,共计29个字符。
时间戳采用用户的访问时间,即DateTime.Now.ToString("yyyyMMddHHmmss")。
2 异或运算编码 固定键预定29个字符,和No.1的加密字符串长度一致。
①将固定字符串的每1位和No.1的加密字符串进行异或运算(^)。
②将异或预算的结果转换为2位16进制字符串,不足2位的首位补0。
③将29对字符的异或运算结果按顺序拼接为1个字符串。
【例】
利用No.1的结果和以下固定键做异或运算,得到长度为58的一个字符串。
固定鍵:abcdefghijklmnopqrstuvwxyz012
输出结果:505553545454545C5C5C5C2D5D5E5E4241434044444645494848020201
 
3 Base64编码 对No2.的字符串进行Base64编码
①将No2.的结果从0开始,对每2位字符以16进制的形式转换为byte,得到长度29的Byte数组。
②将Byte数组转换为Base64字符串。
【例】
No2.的结果Base64编码:
UFVTVFRUVFxcXFwtXV5eQkFDQERERkVJSEgCAgE=
最终得到的始终是长度为40的Base64字符串,并以字符=结尾。
4 Url编码 对No3.的结果进行URL编码
【例】
No.3的结果URL编码:
UFVTVFRUVFxcXFwtXV5eQkFDQERERkVJSEgCAgE%3d
在Base64字符串中可能包含3个特殊字符:「+」、「/」、「=」。
因此最终URL编码后的字符串最小长度为42,最大长度为120。

对应的处理流程如下:

image

2. 加密和解密实现

既然有加密方式,那么就应该有对应的解密方式,以让服务器能够真正获知请求参数的值。
下面的过程描述了加密解密的实现技术以、源代码和运行结果。

实现技术

根据上面加密的实现步骤说明,可以逆推出解密的实现步骤,加密和解密的实现将会用到以下一些基础技术:

  • 日期格式化
  • 异或运算(位运算的一种)和逆运算
  • 进制转换
  • Base64编解码
  • URL编解码

加密和解密的方法各定义1个类,分别为Encryptor类和Decryptor类。

Encryptor类实现

public class Encryptor
{
    // The key for XOR operator
    private const string key = "abcdefghijklmnopqrstuvwxyz012";

    public static string Encrypt(string orignalString)
    {
        // Check the lenght of orignalString.
        if (orignalString.Length != 15)
        {
            throw new ArgumentException("The orignalString must have 15 characters.");
        }
        // Format the orignalString with DateTime.
        orignalString = orignalString + DateTime.Now.ToString("yyyyMMddHHmmss");
        // Check the length of orignalString and key.
        if (orignalString.Length != key.Length)
        {
            throw new ArgumentException("The string and key must have the same character length.");
        }

        // Build the hex string with XOR operator.
        var hexBuilder = new StringBuilder();
        for (var i = 0; i < orignalString.Length; i++)
        {
            var c1 = orignalString[i];
            var c2 = key[i];
            var xor = (c1 ^ c2);
            var hex = Convert.ToString(xor, 16);
            // If the hex lenth is 1, then fill "0" in the front.
            if (hex.Length == 1)
            {
                hex = "0" + hex;
            }
            hexBuilder.Append(hex);
        }
        string hexString = hexBuilder.ToString();

        // Parse the hex string to a byte array and output as a base64 string.
        var bytes = new byte[29];
        var j = 0;
        for (var i = 0; i < hexString.Length; i += 2)
        {
            bytes[j] = byte.Parse(hexString.Substring(i, 2), NumberStyles.HexNumber);
            j++;
        }
        string base64String = Convert.ToBase64String(bytes);
        // Encode the base64 string.
        return HttpUtility.UrlEncode(base64String);
    }
}

Decryptor类实现

public class Decryptor
{
    // The key for XOR operator
    private const string key = "abcdefghijklmnopqrstuvwxyz012";
    public static string Decrypt(string encryptedString)
    {
        
        // Recovery the encoded string to base64 string.
        string base64String = HttpUtility.UrlDecode(encryptedString);

        // Recoverty the base64 string to hex bytes.
        byte[] bytes = Convert.FromBase64String(base64String);

        // Recovery the byte[] array to hex string.
        var hexBuilder = new StringBuilder();
        foreach (var b in bytes)
        {
            var hex = Convert.ToString(b, 16);
            if (hex.Length == 1)
            {
                hex = "0" + hex;
            }
            hexBuilder.Append(hex);
        }

        // Recovery the XOR operation
        var resultBuilder = new StringBuilder();
        string hexString = hexBuilder.ToString();
        var j = 0;
        for (var i = 0; i < hexString.Length; i += 2)
        {
            var hex = hexString.Substring(i, 2);
            var b = Convert.ToByte(hex, 16);
            var xor = b ^ key[j];
            resultBuilder.Append((char)xor);
            j++;
        }

        // Remove the timestamp suffix.
        var originalString = resultBuilder.ToString().Substring(0, 15);
        return originalString;
    }
}

运行代码和结果

class Program
{
    static void Main(string[] args)
    {
        string originalString = "17001234567A001";
        Console.WriteLine("------------------------------------------------------");
        Console.WriteLine("Original String:");
        Console.WriteLine(originalString);
        Console.WriteLine("------------------------------------------------------");
        Console.WriteLine("Encrypted String:");
        string encryptedString = Encryptor.Encrypt(originalString);
        Console.WriteLine(encryptedString);
        Console.WriteLine("------------------------------------------------------");
        Console.WriteLine("Decrypted String:");
        string decryptedString = Decryptor.Decrypt(encryptedString);
        Console.WriteLine(decryptedString);
        Console.WriteLine("------------------------------------------------------");
        Console.ReadKey();
    }
}

 

SNAGHTML189a22f

3. 总结

在实现自定义的加密解密方式时,通常的方法是设定多个“编码”步骤,这里的“编码”并非C#里面的编码方式,更确切地说是“自定义编码方式”(例如本文示例中的时间戳编码、异或预算编码、Base64编码、URL编码)。
然后将这些编码步骤混合起来,最终输出一个用户无法识别的字符串。