百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

你知道Base64编码吗?跟我一起用Go语言实现它吧

liebian365 2024-10-24 14:32 25 浏览 0 评论

为什么会有Base64编码呢?

早期的邮件只能处理文本格式的内容,而且是英文格式的内容,为了能在邮件中使用其他语言,同时可以支持传输图片、音频、视频等多媒体内容,人们设计扩展了邮件传输协议,使得它能支持一种叫做MIME(Multipurpose Internet Mail Extentions,多媒体邮件扩展标准)标准。

在MIME中,为了能够使用文本协议传输这部分内容,就需要对传输的对象进行转码。如果不进行转码,这部分数据是无法直接在邮件传输的。一个很重要的原因是邮件传输协议使用.符号作为正文的结束符,直接传输二进制的数据会导致可能出现误判,就无法获取到完整的邮件内容。

MIME中最常用的转码方式有两种,对于含有大量7BIT字符的数据多采用Quoted-Printable编码,对于二进制的数据多使用Base64编码。


Base64编码的原理是怎样的?

Base64编码就是把二进制的数据通过一个映射关系,全部转换为64个预定义的字符,这64个字符中不包含控制符,所以能够被邮件等正确的传输识别。

这64个字符的码表索引定义如下图所示:

那二进制的数据是怎样转变成上述的码表中的字符呢?

通常,计算机中传输数据都是以字节(一般为8bit)为单位,我们需要对要进行转码的数据以每3个字节分组,也就是24bit一组,将这24bit拆分成4份,每份6bit,每6bit组成一个新的字节(前两位不足的补足为0),这样就能得到n组4字节的数据。

由于拆分后的每个字节实际只有6位有实意,也就是最大只能表示64,于是就可以通过以上的码表转换成对应的字符,然后们就可以通过转换后的字符进行传输了。

到这里可能有的同学会问,上面直接拆分成4字节后直接传输不可以吗?为什么还要再进行码表转换呢?这是由于虽然拆分成了4字节,但是其数据还是包含控制字符,例如39也就是ascii字符中的.号,这个是邮件传输协议中的正文结束符,所以还是不满足要求,只要转换成上面码表中的字符后,才能被正确的识别。

同时,还有同学会问,我们要传输的数据可能并不是3的整数倍,这时候要怎么办呢?对于不是3的整数倍的数据,我们先填充空字符,也就是二进制为 00000000,记录下差几个字符,按上述方法转换完成后,将最后差的字符替换为=号,比如差1个字符,就将最后一位替换为=,差两位就将最后两位替换为==。当然,最大也就只会差两位,至于为什么,可以自己思考下~

这里我们以编码hello这个字符串为例:

根据编码的原理,我们也可以很容易的知道解码的原理。


用Go语言怎么去实现?

对于编码而言,首先我们需要定义一个码表,这里码表中的数字是对应的ascii码。

var table = []byte{
   65, 66, 67, 68, 69, 70, 71, 72,
   73, 74, 75, 76, 77, 78, 79, 80,
   81, 82, 83, 84, 85, 86, 87, 88,
   89, 90, 97, 98, 99, 100, 101, 102,
   103, 104, 105, 106, 107, 108, 109, 110,
   111, 112, 113, 114, 115, 116, 117, 118,
   119, 120, 121, 122, 48, 49, 50, 51,
   52, 53, 54, 55, 56, 57, 43, 47,
}

其实,就是类似如下:

var table = []string{
   "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", "+", "/",
}

我们还定义了一个变量,用于保存缺失字符的替换字符=符号,61是=符号的ascii码:

var byteEq byte = 61

编码的方法:

func Encode(bytes []byte) string {
   bytesLen := len(bytes)
   // 计算缺失的位,即不满足以3字节分组的位数
   bytesMiss := 0
   if bytesLen%3 != 0 {
      bytesMiss = 3 - bytesLen%3
   }
   // 以空字符填充不足位
   if bytesMiss != 0 {
      for i := 0; i < bytesMiss; i++ {
         bytes = append(bytes, 0b00000000)
      }
      bytesLen += bytesMiss
   }
   // 分组,编码
   groupCount := bytesLen / 3
   // 申明一个原始的分组和一个对应新编码索引的分组
   oriGroup := make([]byte, 3)
   newGroupIndex := make([]byte, 4)
   // 申明转换后的变量
   newBytes := make([]byte, bytesLen/3*4)
   // 分组执行
   for i := 0; i < groupCount; i++ {
      // 获得当前这个组的字节码
      oriGroup = bytes[i*3 : i*3+3]
      // 获取对应的索引位置
      newGroupIndex[0] = oriGroup[0] >> 2
      newGroupIndex[1] = (oriGroup[0] & 0b00000011 << 4) | (oriGroup[1] & 0b11110000 >> 4)
      newGroupIndex[2] = (oriGroup[1] & 0b00001111 << 2) | (oriGroup[2] & 0b11000000 >> 6)
      newGroupIndex[3] = oriGroup[2] & 0b00111111
      // 转换,其实上面的oriGroup和newGroup两个变量都没必要,这里只是为了方便理解专门加上的,可以直接用索引定位
      newBytes[i*4] = table[newGroupIndex[0]]
      newBytes[i*4+1] = table[newGroupIndex[1]]
      newBytes[i*4+2] = table[newGroupIndex[2]]
      newBytes[i*4+3] = table[newGroupIndex[3]]
   }
   // 如有缺失位,进行替换,替换为=符号
   if bytesMiss != 0 {
      for i := 1; i <= bytesMiss; i++ {
         newBytes[len(newBytes)-i] = byteEq
      }
   }
   return string(newBytes)
}


顺便,我们也实现了解码的方法。

首先,我们需要定义一个码表对应的索引,key是字符的ascii码:

var tableMap = map[byte]byte{
   65: 0, 66: 1, 67: 2, 68: 3, 69: 4, 70: 5, 71: 6, 72: 7,
   73: 8, 74: 9, 75: 10, 76: 11, 77: 12, 78: 13, 79: 14, 80: 15,
   81: 16, 82: 17, 83: 18, 84: 19, 85: 20, 86: 21, 87: 22, 88: 23,
   89: 24, 90: 25, 97: 26, 98: 27, 99: 28, 100: 29, 101: 30, 102: 31,
   103: 32, 104: 33, 105: 34, 106: 35, 107: 36, 108: 37, 109: 38, 110: 39,
   111: 40, 112: 41, 113: 42, 114: 43, 115: 44, 116: 45, 117: 46, 118: 47,
   119: 48, 120: 49, 121: 50, 122: 51, 48: 52, 49: 53, 50: 54, 51: 55,
   52: 56, 53: 57, 54: 58, 55: 59, 56: 60, 57: 61, 43: 62, 47: 63,
}

其实就是类似如下:

var tableMap = map[string]byte {
   "A": 0, "B": 1, "C": 2, "D": 3, "E": 4, "F": 5, "G": 6, "H": 7,
   "I": 8, "J":9 , "K":10, "L":11, "M":12, "N":13, "O":14, "P":15,
   "Q":16, "R":17, "S":18, "T":19, "U":20, "V":21, "W":22, "X":23,
   "Y":24, "Z":25, "a":26, "b":27, "c":28, "d":29, "e":30, "f":31,
   "g":32, "h":33, "i":34, "j":35, "k":36, "l":37, "m":38, "n":39,
   "o":40, "p":41, "q":42, "r":43, "s":44, "t":45, "u":46, "v":47,
   "w":48, "x":49, "y":50, "z":51, "0":52, "1":53, "2":54, "3":55,
   "4":56, "5":57, "6":58, "7":59, "8":60, "9":61, "+":62, "/":63,
}

解码的代码如下:

func Decode(bytes []byte) (string, error) {
   // 如果不能被4整除,说明不是正常的base64编码后的数据
   if len(bytes)%4 > 0 {
      return "", errors.New("not valid base64 code")
   }
   // 统计缺失的位
   byteMiss := 0
   var ok bool
   // 只有最后才有缺失字符转换码=,这里定义一个标志来进行判断
   var missLock bool
   for i := len(bytes) - 1; i >= 0; i-- {
      if bytes[i] == byteEq {
         if missLock {
            return "", errors.New("only the end could have =")
         }
         byteMiss += 1
         bytes[i] = 0
      } else {
         missLock = true
         // 判断是否是在码表中的字符,并转换成对应的索引
         bytes[i], ok = tableMap[bytes[i]]
         if !ok {
            return "", errors.New("not a valid base64 byte")
         }
      }
   }
   // 缺失位不能超过2个
   if byteMiss > 2 {
      return "", errors.New("byte miss more than 2")
   }
   // 定义一个原始组合一个新的组,进行转换
   oriGroup := make([]byte, 4)
   newGroup := make([]byte, 3)
   groupCount := len(bytes) / 4
   newBytes := make([]byte, len(bytes)/4*3)
   for i := 0; i < groupCount; i++ {
      // 获取到原始组的字节码
      oriGroup = bytes[i*4 : i*4+4]
      // 根据规则计算
      newGroup[0] = (oriGroup[0] << 2) | (oriGroup[1] & 0b00110000 >> 4)
      newGroup[1] = (oriGroup[1] & 0b00001111 << 4) | (oriGroup[2] & 0b00111100 >> 2)
      newGroup[2] = (oriGroup[2] & 0b00000011 << 6) | oriGroup[3]
      newBytes[3*i] = newGroup[0]
      newBytes[3*i+1] = newGroup[1]
      newBytes[3*i+2] = newGroup[2]
   }
   // 如果有缺失位,
   if byteMiss > 0 {
      newBytes = newBytes[0:len(newBytes) - byteMiss]
   }
   return string(newBytes), nil
}

以上,我们就用Go语言实现了一个Base64的编解码的的包,我们可以很方便的调用里面的编解码方法来实现对数据的Base64编解码。

相关推荐

深度解密epoll 如何工作的?(epoll基本处理流程)

epoll...

大乐透第19082期:头奖开出7注1000万分落六地 奖池41亿元

2019年7月17日晚开奖的体彩超级大乐透第19082期开奖号码为:前区06、18、20、21、31,后区03、04。本期大乐透前区号码五区比为1:0:3:0:1,二区和四区号码没有给出。当期前区和值...

【开奖】4月27日周六:福彩、体彩(2021年4月27日体彩开奖结果)

4月27日开奖福彩3D第2019110期:61222选5第2019110期:0812202122排列3第19110期:303排列5第19110期:30305大乐透第19047期:0304...

“红狒狒”落户哈尔滨铁路局(哈尔滨铁路红肠)

这几天,“红人”“红狒狒”在牡丹江机务段可引起了不小的轰动,众粉丝争相与其拍照留念,在该段人气爆棚!“红狒狒”到底何许人也?“红狒狒”,中文名:和谐3D型电力机车;绰号:红狒狒、番茄;制造商:大连机...

2D、3D、2.5D,做游戏还是搞噱头?玩家都晕了

前言游戏类型就像某种潮流,一种流行罢,另一种接棒成为主流。前两年的新作大多以“开放世界”为标签,在追求纯沙盒的过程中打造出一些细致的分类,比如说“类GTA沙盒”。诚然,纯碎的沙盒游戏并不多见,业内只有...

《战神4》PC版宣传片发布 GTX 1070即可60帧畅玩

在今年10月的时候索尼PlayStation官方正式宣布圣莫尼卡2018年的《战神4》将于2022年1月14日推出PC版本,官方在今天公布了一段PC版宣传片,并且公开了游戏的配置需求。下面让我们一起来...

男星深情好丈夫形象崩塌,半夜搂美女坐大腿,举止亲密

近日,于晓光被拍到深夜在酒吧玩,结束后与一名女子一起上车离开。上车后,女子直接坐在了他腿上,他也顺势搂着美女,美女满脸笑容地坐在他腿上玩手机离开。可能有人会好奇,于晓光是谁呢?于晓光是韩国艺人秋瓷炫的...

d3d12dll丢失怎么修复?d3d12dll加载失败怎么解决?

  d3d12.dll丢失怎么修复?d3d12.dll加载失败怎么解决?很多朋友想要运行游戏的时候都会遇到这个问题,这种情况该怎么办呢?今天系统之家小编给朋友们讲讲具体的解决方法,操作其实还蛮简单的。...

许多玩家反馈《生化4RE》PC一直崩溃 无法进入游戏

今日(3月24日),卡普空《生化危机4:重制版》正式发售,然而有部分PC玩家遇到了游戏崩溃等问题。很多玩家在贴吧发帖称游戏遇到了严重的崩溃问题,且经常反复,报错代码普遍为FatalD3Derror...

微软正式推出适用于WSL Linux的D3D12 GPU视频加速技术

今天,微软正式向WindowsSubsystemforLinux(WSL)用户发布了Direct3D12GPU视频加速支持。在微软通过WSL允许在Linux下使用Open...

《怪物猎人:崛起》曙光系统报错“Fatal d3d error”的解决办法

《怪物猎人:崛起》曙光系统报错“Fatald3derror”的解决办法不少小伙伴反应《怪物猎人:崛起》DLC曙光预载以后打不开游戏,出现了Fatald3derror类似的错误代码,这类问题的解...

Mac+双屏,前端程序员的专业配置 - Loctek 乐歌 D3D 双屏电脑显示器支架

做FE也有一段日子了,电脑屏幕每天在设计稿、浏览器、IDE、即时通讯工具、Terminal、邮箱之间切换。虽然mac的工作区带来了很多灵活,但是依然略显不足。于是入手支架,把公司配的电脑和显示器发挥起...

RPC 的原理和简单使用(rpc详解)

RPC的概念RPC,RemoteProcedureCall,翻译成中文就是远程过程调用,是一种进程间通信方式。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数。在调用的...

大厂开源的golang微服务rpc框架 — kitex

提前rpc估计所有的开发同学都知道,不知道的也无所谓,毕竟我也好几年没用了,今天带大家在复习一下。RPC(RemoteProcedureCall):远程过程调用,...

干货!一文掌握Protobuf所有语言所有用法,快收藏

说实话,Protobuf这个库,让人相见时难别亦难,东风无力百花残,每次等到要用它的时候,总感觉还没有完全掌握它的用法,而实际上等去百度或者谷歌的时候,教程都是多么的凌乱不堪。学会它,最直接关系到的,...

取消回复欢迎 发表评论: