C# 个人整的编码方式,将任意字节数组和可打印中文字符串相互转换

正常情况下咱们可以用Base64将字节数组转成可打印字符串,

但是有的时候咱们需要编码后具有一定的保密性,很明显Base64就不适用了,网上有个与熊论道就挺有意思的,

于是我也研究学习了下,自己实现了一个将字节流编码为可打印(可拷贝)中文字符串的功能,反之,也能将中文字符串解码为原始字节流

具体的思路如下

编码:

1,将字节流从8位一个数值,转成从高位开始14位一个数值

2,由于转换后无法确定最后的有效位的结束位置,特规定编码时,有效位的最后额外加上一位1

3,将新得到的0-16384范围内的数值每个都加上Unicode中文区开始位置0x4E00,这样每个数值就能对应一个中文字符

4,然后就是把数值转成字节流(大端)然后再通过Unicode(大端)编码位中文字符串

解码:

1,将中文字符串根据Unicode(大端)转成字节数组

2,将字节数组中每2个字节转成一个uint16数值(大端)

3,将数值减去0x4E00得到原始数值

4,将原始数值从14位一个转成8位一个的字节数组

代码:

    /// <summary>
    /// 将字节数组编码为可打印的中文字符串
    /// </summary>
    public class BaseHanZi {
        /// <summary>
        /// 对字节数组进行编码,返回一个可打印的中文字符串
        /// </summary>
        /// <param name="bs"></param>
        /// <returns></returns>
        public string Encode(byte[] bs) {
            List<int> list = ToNewBitSize(bs.Select(p => (int)p).ToList(), 8, 14); //将字节数组按照14位一个生成数字
            List<byte> arr = new List<byte>(); //选择14位是0x4E00加上他不会超出汉字区的范围,15位就超出了,更不要说16位整型
            foreach (var p in list) { //将每个数字+Unicode汉字开始区,将每个数字对应一个汉字编码
                arr.AddRange(BitConverter.GetBytes((ushort)(p + 0x4E00)).Reverse());
            }
            return Encoding.BigEndianUnicode.GetString(arr.ToArray()); //最后以大端Unicode编码将字节流转成字符串
        }
        /// <summary>
        /// 对一个可打印的中文字符串进行解码,得到一个字节数组
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public byte[] Decode(string str) {
            byte[] bs = Encoding.BigEndianUnicode.GetBytes(str); //将中文字符串以Unicode大端转成字节流
            List<int> list = new List<int>();
            int i = 0;
            byte[] temp = new byte[2];
            while (i < bs.Length) { //每2个字节转成一个数字
                temp[0] = bs[i + 1];
                temp[1] = bs[i];
                list.Add(BitConverter.ToUInt16(temp, 0) - 0x4E00); //减去Unicode汉字开始位置,得到原始的数值
                i += 2;
            }
            return ToNewBitSize(list, 14, 8).Select(p => (byte)p).ToArray(); //将14位的数值转成字节流
        }
        /// <summary>
        /// 将sizeOld比特位的数值转成sizeNew比特位的数值
        /// </summary>
        /// <param name="values"></param>
        /// <param name="sizeOld"></param>
        /// <param name="sizeNew"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public List<int> ToNewBitSize(IList<int> values, int sizeOld = 8, int sizeNew = 14) {
            if (sizeOld == sizeNew) {
                throw new Exception("长度一致没有意义!");
            }
            if (sizeNew > 16) {
                throw new Exception("新长度不能超过16!");
            }
            int endMark = 0x80; //结束码,因为变更后无法知道结束位,因此规定编码后必须以0x80即0b_1000_0000结尾
            if (sizeOld == 8) {
                values.Add(endMark); //编码时加入结束码
            }
            int numNew = 0;
            int size = 0; //新数据当前的长度
            int index = 0; //旧数据数组的转换进度索引
            List<int> list = new List<int>();
            int mark = 0xFFFF >> (16 - sizeOld); //旧数据的掩码,用来清除移位后超出的高位
            while (index < values.Count) {
                int len = sizeOld;  //旧数据可用长度
                int numOld = values[index]; //旧数据
                while (len > 0) {
                    int length = sizeNew - size; //新数据需要的长度
                    if (length >= len) {
                        length = len;
                    }
                    numNew <<= length; //左移留空间
                    numNew |= (numOld >> (sizeOld - length)); //将旧数据右移得到指定长度的高位数据并放到新数据中
                    size += length;
                    len -= length;
                    if (len > 0) {
                        numOld = (numOld << length) & mark; //如果旧数据没用完,移位调整其高位,方便下次使用
                    }
                    if (size == sizeNew) {
                        list.Add(numNew);
                        numNew = 0;
                        size = 0;
                    }
                }
                index++;
            }
            if (size > 0) {
                numNew <<= (sizeNew - size); //最后如果位,需要移位到最左侧,方便处理
                list.Add(numNew);
            }
            numNew = list[list.Count - 1];
            while (numNew == 0) { //因为加了结束码,最后一位不可能为0,移除结束码后面为0的数值
                list.RemoveAt(list.Count - 1);
                numNew = list[list.Count - 1];
            }
            if (sizeOld != 8) {
                if (numNew == endMark) { //解码时需要移除结束码
                    list.RemoveAt(list.Count - 1);
                }
            }
            return list;
        }
    }
View Code

测试:

        public void Start() {
            string str = "开始测试内容123阿斯顿abc测试结束了"; //明文字符串
            Console.WriteLine(str);
            byte[] arr = Encoding.UTF8.GetBytes(str);  //原始字节流,开头和结尾都可以有0
            BaseHanZi baseHanZi = new BaseHanZi();
            string hanzi = baseHanZi.Encode(arr); //编码
            Console.WriteLine(hanzi); //中文字符串,看上去像乱码,没人知道是什么鬼
            byte[] arr_decode = baseHanZi.Decode(hanzi); //解码
            string str_decode = Encoding.UTF8.GetString(arr_decode); //将解码后的字节流转成字符串
            Console.WriteLine(str_decode);
            Console.WriteLine(str == str_decode); //对比是否一致 
            Console.WriteLine(Convert.ToBase64String(arr)); //base64更简单,但是不具有保密性
        }

 

posted @ 2024-02-01 17:05  WmW  阅读(34)  评论(0编辑  收藏  举报