base64的编码原理网上讲解较多,但解码原理讲解较少,并且没有对其中的内部实现原理进行剖析。想要彻底了解base64的编码与解码原理,请耐心看完此文,你一定会有所收获。
在探究base64编码原理和解码原理的过程中,我们首先需要了解下面会用到的算法和逻辑运算的概念,这样才能真正的吃透base64的编码原理和解码原理,体会到其中算法的精妙,甚至是在思考的过程中得到意想不到的收获。不清楚base64编码表和ascII编码表的同学可直接前往文末查看。
短除法运算方法是先用一个除数除以能被它除尽的一个质数,以此类推,除到商是质数为止。 通过短除法,十进制数可以不断除以2得到多个余数。最后,将余数从下到上进行排列组合,得到二进制数,我们以字符n对应的ascII编码110为例。
110 / 2 = 55...0
55 / 2 = 27...1
27 / 2 = 13...1
13 / 2 = 6...1
6 / 2 = 3...0
3 / 2 = 1...1
1 / 2 = 0...1
将余数从下到上进行排列组合,得到字符n对应的ascII编码110转二进制为1101110,因为一字节对应8位(bit), 所以需要向前补0补足8位,得到01101110。其余字符同理可得。
按权展开求和, 8位二进制数从右到左,次数是0到7依次递增, 基数*底数次数,从左到右依次累加,相加结果为对应十进制数。我们以二进制数01101110转10进制为例:
(01101110)2 = 0 * 20 + 1 * 21 + 1 * 22 + 1 * 23 + 0 * 24 + 1 * 25 + 1 * 26 + 0 * 27
二进制数系统中,每个0或1就是一个位(bit,比特),也叫存储单元,位是数据存储的最小单位。其中8bit就称为一个字节(Byte)。
移位运算符在程序设计中,是位操作运算符的一种。移位运算符可以在二进制的基础上对数字进行平移。按照平移的方向和填充数字的规则分为三种:<<(左移)、>>(带符号右移)和>>>(无符号右移)。我们在base64的编码和解码过程中操作的又是正数,所以仅使用<<(左移)、>>(带符号右移)两种运算符。
- 左移运算:是将一个二进制位的操作数按指定移动的位数向左移动,移出位被丢弃,右边移出的空位一律补0。
- 右移运算:是将一个二进制位的操作数按指定移动的位数向右移动,移出位被丢弃,左边移出的空位一律补0,或者补符号位,这由不同的机器而定。在使用补码作为机器数的机器中,正数的符号位为0,负数的符号位为1。
我们用大白话来描述左移位,一共有8个座位,坐了8个人,在8个座位不动的情况下,现在我让这8个人往左挪2个座位,于是最左边的两个人站了起来,没有座位坐,而最右边空出来了两个座位。移位操作就相当于站起来的人出局,留出来的空位补0.
// 左移
01101000 << 2 -> 101000(左侧移出位被丢弃) -> 10100000(右侧空位一律补0)
// 右移
01101000 >> 2 -> 011010(右侧移出位被丢弃) -> 00011010(左侧空位一律补0)
与运算、或运算都是计算机中一种基本的逻辑运算方式。
- 与运算:符号表示为&。运算规则:两位同时为“1”,结果才为“1”,否则为0
- 或运算:符号表示为|。运算规则:两位只要有一位为“1”,结果就为“1”,否则为0
Base64编码是将字符串以每3个8比特(bit)的字节子序列拆分成4个6比特(bit)的字节(6比特有效字节,最左边两个永远为0,其实也是8比特的字节)子序列,再将得到的子序列查找Base64的编码索引表,得到对应的字符拼接成新的字符串的一种编码方式。
每3个8比特(bit)的字节子序列拆分成4个6比特(bit)的字节的拆分过程如下图所示:
base64
因为6和8的最大公倍数是24,所以3个8比特的字节刚好可以拆分成4个6比特的字节,38 = 64。计算机中,因为一个字节需要8个存储单元存储,所以我们要把6个比特往前面补两位0,补足8个比特。如下图所示: 很明显,补足后所需的存储单元为32个,是原来所需的24个的4/3倍。现在大家明白为什么base64编码后的大小是原来的4/3倍了吧。
因为6位(bit)的二进制数有2的6次方个,也就是二进制数(00000000-00111111)之间的代表0-63的64个二进制数。
因为我们得到的8位二进制数的前两位永远是0,真正的有效位只有6位,所以我们所能够得到的二进制数只有2的6次方个。
Base64的编码索引表,字符选用了"A-Z、a-z、0-9、+、/" 64个可打印字符来代表(00000000-00111111)这64个二进制数。即
let base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
我们不妨自己先思考一下,要把3个字节拆分成4个字节可以怎么做?你的实现思路和我的实现思路有哪个不同,我们之间又会碰出怎样的火花?
流程图
分析映射关系:abc -> xyzi。我们从高位到低位添加索引来分析这个过程
1 . 将字符对应的ascII编码转为8位二进制数
2 . 将每三个8位二进制数进行以下操作
3 . 将获得的6位有效位二进制数转十进制,查找对应base64字符 我们以hao字符串为例,观察base64编码的过程,我们将上面转换通过代码逻辑分析实现吧。
// 输入字符串
let str = 'hao'
// base64字符串
let base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
// 定义输入、输出字节的二进制数
let char1, char2, char3, out1, out2, out3, out4, out
// 将字符对应的ascII编码转为8位二进制数
char1 = str.charCodeAt(0) & 0xff // 104 01101000
char2 = str.charCodeAt(1) & 0xff // 97 01100001
char3 = str.charCodeAt(2) & 0xff // 111 01101111
// 输出6位有效字节二进制数
6out1 = char1 >> 2 // 26 011010
out2 = (char1 & 0x3) << 4 | (char2 & 0xf0) >> 4 // 6 000110
out3 = (char2 & 0xf) << 2 | (char3 & 0xc0) >> 6 // 5 000101
out4 = char3 & 0x3f // 47 101111
out = base64EncodeChars[out1] + base64EncodeChars[out2] + base64EncodeChars[out3] + base64EncodeChars[out4] // aGFv
算法剖析
01101000 -> 00011010
2 . out2 = (char1 & 0x3) << 4 | (char2 & 0xf0) >> 4
// 且运算
01101000 01100001
00000011 11110000
-------- --------
00000000 01100000
// 移位运算后得
00000000 00000110
// 或运算
00000000
00000110
--------
00000110
第三个字符第四个字符同理
整理上述代码,扩展至多字符字符串
// 输入字符串
let str = 'haohaohao'
// base64字符串
let base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
// 获取字符串长度
let len = str.length
// 当前字符索引
let index = 0
// 输出字符串
let out = ''
while(index < len) {
// 定义输入、输出字节的二进制数
let char1, char2, char3, out1, out2, out3, out4
// 将字符对应的ascII编码转为8位二进制数
char1 = str.charCodeAt(index++) & 0xff // 104 01101000
char2 = str.charCodeAt(index++) & 0xff // 97 01100001
char3 = str.charCodeAt(index++) & 0xff // 111 01101111
// 输出6位有效字节二进制数
out1 = char1 >> 2 // 26 011010
out2 = (char1 & 0x3) << 4 | (char2 & 0xf0) >> 4 // 6 000110
out3 = (char2 & 0xf) << 2 | (char3 & 0xc0) >> 6 // 5 000101
out4 = char3 & 0x3f // 47 101111
out = out + base64EncodeChars[out1] + base64EncodeChars[out2] + base64EncodeChars[out3] + base64EncodeChars[out4] // aGFv
}
原字符串长度不是3的整倍数的情况,需要特殊处理
...
char1 = str.charCodeAt(index++) & 0xff // 104 01101000
if (index == len) {
out2 = (char1 & 0x3) << 4
out = out + base64EncodeChars[out1] + base64EncodeChars[out2] + '=='
return out
}
char2 = str.charCodeAt(index++) & 0xff // 97 01100001
if (index == len) {
out1 = char1 >> 2 // 26 011010
out2 = (char1 & 0x3) << 4 | (char2 & 0xf0) >> 4 // 6 000110
out3 = (char2 & 0xf) << 2
out = out + base64EncodeChars[out1] + base64EncodeChars[out2] + base64EncodeChars[out3] + '='
return out
}
...
全部代码
function base64Encode(str) {
// base64字符串
let base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
// 获取字符串长度
let len = str.length
// 当前字符索引
let index = 0
// 输出字符串
let out = ''
while(index < len) {
// 定义输入、输出字节的二进制数
let char1, char2, char3, out1, out2, out3, out4
// 将字符对应的ascII编码转为8位二进制数
char1 = str.charCodeAt(index++) & 0xff
out1 = char1 >> 2
if (index == len) {
out2 = (char1 & 0x3) << 4
out = out + base64EncodeChars[out1] + base64EncodeChars[out2] + '=='
return out
}
char2 = str.charCodeAt(index++) & 0xff
out2 = (char1 & 0x3) << 4 | (char2 & 0xf0) >> 4
if (index == len) {
out3 = (char2 & 0xf) << 2
out = out + base64EncodeChars[out1] + base64EncodeChars[out2] + base64EncodeChars[out3] + '='
return out
}
char3 = str.charCodeAt(index++) & 0xff
// 输出6位有效字节二进制数
out3 = (char2 & 0xf) << 2 | (char3 & 0xc0) >> 6
out4 = char3 & 0x3f
out = out + base64EncodeChars[out1] + base64EncodeChars[out2] + base64EncodeChars[out3] + base64EncodeChars[out4]
}
return out
}
base64Encode('haohao') // aGFvaGFv
base64Encode('haoha') // aGFvaGE=
base64Encode('haoh') // aGFvaA==
逆向推导,由每4个6位有效位的二进制数合并成3个8位二进制数,根据ascII编码映射到对应字符后拼接字符串
分析映射关系 xyzi -> abc
1 . 将字符对应的base64字符集的索引转为6位有效位二进制数
2 . 将每四个6位有效位二进制数进行以下操作
a. 第一个二进制数左移位2位,得到新二进制数的前6位,第二个二进制数 & 0x30之后右移位4位,或运算后得到第一个新二进制数
b. 第二个二进制数 & 0xf之后左移位4位,第三个二进制数 & 0x3c之后右移位2位,或运算后得到第二个新二进制数
c. 第二个二进制数 & 0x3之后左移位6位,与第四个二进制数或运算后得到第二个新二进制数
3 . 根据ascII编码映射到对应字符后拼接字符串
// base64字符串
let str = 'aGFv'
// base64字符集
let base64CharsArr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('')
// 获取索引值
let char1 = base64CharsArr.findIndex(char => char==str[0]) & 0xff // 26 011010
let char2 = base64CharsArr.findIndex(char => char==str[1]) & 0xff // 6 000110
let char3 = base64CharsArr.findIndex(char => char==str[2]) & 0xff // 5 000101
let char4 = base64CharsArr.findIndex(char => char==str[3]) & 0xff // 47 101111
let out1, out2, out3, out
// 位运算
out1 = char1 << 2 | (char2 & 0x30) >> 4
out2 = (char2 & 0xf) << 4 | (char3 & 0x3c) >> 2
out3 = (char3 & 0x3) << 6 | char4
console.log(out1, out2, out3)
out = String.fromCharCode(out1) + String.fromCharCode(out2) + String.fromCharCode(out3)
遇到有用'='补过位的情况时
function base64decode(str) {
// base64字符集
let base64CharsArr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('')
let char1 = base64CharsArr.findIndex(char => char==str[0])
let char2 = base64CharsArr.findIndex(char => char==str[1])
let out1, out2, out3, out
if (char1 == -1 || char2 == -1) return out
char1 = char1 & 0xff
char2 = char2 & 0xff
let char3 = base64CharsArr.findIndex(char => char==str[2])
// 第三位不在base64对照表中时,只拼接第一个字符串
if (char3 == -1) {
out1 = char1 << 2 | (char2 & 0x30) >> 4
out = String.fromCharCode(out1)
return out
}
let char4 = base64CharsArr.findIndex(char => char==str[3])
// 第三位不在base64对照表中时,只拼接第一个和第二个字符串
if (char4 == -1) {
out1 = char1 << 2 | (char2 & 0x30) >> 4
out2 = (char2 & 0xf) << 4 | (char3 & 0x3c) >> 2
out = String.fromCharCode(out1) + String.fromCharCode(out2)
return out
}
// 位运算
out1 = char1 << 2 | (char2 & 0x30) >> 4
out2 = (char2 & 0xf) << 4 | (char3 & 0x3c) >> 2
out3 = (char3 & 0x3) << 6 | char4
console.log(out1, out2, out3)
out = String.fromCharCode(out1) + String.fromCharCode(out2) + String.fromCharCode(out3)
return out
}
解码整个字符串,整理代码后
function base64decode(str) {
// base64字符集
let base64CharsArr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('')
let i = 0
let len = str.length
let out = ''
while(i < len) {
let char1 = base64CharsArr.findIndex(char => char==str[i])
i++
let char2 = base64CharsArr.findIndex(char => char==str[i])
i++
let out1, out2, out3
if (char1 == -1 || char2 == -1) return out
char1 = char1 & 0xff
char2 = char2 & 0xff
let char3 = base64CharsArr.findIndex(char => char==str[i])
i++
// 第三位不在base64对照表中时,只拼接第一个字符串
out1 = char1 << 2 | (char2 & 0x30) >> 4
if (char3 == -1) {
out = out + String.fromCharCode(out1)
return out
}
let char4 = base64CharsArr.findIndex(char => char==str[i])
i++
// 第三位不在base64对照表中时,只拼接第一个和第二个字符串
out2 = (char2 & 0xf) << 4 | (char3 & 0x3c) >> 2
if (char4 == -1) {
out = out + String.fromCharCode(out1) + String.fromCharCode(out2)
return out
}
// 位运算
out3 = (char3 & 0x3) << 6 | char4
console.log(out1, out2, out3)
out = out + String.fromCharCode(out1) + String.fromCharCode(out2) + String.fromCharCode(out3)
}
return out
}
base64decode('aGFvaGFv') // haohao
base64decode('aGFvaGE=') // haoha
base64decode('aGFvaA==') // haoh
上述解码核心是字符与base64字符集索引的映射,网上看到过使用AccII编码索引映射base64字符索引的方法
let base64DecodeChars = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1]
//
let char1 = 'hao'.charCodeAt(0) // h -> 104
base64DecodeChars[char1] // 33 -> base64编码表中的h
由此可见,base64DecodeChars对照accII编码表的索引存放的是base64编码表的对应字符的索引。
说起Base64编码可能有些奇怪,因为大多数的编码都是由字符转化成二进制的过程,而从二进制转成字符的过程称为解码。而Base64的概念就恰好反了,由二进制转到字符称为编码,由字符到二进制称为解码。Base64 是一种数据编码方式,可做简单加密使用,我们可以改变base64编码映射顺序来形成自己独特的加密算法进行加密解密。
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/6gwAkAoIRwii2wDm5Zc8iQ
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。