一份简明的 Base64 原理解析
书接上回,在 记一个 Base64 有关的 Bug 一文里,我们说到了 Base64 的编解码器有不同实现,交叉使用它们可能引发的问题等等。
这一回,我们来对 Base64 这一常用编解码技术的原理一探究竟。
1. Base64 是什么
Base64 是一种基于 64 个可打印字符来表示二进制数据的表示方法。由于 2^6=64,所以每 6 个比特为一个单元,对应某个可打印字符。3 个字节有 24 个比特,对应于 4 个 Base64 单元,即 3 个字节可由 4 个可打印字符来表示。
——维基百科
它不是一种加解密技术,是一种简单的编解码技术。
Base64 常用于表示、传输、存储二进制数据,也可以用于将一些含有特殊字符的文本内容编码,以便传输。
比如:
在电子邮件的传输中,Base64 可以用来将 binary 的字节序列,比如附件,编码成 ASCII 字节序列;
将一些体积不大的图片 Base64 编码后,直接内嵌到网页源码里;
将要传递给 HTTP 请求的参数做简单的转换,降低肉眼可读性;
注:用于 URL 的 Base64 非标准 Base64,是一种变种。
网友们在论坛等公开场合习惯将邮箱地址 Base64 后再发出来,防止被爬虫抓取后发送垃圾邮件。
2. Base64 编码原理
标准 Base64 里的 64 个可打印字符是 A-Za-z0-9+/
,分别依次对应索引值 0-63。索引表如下:
编码时,每 3 个字节一组,共 8bit*3=24bit,划分成 4 组,即每 6bit 代表一个编码后的索引值,划分如下图所示:
这样可能不太直观,举个例子就容易理解了。比如我们对 cat
进行编码:
可以看到 cat
编码后变成了 Y2F0
。
如果待编码内容的字节数不是 3 的整数倍,那需要进行一些额外的处理。
如果最后剩下 1 个字节,那么将补 4 个 0 位,编码成 2 个 Base64 字符,然后补两个 =
:
如果最后剩下 2 个字节,那么将补 2 个 0 位,编码成 3 个 Base64 字符,然后补一个 =
:
3. 实现一个简易的 Base64 编码器
讲完原理,我们就可以动手实现一个简易的标准 Base64 编码器了,以下是我参考 Java 8 的 java.util.Base64 乱写的一个 Java 版本,仅供参考,主要功能代码如下:
public class CustomBase64Encoder {
/**
* 索引表
*/
private static final char[] sBase64 = {
'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', '+', '/'
};
/**
* 将 byte[] 进行 Base64 编码并返回字符串
* @param src 原文
* @return 编码后的字符串
*/
public static String encode(byte[] src) {
if (src == null) {
return null;
}
byte[] dst = new byte[(src.length + 2) / 3 * 4];
int index = 0;
// 每次将 3 个字节编码为 4 个字节
for (int i = 0; i < (src.length / 3 * 3); i += 3) {
int bits = (src[i] & 0xff) << 16 | (src[i + 1] & 0xff) << 8 | (src[i + 2] & 0xff);
dst[index++] = (byte) sBase64[(bits >>> 18) & 0x3f];
dst[index++] = (byte) sBase64[(bits >>> 12) & 0x3f];
dst[index++] = (byte) sBase64[(bits >>> 6) & 0x3f];
dst[index++] = (byte) sBase64[bits & 0x3f];
}
// 处理剩下的 1 个或 2 个字节
if (src.length % 3 == 1) {
int bits = (src[src.length - 1] & 0xff) << 4;
dst[index++] = (byte) sBase64[(bits >>> 6) & 0x3f];
dst[index++] = (byte) sBase64[bits & 0x3f];
dst[index++] = '=';
dst[index] = '=';
} else if (src.length % 3 == 2) {
int bits = (src[src.length - 2] & 0xff) << 10 | (src[src.length - 1] & 0xff) << 2;
dst[index++] = (byte) sBase64[(bits >>> 12) & 0x3f];
dst[index++] = (byte) sBase64[(bits >>> 6) & 0x3f];
dst[index++] = (byte) sBase64[bits & 0x3f];
dst[index] = '=';
}
return new String(dst);
}
}
这部分源码我也上传到 GitHub 仓库 https://github.com/mzlogin/spring-practices 的 base64test 工程里了。
4. 其它知识点
4.1 为什么有的编码结果带回车
在电子邮件中,根据 RFC 822 规定,每 76 个字符需要加上一个回车换行,所以有些编码器实现,比如 sun.misc.BASE64Encoder.encode,是带回车的,还有 java.util.Base64.Encoder.RFC2045,是带回车换行的,每行 76 个字符。
4.2 Base64 的变种
除了标准 Base64 之外,还有一些其它的 Base64 变种。
比如在 URL 的应用场景中,因为标准 Base64 索引表中的 /
和 +
会被 URLEncoder 转义成 %XX
形式,但 %
是 SQL 中的通配符,直接用于数据库操作会有问题。此时可以采用 URL Safe 的编码器,索引表中的 /+
被换成 -_
,比如 java.util.Base64.Encoder.RFC4648_URLSAFE 就是这样的实现。
5. 参考链接
- https://zh.wikipedia.org/zh-hans/Base64
- https://www.liaoxuefeng.com/wiki/897692888725344/949441536192576
假如你对我的文章感兴趣,可以关注我的微信公众号『闷骚的程序员』随时阅读更多内容。
一份简明的 Base64 原理解析的更多相关文章
- Base64原理解析
一. Base64编码由来 为什么会有Base64编码呢?因为有些网络传送渠道并不支持所有的字节,例如传统的邮件只支持可见字符的传送,像ASCII码的控制字符就 不能通过邮件传送.这样用途就受到了很大 ...
- Base64原理解析与使用
一.Base64编码由来 为什么会有Base64编码呢?因为有些网络传送渠道并不支持所有的字节,例如传统的邮件只支持可见字符的传送,像ASCII码的控制字符就 不能通过邮件传送.这样用途就受到了很大的 ...
- Base64原理
一.Base64编码由来 为什么会有Base64编码呢?因为有些网络传送渠道并不支持所有的字节,例如传统的邮件只支持可见字符的传送,像ASCII码的控制字符就 不能通过邮件传送.这样用途就受到了很大的 ...
- 第16 章 : 深入理解 etcd:基于原理解析
深入理解 etcd:基于原理解析 本文将主要分享以下三方面的内容: 第一部分,会为大家介绍 etcd 项目发展的整个历程,从诞生至今 etcd 经历的那些重要的时刻: 第二部分,会为大家介绍 etcd ...
- 一篇不一样的docker原理解析
转自:https://zhuanlan.zhihu.com/p/22382728 https://zhuanlan.zhihu.com/p/22403015 在学习docker的过程中,我发现目前do ...
- ThreadLocal系列(一)-ThreadLocal的使用及原理解析
ThreadLocal系列之ThreadLocal(源码基于java8) 项目中我们如果想要某个对象在程序运行中的任意位置获取到,就需要借助ThreadLocal来实现,这个对象称作线程的本地变量,下 ...
- Flask 上下文(Context)原理解析
:first-child { margin-top: 0; } blockquote > :last-child { margin-bottom: 0; } img { border: 0; m ...
- 【转载】Java类加载原理解析
Java类加载原理解析 原文出处:http://www.blogjava.net/zhuxing/archive/2008/08/08/220841.html 1 基本信息 摘要: 每个j ...
- android黑科技系列——微信抢红包插件原理解析和开发实现
一.前言 自从几年前微信添加抢红包的功能,微信的电商之旅算是正式开始正式火爆起来.但是作为Android开发者来说,我们在抢红包的同时意识到了很多问题,就是手动去抢红包的速度慢了,当然这些有很多原因导 ...
随机推荐
- 算法笔记4.3递归 问题 B: 数列
题目描述 编写一个求斐波那契数列的递归函数,输入n 值,使用该递归函数,输出如下图形(参见样例). 输入 输入第一行为样例数m,接下来有m行每行一个整数n,n不超过10. 输出 对应每个样例输出要求的 ...
- html lang="en"
<html lang="en">向搜索引擎表示该页面是html语言,并且语言为英文网站,其"lang"的意思就是“language”,语言的意思,而 ...
- android采用MVP漫画APP、适配刘海屏、小黄车主界面、录音波浪动画、综合APP等源码
Android精选源码 一款采用MVP架构的仿完整漫画APP源码 Android适配刘海屏幕 基于Xmpp协议的即时通讯社交软件(客户端+服务端) Android小黄车(ofo)app主页菜单效果 一 ...
- 使用这些高效Java工具类享受开发乐趣
使用这些高效Java工具类享受开发乐趣导语在Java中,工具类定义了一组公共方法,这篇文章将介绍Java中使用最频繁及最通用的Java工具类.在开发中,使用这些工具类,不仅可以提高编码效率,还可以提高 ...
- Java IO: 其他字节流(上)
作者: Jakob Jenkov 译者: 李璟(jlee381344197@gmail.com) 本小节会简要概括Java IO中的PushbackInputStream,SequenceInputS ...
- PP图|QQ图|正态性检验|K-S检验|S-W检验|
应用统计学: 物理条件一致时,有理由认为方差是一致的.配对检验可排除物理影响,使方差变小,但是自由度降低了,即样本数变小.二项分布均值假设检验的模型要依据前面的假设条件: PP图统计图要看中间的贴近情 ...
- reduced penetrance|COPE-PCG
生物医学大数据 Case study 由Human genome project提出之后,提出的精准医学.它的初衷是将数据standard后easy应用,我国重要重在疾病预警和疗效评价. 在疾病预警上 ...
- win10安装3DMAX失败,怎么强力卸载删除注册表并重新安装
一些搞设计的朋友在win10系统下安装3DMAX失败或提示已安装,也有时候想重新安装3DMAX的时候会出现本电脑windows系统已安装3DMAX,你要是不留意直接安装3DMAX,只会安装3DMAX的 ...
- REVIT 卸载工具,完美彻底卸载清除干净revit各种残留注册表和文件
一些同学安装revit出错了,也有时候想重新安装revit的时候会出现这种本电脑windows系统已安装revit,你要是不留意直接安装,只会安装revit的附件,revit是不会安装上的.这种原因呢 ...
- 携程酒店DevOps测试实践
作者简介 王幸福,携程酒店研发部高级测试经理,负责无线自动化测试相关工作.在测试框架和平台研发.移动测试.DevOps等领域有着丰富的经验. 如今很多大型互联网公司.创新型企业都在积极地进行DevOp ...