上一篇我们已经使用C#函数Convert.ToBase64String()和Convert.FromBase64String()来加密和解密Base64,这里我们使用字典Dictionary<byte,char>来实现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();
}
}
}
程序运行如图: