Base64属于简单加密算法的一种。类似于凯撒密码【它是一种替换加密的技术】
Base64字符串由65个字符组成,
大写字母A~Z,
小写字母a~z,
数字0~9,以及三个特殊字符+、/、=
【=“等号”用于补充字符,使Base64字符串长度变成4的倍数】
规则
考虑到初始源字符串可能是任何文本编码的【中文GBK,Unicode,ASCII等】,因此Base64字符串加密只处理字节数组【字节数组通过encoding.GetBytes(string src)获得】。
Base64编码字符串的长度一定是4的倍数。
Base64要求把每三个8Bit的字节转换为四个6Bit的字节(3*8 = 4*6 = 24),然后把6Bit再添两位高位0,组成四个8Bit的字节,因此每个Base64字节的十进制范围为0~63。也就是说,转换后的字符串理论上将要比原来的长1/3。
字节数组的长度应该是3的倍数,如果这个条件不能满足的话,具体的解决办法是这样的:原文剩余的字节根据编码规则继续单独转(1变2,2变3;不够的位数用0补全),再用=号补满4个字节。这就是为什么有些Base64编码会以一个或两个等号结束的原因,但等号最多只有两个。因为一个原字节至少会变成两个目标字节,所以余数任何情况下都只可能是0,1,2这三个数中的一个。如果余数是0的话,就表示原文字节数正好是3的倍数(最理想的情况)。如果是1的话,转成2个Base64编码字符,为了让Base64编码是4的倍数,就要补2个等号;同理,如果是2的话,就要补1个等号。
6Bit数字【0~63】映射Base64字符表如下
索引 |
对应字符 |
索引 |
对应字符 |
索引 |
对应字符 |
索引 |
对应字符 |
0 |
A |
17 |
R |
34 |
i |
51 |
z |
1 |
B |
18 |
S |
35 |
j |
52 |
0 |
2 |
C |
19 |
T |
36 |
k |
53 |
1 |
3 |
D |
20 |
U |
37 |
l |
54 |
2 |
4 |
E |
21 |
V |
38 |
m |
55 |
3 |
5 |
F |
22 |
W |
39 |
n |
56 |
4 |
6 |
G |
23 |
X |
40 |
o |
57 |
5 |
7 |
H |
24 |
Y |
41 |
p |
58 |
6 |
8 |
I |
25 |
Z |
42 |
q |
59 |
7 |
9 |
J |
26 |
a |
43 |
r |
60 |
8 |
10 |
K |
27 |
b |
44 |
s |
61 |
9 |
11 |
L |
28 |
c |
45 |
t |
62 |
+ |
12 |
M |
29 |
d |
46 |
u |
63 |
/ |
13 |
N |
30 |
e |
47 |
v |
||
14 |
O |
31 |
f |
48 |
w |
||
15 |
P |
32 |
g |
49 |
x |
||
16 |
Q |
33 |
h |
50 |
y |
测试Base64源程序
新建WinForm应用程序Base64EncoderDemo,重命名默认的 Form1为FormBase64Encoder,
窗体FormBase64Encoder设计如图:
FormBase64Encoder.cs主要代码如下
(忽略设计器自动生成的代码):
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Base64EncoderDemo
{
public partial class FormBase64Encoder : Form
{
public FormBase64Encoder()
{
InitializeComponent();
//参考Convert微软源程序
//https://referencesource.microsoft.com/#mscorlib/system/convert.cs,fc990bd1275d43d6
}
private void FormBase64Encoder_Load(object sender, EventArgs e)
{
rtxtMessage.ReadOnly = true;
//编码格式
cboEncoding.Items.AddRange(new string[] { "ASCII", "Unicode", "UTF-8", "GBK" });
cboEncoding.SelectedIndex = 0;
}
private void btnClear_Click(object sender, EventArgs e)
{
rtxtSourceString.Clear();
rtxtBase64String.Clear();
rtxtMessage.Clear();
}
/// <summary>
/// 显示提示消息
/// </summary>
/// <param name="content"></param>
private void DisplayMessage(string content)
{
if (rtxtMessage.TextLength >= 20480)
{
rtxtMessage.Clear();
}
rtxtMessage.AppendText($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} -> {content}\n");
rtxtMessage.ScrollToCaret();
}
private void btnConvertBase64_Click(object sender, EventArgs e)
{
rtxtBase64String.Clear();
if (rtxtSourceString.Text.Trim().Length == 0)
{
rtxtSourceString.Focus();
DisplayMessage("源字符串不能为空");
return;
}
try
{
Encoding encoding = Encoding.GetEncoding(cboEncoding.Text);
byte[] buffer = encoding.GetBytes(rtxtSourceString.Text.Trim());
rtxtBase64String.Text = Convert.ToBase64String(buffer, Base64FormattingOptions.None);
DisplayMessage($"转换成功,Base64字符串【{rtxtBase64String.Text}】");
}
catch (Exception ex)
{
DisplayMessage($"转换为Base64时出错:【{ex.Message}】");
}
}
private void btnRestore_Click(object sender, EventArgs e)
{
rtxtSourceString.Clear();
if (rtxtBase64String.Text.Trim().Length == 0)
{
rtxtBase64String.Focus();
DisplayMessage("Base64字符串不能为空");
return;
}
try
{
Encoding encoding = Encoding.GetEncoding(cboEncoding.Text);
byte[] buffer = Convert.FromBase64String(rtxtBase64String.Text);
rtxtSourceString.Text = encoding.GetString(buffer);
DisplayMessage($"还原成功,源字符串【{rtxtSourceString.Text}】");
}
catch (Exception ex)
{
DisplayMessage($"还原字符串时出错:【{ex.Message}】");
}
}
}
}
程序运行如图:
参考微软源代码:Reference Source
public static unsafe String ToBase64String(byte[] inArray, int offset, int length, Base64FormattingOptions options) {
//Do data verfication
if (inArray==null)
throw new ArgumentNullException("inArray");
if (length<0)
throw new ArgumentOutOfRangeException("length", Environment.GetResourceString("ArgumentOutOfRange_Index"));
if (offset<0)
throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_GenericPositive"));
if (options < Base64FormattingOptions.None || options > Base64FormattingOptions.InsertLineBreaks)
throw new ArgumentException(Environment.GetResourceString("Arg_EnumIllegalVal", (int)options));
Contract.Ensures(Contract.Result<string>() != null);
Contract.EndContractBlock();
int inArrayLength;
int stringLength;
inArrayLength = inArray.Length;
if (offset > (inArrayLength - length))
throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_OffsetLength"));
if (inArrayLength == 0)
return String.Empty;
bool insertLineBreaks = (options == Base64FormattingOptions.InsertLineBreaks);
//Create the new string. This is the maximally required length.
stringLength = ToBase64_CalculateAndValidateOutputLength(length, insertLineBreaks);
string returnString = string.FastAllocateString(stringLength);
fixed (char* outChars = returnString){
fixed (byte* inData = inArray) {
int j = ConvertToBase64Array(outChars,inData,offset,length, insertLineBreaks);
BCLDebug.Assert(returnString.Length == j, "returnString.Length == j");
return returnString;
}
}
}
Base64处理字节数组逻辑函数
internal static readonly char[] base64Table = {'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O',
'P','Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d',
'e','f','g','h','i','j','k','l','m','n','o','p','q','r','s',
't','u','v','w','x','y','z','0','1','2','3','4','5','6','7',
'8','9','+','/','=' };
private const Int32 base64LineBreakPosition = 76;
[System.Security.SecurityCritical] // auto-generated
private static unsafe int ConvertToBase64Array(char* outChars, byte* inData, int offset, int length, bool insertLineBreaks) {
int lengthmod3 = length%3;
int calcLength = offset + (length - lengthmod3);
int j=0;
int charcount = 0;
//Convert three bytes at a time to base64 notation. This will consume 4 chars.
int i;
// get a pointer to the base64Table to avoid unnecessary range checking
fixed(char* base64 = base64Table) {
for (i=offset; i<calcLength; i+=3)
{
if (insertLineBreaks) {
if (charcount == base64LineBreakPosition) {
outChars[j++] = '\r';
outChars[j++] = '\n';
charcount = 0;
}
charcount += 4;
}
outChars[j] = base64[(inData[i]&0xfc)>>2];
outChars[j+1] = base64[((inData[i]&0x03)<<4) | ((inData[i+1]&0xf0)>>4)];
outChars[j+2] = base64[((inData[i+1]&0x0f)<<2) | ((inData[i+2]&0xc0)>>6)];
outChars[j+3] = base64[(inData[i+2]&0x3f)];
j += 4;
}
//Where we left off before
i = calcLength;
if (insertLineBreaks && (lengthmod3 !=0) && (charcount == base64LineBreakPosition)) {
outChars[j++] = '\r';
outChars[j++] = '\n';
}
switch(lengthmod3)
{
case 2: //One character padding needed
outChars[j] = base64[(inData[i]&0xfc)>>2];
outChars[j+1] = base64[((inData[i]&0x03)<<4)|((inData[i+1]&0xf0)>>4)];
outChars[j+2] = base64[(inData[i+1]&0x0f)<<2];
outChars[j+3] = base64[64]; //Pad
j+=4;
break;
case 1: // Two character padding needed
outChars[j] = base64[(inData[i]&0xfc)>>2];
outChars[j+1] = base64[(inData[i]&0x03)<<4];
outChars[j+2] = base64[64]; //Pad
outChars[j+3] = base64[64]; //Pad
j+=4;
break;
}
}
return j;
}
private static int ToBase64_CalculateAndValidateOutputLength(int inputLength, bool insertLineBreaks) {
long outlen = ((long)inputLength) / 3 * 4; // the base length - we want integer division here.
outlen += ((inputLength % 3) != 0) ? 4 : 0; // at most 4 more chars for the remainder
if (outlen == 0)
return 0;
if (insertLineBreaks) {
long newLines = outlen / base64LineBreakPosition;
if ((outlen % base64LineBreakPosition) == 0) {
--newLines;
}
outlen += newLines * 2; // the number of line break chars we'll add, "\r\n"
}
// If we overflow an int then we cannot allocate enough
// memory to output the value so throw
if (outlen > int.MaxValue)
throw new OutOfMemoryException();
return (int)outlen;
}