背景介绍
抛个问题,什么是base64?何时会用到他?与我们熟知的charset又是怎样的关系呢?跟国际化又有何恩怨呢?先看看wikipedia对其定义吧。
“ Base64是一种基于64个可打印字符来表示二进制数据的表示方法。由于 26=64,所以每6个比特为一个单元,对应某个可打印字符。3个字节有24个比特,对应于4个Base64单元,即3个字节可由4个可打印字符来表示。它可用来作为电子邮件的传输编码。在Base64中的可打印字符包括字母A-Z、a-z、数字0-9,这样共有62个字符,此外两个可打印符号在不同的系统中而不同。
Base64常用于在通常处理文本数据的场合,表示、传输、存储一些二进制数据,包括MIME的电子邮件及XML的一些复杂数据。”
编码原理
之所以称为Base64,是因为其使用64个字符来对任意数据进行编码,同理有Base32、Base16编码。标准Base64编码使用的64个字符为:
这64个字符是各种字符编码(比如ASCII编码)所使用字符的子集,基本,并且可打印。唯一有点特殊的是最后两个字符,因对最后两个字符的选择不同,Base64编码又有很多变种,比如Base64 URL编码。
具体算法可以细分为如下步骤。
- 获取字符的UTF8对应编码,转化为对应的24位二进串
- 将这24个的二进制串分成4组,每组6个
- 在每组前添加两个0,扩展成32位二进制串
- 分组获取二进制对应的数字
- 查表获得每个值对应Base64编码
用中文举例,“中”字对应的UTF-8编码为E4 B8 AD,转化为二进制是三字节的11100100 10111000 10101101。将这个24位的二进制串,转换成四组32位的二进制值00111001 00001011 00100010 00101101,他们对应的十进制数字为57 11 34 45,通过查表我们就可以知道“中”字对应base64就是5Lit。
若原数据长度不是3的倍数时且剩下1个输入数据,则在编码结果后加2个=;若剩下2个输入数据,则在编码结果后加1个=。标准Base64编码通常用=来替换最后的A,即编码结果为 SGVsbG8hIQ==。因为= 并不在Base64编码索引表中,其意义在于结束符号,在Base64解码时遇到=时即可知道一个Base64编码字符串结束。
场景应用
- 对图片进行编码
在前端的世界里,对于一些简单图片,通常会选择将内容直接内嵌在页面中,避免不必要的资源加载。而一旦图片是二进制数据,就需要用到一个叫做Data URLs 的属性,使用Base64对图片或其他文件的二进制数据进行编码,将编码后的为文本字符串嵌入到网页中,如下所示。
<img alt="Embedded Image" src="https://img-blog.csdnimg.cn/2022010709205219735.png" />
- MIME
发mail使用的SMTP协议最初是基于ASCII文本的,对于二进制文件(包括邮件附件中的图像、声音等)的处理并不好,后来新增MIME标准来编码二进制文件,使其能够通过SMTP协议传输。
看来一封mail的源码。
Content-Type: text/plain; name="hello.txt"
表示附件文件名为 hello.txt ,格式为纯文本。
Content-Transfer-Encoding: base64
表示附件文件内容使用base64编码后传输。
5oKo5aW977yM5LiW55WM77yB
这一串无意义的数字则是文件内容经过Base64编码后的显示结果。
关于完整Base64定义可见RFC 1421和RFC 2045。需要注意编码后的数据比原始数据略长,大概为原来的 1.35倍。
不过这些似乎跟国际化都没明显啥关系哦?稍安勿躁,真实案例这就登场。
案例分析
首先在AD中创建一个叫做 卡尔文@xx.com的用户并赋予其管理员权限。接近着用该用户登陆,意外就这样的不请自来了。
该用户未被授权?反复验证后,错误依旧。
从log中我们发现,该用户正在尝试用这样的HTTP header进行authentication。
X-Username: 卡尔文
X-Password: 123456
而错误信息为Unable set request header: X-Username: 卡尔文
通过查阅RFC,我们看到了这样的信息。RFC 7230 3.2.4(https://tools.ietf.org/html/rfc7230#section-3.2.4)写到:
“ Historically, HTTP has allowed field content with text in the ISO-8859-1 charset [ISO-8859-1], supporting other charsets only through use of [RFC2047] encoding. In practice, most HTTP header field values use only a subset of the US-ASCII charset [USASCII]. Newly defined header fields SHOULD limit their field values to US-ASCII octets. A recipient SHOULD treat other octets in field content (obs-text) as opaque data. ”
哦~~~原来HTTP header中是无法包含中文信息的,他只能接受ASCII,而一旦用户信息中包含中文、日文这样的foreign character,就需要考虑召唤base64这样的编码规范来单骑救主了。Fix示意代码如下。
public class Base64Test {
public static String encodeBase64(String value) {
if (value == null) {
return null;
}
try {
return new String(java.util.Base64.getEncoder().encode(value.getBytes("UTF-8")), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
}
public static String decodeBase64(String value) {
if (value == null) {
return null;
}
try {
return new String(Base64.getDecoder().decode(value.getBytes("UTF-8")), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
}
public static void replaceWithBase64DecodedHeaders(String value) {
System.out.println("Base64DecodedHeader is X-Username: " + encodeBase64(value));
}
}
打印结果为Base64DecodedHeader is X-Username: 5Y2h5bCU5paH,再次尝试用该中文用户登陆,之前弹出的问题随之烟消云散。
个人总结
不少业务场景下的数据传输要求只能由ASCII字符构成,例如HTTP协议要求请求的首行和请求头都必须是ASCII编码,这种情形下请务必使用base64或类似编码方式进行处理,以绝后患。
注意事项
Base64作为一种数据编码方式,其目的是让数据符合传输协议的要求,万不可用于数据加密或数据校验。因为标准Base64编码解码无需额外信息即完全可逆,即使你自己自定义字符集设计一种类Base64的编码方式用于数据加密,在多数场景下也非常容易破解。
Base64兼顾字符集大小和编码后数据长度,并且可以灵活替换字符集的最后两个字符,以应对多样的需求,使其适用场景非常广泛。当然根据场景不同,我们有多种编码方式可供选择,Base64编码并非唯一。就笔者非常有限的了解来看,Quoted-printable就可作为plan B。