Base64 编码是通过 64 基数将二进制数据转换为 ASCII 字符串的一种转换方案。由于通过改变这些基数可以产生不同的转换结果以更适用于某一领域,所以 Base64 实际上是此类转换方案的一个集合。
回顾历史
说到 Base64 编码,要先回顾一下 STMP( Simple Mail Transfer Protocol )协议的发布。
As this protocol started out purely ASCII text-based, it did not deal well with binary files, or characters in many non-English languages. Standards such as Multipurpose Internet Mail Extensions (MIME) were developed to encode binary files for transfer through SMTP.
STMP 是基于 ASCII 文本格式的传输协议,而 ASCII 是 7 位的。为什么用 7 位而不用 8 位呢?
翻开通信历史,莫尔斯码可以看做是 1 ~ 5 位的,后来有了固定 5 位的 Baudot 码,又有了 6 位的 TeleTypeSetter ( TTS ) 码,再后来则出现了我们更为熟知的 7 位 ASCII 码。
ASCII (/ˈæski/ ass-kee), abbreviated from American Standard Code for Information Interchange, is a character-encoding scheme (the IANA prefers the name US-ASCII). ASCII codes represent text in computers, communications equipment, and other devices that use text. Most modern character-encoding schemes are based on ASCII, though they support many additional characters. ASCII was the most common character encoding on the World Wide Web until December 2007, when it was surpassed by UTF-8, which includes ASCII as a subset.
ASCII developed from telegraphic codes. Its first commercial use was as a seven-bit teleprinter code promoted by Bell data services. Work on the ASCII standard began on October 6, 1960, with the first meeting of the American Standards Association’s (ASA) X3.2 subcommittee. The first edition of the standard was published during 1963, underwent a major revision during 1967, and experienced its most recent update during 1986. Compared to earlier telegraph codes, the proposed Bell code and ASCII were both ordered for more convenient sorting (i.e., alphabetization) of lists, and added features for devices other than teleprinters.
技术的发展一般有两点局限性,一是需要继承,二是视野有限。 7 位 ASCII 码通信继承了电报通信,并前进了一大步,然而还没有考虑到全球化的大环境下该怎么办。一个字节 8 位,低 7 位用于英文、符号、控制编码已足够,留下的最高位却也没闲着,可以做奇偶校验位或标志位等,似乎是非常圆满。 STMP 正是在这样的历史环境下发布的,也埋下了将来的难题。
难题来了,到了互联网时代,怎么发送非英文的内容?怎么让邮件携带多媒体信息?
MIME( Multipurpose Internet Mail Extensions ) 编码解决了这一难题,而它,正是一种典型的 Base64 编码。从其历史不难看出,Base64 编码的创造并不是用来加密的,加密的效果只不过是编码的一种基本属性罢了。
一个小实验
写下一封邮件,内容为:
Hello World!
世界你好:)
可以查看到邮件数据为:
Date: Sat, 10 Oct 2015 00:25:28 +0800
From: xxx
To: xxx
Subject: Hello
X-Mailer: NetEase Flash Mail 2.3.1.12
X-Priority: 3 (Normal)
MIME-Version: 1.0
Message-ID: <5617EA78.50506@163.com>
Content-Type: text/plain;
charset="UTF-8"
Content-Transfer-Encoding: base64
SGVsbG8gV29ybGQhDQrkuJbnlYzkvaDlpb06KQ==
这串 SGVsbG8gV29ybGQhDQrkuJbnlYzkvaDlpb06KQ==
就是邮件正文编码过后的内容。
如何编码
既然是将二进制数转换为 ASCII 码,而且是 64 基数的,那么他的原理就是将 1 字节转换成若干字节的 6 位编码( 64 基数),取整后就是:3 字节二进制数 => 4 字节的 6 位编码(高两位为 0 ),也就是说 24 个数据位的一组原始数据转换为一组 32 位的编码数据。如果原始数据位数不是 6 的倍数,则补 0 以成为 6 的最小倍数,再转换,若转换后字节数不是 4 的倍数则用等号 =
补足。例如原始数据剩余 1 字节,则应在其后补 4 位的 0 ,这时候就是 12 位的数据了。转换后得到 2 字节的编码,还应补上 ==
。
举个例子说明转换过程:有一个字符串 Hello World!
,先取其前 3 字节 Hel
。其二进制原始数据为:010010000110010101101100
分组:010010000110010101101100
补高二字节:00010010000001100001010100101100
即转换后的数据为:0x12 0x06 0x15 0x2C
这 4 字节的转换数据如何译成 ASCII 码呢?答案是通过一个映射表,这个表就是由我们之前提到的 64 个基数组成的。Base64 是 64 基数的编码集,基数码表不同编码后的内容自然不同,通常狭义上所说的 Base64 指的是 MIME's Base64 。他的码表可以表示为:
char encTab[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
编码的最后一步就是对照码表,将数据译为 ASCII 码:SGVs
static const char encTab[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/**
* @brief Base64编码
* @param data 待编码原始数据指针
* @param len 待编码原始数据长度
* @param enc 编码字符串存储指针
* @return 编码字符串存储指针
*/
char *base64Encode(const uint8_t *data, int len, char *enc)
{
int i = 0;
while(len)
{
enc[i++] = encTab[*data >> 2];
if(len > 1)
{
enc[i++] = encTab[(*data << 4 | *(data + 1) >> 4) & 0x3f];
if(len > 2)
{
enc[i++] = encTab[(*(data + 1) << 2 | *(data + 2) >> 6) & 0x3f];
enc[i++] = encTab[*(data + 2) & 0x3f];
len -= 3;
data += 3;
}
else
{
enc[i++] = encTab[(*(data + 1) << 2) & 0x3f];
enc[i++] = '=';
break;
}
}
else
{
enc[i++] = encTab[(*data << 4) & 0x3f];
enc[i++] = '=';
enc[i++] = '=';
break;
}
}
enc[i] = '\0';
return enc;
}
如何解码
解码是编码的逆过程,所以,当你知道编码的原理后,解码自然不在话下了。
/**
* @brief Base64解码
* @param enc 待解码原始字符串指针
* @param data 解码数据存储指针
* @return 解码数据的长度 -1表示解码失败
*/
int base64Decode(const char *enc, uint8_t *data)
{
int cnt = 0;
uint8_t buf[4], i = 0;
while(*enc)
{
if(*enc >= 'A' && *enc <= 'Z')
{
buf[i++] = *enc - 'A';
}
else if(*enc >= 'a' && *enc <= 'z')
{
buf[i++] = *enc - 'a' + 26;
}
else if(*enc >= '0' && *enc <= '9')
{
buf[i++] = *enc - '0' + 52;
}
else if(*enc == '+')
{
buf[i++] = 62;
}
else if(*enc == '/')
{
buf[i++] = 63;
}
else if(*enc != '=')
{
return -1;
}
enc++;
if(i == 4 || *enc == '\0')
{
if(i > 1)
{
data[cnt++] = buf[0] << 2 | buf[1] >> 4;
if(i > 2)
{
data[cnt++] = buf[1] << 4 | buf[2] >> 2;
if(i > 3)
{
data[cnt++] = buf[2] << 6 | buf[3];
i = 0;
}
}
}
else return -1;
}
}
return cnt;
}
参考资料
Telegraph code https://en.wikipedia.org/wiki/Telegraph_code
ASCII https://en.wikipedia.org/wiki/ASCII
Base64 https://en.wikipedia.org/wiki/Base64