C#根据Base64规则自己编写相应的Base64加密和解密函数

上一篇我们已经使用C#函数Convert.ToBase64String()和Convert.FromBase64String()来加密和解密Base64,这里我们使用字典Dictionary<byte,char>来实现Base64加密和解密。

Base64介绍以及加密解密

新建控制台程序Base64ConsoleDemo,选择框架为.net framework 4.5

新建Base64转换和还原类Base64Convert。

Base64Convert.cs的源程序如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace Base64ConsoleDemo
{
    /// <summary>
    /// Base64字符串的长度一定是4的倍数,
    /// 尾巴没有填充等号,说明源字节数组长度是3的倍数【3*N】
    /// 尾巴有一个填充等号,说明源字节数组长度多余两个字节【3*N+2】
    /// 尾巴有两个填充等号,说明源字节数组长度多余一个字节【3*N+1】
    /// </summary>
    public class Base64Convert
    {
        /// <summary>
        /// 6Bit数字【0~63】映射Base64字符表如下,补位我们使用=等号代替,按(64,'=')处理
        /// </summary>
        private static readonly Dictionary<byte, char> base64Table = new Dictionary<byte, char>()
        {
            {0, 'A'},{1, 'B'},{2, 'C'},{3, 'D'},{4, 'E'},{5, 'F'},{6, 'G'},{7, 'H'},{8, 'I'},{9, 'J'},{10, 'K'},{11, 'L'},{12, 'M'},
            {13, 'N'},{14, 'O'},{15, 'P'},{16, 'Q'},{17, 'R'},{18, 'S'},{19, 'T'},{20, 'U'},{21, 'V'},{22, 'W'},{23, 'X'},{24, 'Y'},{25, 'Z'},
            {26, 'a'},{27, 'b'},{28, 'c'},{29, 'd'},{30, 'e'},{31, 'f'},{32, 'g'},{33, 'h'},{34, 'i'},{35, 'j'},{36, 'k'},{37, 'l'},{38, 'm'},
            {39, 'n'},{40, 'o'},{41, 'p'},{42, 'q'},{43, 'r'},{44, 's'},{45, 't'},{46, 'u'},{47, 'v'},{48, 'w'},{49, 'x'},{50, 'y'},{51, 'z'},
            {52, '0'},{53, '1'},{54, '2'},{55, '3'},{56, '4'},{57, '5'},{58, '6'},{59, '7'},{60, '8'},{61, '9'},
            {62, '+'},{63, '/'},{64, '='},
        };

        /// <summary>
        /// 【1~3个分段数组】转化成 长度为4的字符串
        /// 如果segmentArray部分数组只有一个元素,则结果增加两个填充字符【==】
        /// 如果segmentArray部分数组有两个元素,则结果增加一个填充字符【=】
        /// 如果segmentArray部分数组有三个元素,则结果无填充字符
        /// </summary>
        /// <param name="segmentArray">分段数组,一般个数为3</param>
        /// <returns></returns>
        private static string ToFourCharArray(ArraySegment<byte> segmentArray)
        {
            if (segmentArray == null)
            {
                throw new ArgumentNullException(nameof(segmentArray), "分段数组不能为空");
            }
            int count = segmentArray.Count;
            if (count < 1 || count > 3)
            {
                throw new Exception("分段数组的实际长度必须在[1,3]之间");
            }
            char[] destCharArray = new char[4];
            byte[] srcData = segmentArray.ToArray();
            //三个字节【24位】转4个字节【每6位作为一组】,由0和1组成的24位字符串
            //二个字节【16位】转3个字节【每6位作为一组】,强行在右边补充两个0,凑够18位【6*3】
            //一个字节【8位】转2个字节【每6位作为一组】,强行在右边补充四个0,凑够12位【6*2】
            string str = string.Join("", srcData.Select(element => Convert.ToString(element, 2).PadLeft(8, '0')));
            if (str.Length == 8)
            {
                //当一个字节时,强行在右边补充四个0,凑够12位【6*2】
                str = str + "0000";
            }
            else if (str.Length == 16)
            {
                //当两个字节时,强行在右边补充两个0,凑够18位【6*3】
                str = str + "00";
            }
            int cnt = 0;//转化的【6位】字节个数
            int startIndex = 0;
            byte keyIndex = 0;
            while (startIndex < str.Length)
            {
                keyIndex = Convert.ToByte(str.Substring(startIndex, 6), 2);
                destCharArray[cnt] = base64Table[keyIndex];
                startIndex += 6;
                cnt++;
            }
            //三个【8位】字节 转化为4个【6位】字节,默认 cnt为4
            if (cnt == 3)
            {
                //二个字节【16位】转3个字节【每6位作为一组】,最后一个用=等号填充,凑够4个字符
                destCharArray[3] = base64Table[64];
            }
            else if (cnt == 2)
            {
                //一个字节【8位】转2个字节【每6位作为一组】,最后两个用=等号填充,凑够4个字符
                destCharArray[2] = base64Table[64];
                destCharArray[3] = base64Table[64];
            }
            return new string(destCharArray);
        }

        /// <summary>
        /// 四个Base64字符串段 转为三个字节,当尾巴填充一个等号时,返回2个字节
        /// 当尾巴填充两个等号时,返回1个字节
        /// </summary>
        /// <param name="segmentString">长度为4的Base64分段字符串</param>
        /// <returns></returns>
        private static byte[] ToThreeByteArray(string segmentString)
        {
            if (segmentString == null)
            {
                throw new ArgumentNullException(nameof(segmentString), "要还原的Base64片段字符串不能为空");
            }
            if (segmentString.Length != 4)
            {
                throw new Exception($"Base64片段字符串的长度必须为4,当前字符串长度【{segmentString.Length}】");
            }
            //移除尾巴的填充字符 等号=
            segmentString = segmentString.Trim('=');
            //根据字典base64Table的值查找对应的索引键,因为去掉了等号(=),因此索引键的范围为【0~63】
            string binaryString = "";//每个Base64字符的索引转换成6位二进制,然后拼接起来,字符串长度为24 或者 18 或者 12
            for (int i = 0; i < segmentString.Length; i++)
            {
                KeyValuePair<byte, char> keyValuePair = base64Table.ToList().Find(keyValue => keyValue.Value == segmentString[i]);
                //每个Base64字符的索引转换成6位二进制
                binaryString += Convert.ToString(keyValuePair.Key, 2).PadLeft(6, '0');
            }
            if (segmentString.Length == 3)
            {
                //如果填充一个等号,binaryString长度为18,只抓取16位
                binaryString = binaryString.Remove(16, 2);//移除第三个【6位二进制】的后两位
            }
            else if (segmentString.Length == 2)
            {
                //如果填充两个等号,binaryString长度为12,只抓取8位
                binaryString = binaryString.Remove(8, 4);//移除第二个【6位二进制】的后四位
            }

            List<byte> list = new List<byte>();
            int startIndex = 0;
            //此时binaryString的长度可能是8,16,24
            while (startIndex < binaryString.Length)
            {
                list.Add(Convert.ToByte(binaryString.Substring(startIndex, 8), 2));
                startIndex += 8;
            }
            return list.ToArray();
        }

        /// <summary>
        /// 字符串 转 Base64,转化后的字符串长度一定是4的倍数
        /// </summary>
        /// <param name="sourceString">初始字符串</param>
        /// <param name="encoding">字符编码格式</param>
        /// <returns></returns>
        public static string ToBase64String(string sourceString, Encoding encoding)
        {
            if (sourceString == null)
            {
                throw new ArgumentNullException(nameof(sourceString), "要进行Base64转换的源字符串不能为空");
            }
            byte[] buffer = encoding.GetBytes(sourceString);
            int length = buffer.Length;
            int pageSize = (length + 2) / 3;
            //每三个字节作为一组转化为4个字节
            string[] destArray = new string[pageSize];//每个字符串的长度一定是4
            int count = 3;
            for (int i = 0; i < pageSize; i++)
            {
                if (i + 1 == pageSize)
                {
                    //如果是最后一次,把剩余个数取出来
                    count = length - 3 * i;
                }
                destArray[i] = ToFourCharArray(new ArraySegment<byte>(buffer, 3 * i, count));
            }
            return string.Join("", destArray);
        }

        /// <summary>
        /// 将Base64字符串还原为初始字符串
        /// </summary>
        /// <param name="base64String">Base64字符串,长度必须是4的倍数,尾巴有0~2个填充字符(等号=),必须由[A-Za-z0-9+/]组成</param>
        /// <param name="encoding">字符编码格式</param>
        /// <returns></returns>
        public static string FromBase64String(string base64String, Encoding encoding)
        {
            if (base64String == null)
            {
                throw new ArgumentNullException(nameof(base64String), "要还原的Base64字符串不能为空");
            }
            int length = base64String.Length;
            if (length % 4 != 0)
            {
                throw new Exception($"【格式非法】Base64字符串一定是4的倍数,当前长度【{length}】");
            }
            if (length == 0)
            {
                return string.Empty;
            }
            if (!Regex.IsMatch(base64String, "^[A-Za-z0-9\\+/]{2,}[=]{0,2}$"))
            {
                throw new Exception($"【格式非法】Base64尾巴有0~2个填充字符(等号=),必须由至少2个[A-Za-z0-9+/]组成");
            }
            int deltaCount = 0;
            if (base64String.EndsWith("=="))
            {
                //有两个填充等号,说明多余一个字节,总个数减去2个
                deltaCount = 2;
            }
            else if (base64String.EndsWith("="))
            {
                //有一个填充等号,说明多余两个字节,总个数减去1个
                deltaCount = 1;
            }
            //目标数组
            byte[] sourceArray = new byte[length / 4 * 3 - deltaCount];
            for (int i = 0; i < length / 4; i++)
            {
                byte[] segmentByteArray = ToThreeByteArray(base64String.Substring(4 * i, 4));
                Buffer.BlockCopy(segmentByteArray, 0, sourceArray, i * 3, segmentByteArray.Length);
            }
            return encoding.GetString(sourceArray);
        }
    }
}

默认的Program.cs源程序如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Base64ConsoleDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.SetWindowSize(170, 30);
            Encoding encoding = Encoding.GetEncoding("GBK");
            string sourceString = @"古剑奇谭三·梦付千秋星垂野 是由上海烛龙信息科技有限公司研发、北京网元圣唐娱乐科技有限公司出品、
运营的一款国产单机ARPG游戏,是《古剑奇谭》系列第三部单机作品。
该游戏于2014年12月底立项,2015年年初正式开发,采用全即时战斗模式,已于2018年11月23日正式上市.";
            string base64Custom = Base64Convert.ToBase64String(sourceString, encoding);
            Console.WriteLine(base64Custom);
            Console.WriteLine();
            string base64Auto= Convert.ToBase64String(encoding.GetBytes(sourceString));
            Console.WriteLine(base64Auto);

            Console.WriteLine($"比较 手动转化 与 调用系统函数转化 Base64结果:【{base64Custom == base64Auto}】");
            Console.WriteLine("------------下面测试Base64还原字符串------------");
            string srcCustom = Base64Convert.FromBase64String(base64Custom, encoding);
            Console.WriteLine(srcCustom);
            Console.WriteLine();
            string srcAuto = encoding.GetString(Convert.FromBase64String(base64Auto));
            Console.WriteLine(srcAuto);
            Console.WriteLine($"比较 手动还原 与 调用系统函数还原 Base64结果:【{srcCustom == srcAuto}】");
            Console.ReadLine();
        }
    }
}

程序运行如图:

C#根据Base64规则自己编写相应的Base64加密和解密函数

 

上一篇:C#Base64简单加密与解密


下一篇:手写数字识别