上一篇我们已经使用C#函数Convert.ToBase64String()和Convert.FromBase64String()来加密和解密Base64,这里我们使用字典Dictionary<byte,char>来实现Base64加密和解密。
Base64介绍以及加密解密
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); } } }
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(); } } }