Base64编解码原理

目前Base64已经成为网络上常见的传输8比特字节代码的编码方式之一。在做支付系统时,系统之间的报文交互都需要使用Base64对明文进行转码,然后进行签名或加密,之后再次Base64编码传输。那么,Base64到底起到什么作用呢?

在参数传输的过程中经常遇到的一种情况:使用全英文的没问题,但一旦涉及到中文就会出现乱码情况。与此类似,网络上传输的字符并不全是可打印的字符,比如二进制文件、图片等。Base64的出现就是为了解决此问题,它是基于64个可打印的字符来表示二进制的数据的一种方法。

电子邮件刚问世的时候,只能传输英文,但后来随着用户的增加,中文、日文等文字的用户也有需求,但这些字符并不能被服务器或网关有效处理,因此Base64就登场了。随之,Base64在URL、Cookie、网页传输少量二进制文件中也有相应的使用。

Base64的原理比较简单,Base64定义了"A-Z、a-z、0-9、+、/"64个可打印字符,这是标准的Base64协议规定。在日常使用中我们还会看到=或==号出现在Base64的编码结果中,=在此是作为填充字符出现,待会会讲到。

Base64编码步骤:

  1. 将待转换的字符串按指定的字符集(如UTF-8、GBk等)编码成字节数组
  2. 每三个字节分为一组,每个字节占8比特,那么每组共有24个比特位
  3. 将上面的24个比特位每6个一组,共分为4组
  4. 在每组前面添加两个0,每组由6个变为8个比特位,总共32个比特位,即四个字节
  5. 按照Base64编码表(如下所示)将四个字节转化为四个对应的字符
0 A  17 R   34 i   51 z1 B  18 S   35 j   52 02 C  19 T   36 k   53 13 D  20 U   37 l   54 24 E  21 V   38 m   55 35 F  22 W   39 n   56 46 G  23 X   40 o   57 57 H  24 Y   41 p   58 68 I  25 Z   42 q   59 79 J  26 a   43 r   60 810 K  27 b   44 s   61 911 L  28 c   45 t   62 +12 M  29 d   46 u   63 /13 N  30 e   47 v14 O  31 f   48 w   15 P  32 g   49 x16 Q  33 h   50 y

由于Base64是按三个字节来分组,如果最后一组字节数不足三个,那么该如何处理?

  • 两个字节:两个字节共16个比特位,依旧按照规则进行分组。此时总共16个比特位,每6个一组,则第三组缺少2位,低位用0补齐,得到三个Base64编码,第四组完全没有数据则用=补齐
  • 一个字节:一个字节共8个比特位,依旧按照规则进行分组。此时共8个比特位,每6个一组,则第二组缺少4位,低位用0补齐,得到两个Base64编码,而后面两组没有对应数据,都用=补齐

从上面的步骤我们发现:

  • Base64将3个字节共24个比特分成4组,编码后每组对应一个字节,即3个字节编码后变成4个字节,因此Base64编码后要比原文大1/3
  • 为什么使用3个字节一组呢?因为6和8的最小公倍数为24,三个字节正好24个比特位,每6个比特位一组,恰好能够分为4组

注意:

  • 大多数编码都是由字符串转化成二进制,而Base64编码恰恰相反是从二进制转换为字符串
  • Base64编码主要用传输、存储、表示二进制领域,算不上加密,Base64是可以直接解码的
  • 由于Base64是对字节编码,因为同一字符串的不同编码(UTF-8、GBK等)对应的Base64编码不同
  • Base64用6个比特位表示字符,因此称为Base64((2的6次幂为64)。同理,Base32就是用5位,Base16就是用4位

知道了Base64如何编码,那么解码就很容易了,下面说下步骤。
Base64解码步骤:

  1. 按4个字符为一组(Base64编码出来的字符串的长度一定是4的倍数)
  2. 解析拿到每一组有效的24个比特串,如果最后一组有一个=号,则16个比特串有效,最后一组有两个=号,则8个比特串有效(至于是哪些比特串有效,不用再介绍了吧,前面编码步骤中已经介绍过了)
  3. 将有效的比特串转为字节数组,再将字节数组按指定的字符集(如UTF-8、GBk等)转为字符串

参考文章:一篇文章彻底弄懂Base64编码原理

Java实现Base64编解码(不使用任何API)

下面是纯手工实现的Base64编解码,没有用到任何API(为什么这么说呢?因为强大的Java已经有实现了即class java.util.Base64)

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
/*** Base64编解码实现*/
public class MyBase64 {/*** 编码*/public static String encode(String s,String charset) throws UnsupportedEncodingException {byte[] arr = s.getBytes(charset);int a = arr.length % 3;int b = arr.length / 3;StringBuilder base64Str = new StringBuilder();// 以3个字节为一组for (int i = 0; i < 3*b; i=i+3) {// 3个字节一共24位,每6位为一组,分为4组。由于是6位,一共有2^6=64个值(0~63),因为称为Base64String bits1 = byteToBinaryString(arr[i]).substring(0, 6);String bits2 = byteToBinaryString(arr[i]).substring(6, 8)+byteToBinaryString(arr[i+1]).substring(0, 4);String bits3 = byteToBinaryString(arr[i+1]).substring(4, 8)+byteToBinaryString(arr[i+2]).substring(0, 2);String bits4 = byteToBinaryString(arr[i+2]).substring(2, 8);// 计算这4组每一组的值int w=compute(bits1);int x=compute(bits2);int y=compute(bits3);int z=compute(bits4);// 将每一组的值转换为Base64编码,并拼接在一起base64Str.append(convert(w)).append(convert(x)).append(convert(y)).append(convert(z));}if(a == 1){// 分组后,多出来一个字节,共8个bit位,则只能分为两组,即6+2,第三组和第四组直接用=表示,第二组缺失的低四位补0String bits1 = byteToBinaryString(arr[3*b]).substring(0, 6);String bits2 = byteToBinaryString(arr[3*b]).substring(6, 8)+"0000";int w = compute(bits1);int x = compute(bits2);base64Str.append(convert(w)).append(convert(x)).append('=').append('=');}else if(a == 2){// 分组后,多出来二个字节,共16个bit位,则只能分为三组,即6+6+4,第四组直接用=表示,第二组缺失的低二位补0String bits1 = byteToBinaryString(arr[3*b]).substring(0, 6);String bits2 = byteToBinaryString(arr[3*b]).substring(6, 8)+byteToBinaryString(arr[3*b+1]).substring(0, 4);String bits3 = byteToBinaryString(arr[3*b+1]).substring(4, 8)+"00";int w = compute(bits1);int x = compute(bits2);int y = compute(bits3);base64Str.append(convert(w)).append(convert(x)).append(convert(y)).append('=');}return base64Str.toString();}/*** 解码*/public static String decode(String s,String charset) throws UnsupportedEncodingException {List<Byte> bytes = new ArrayList<>();// 由于Base64编码是将三个字节作为一组变成4个字节,即4个字符,则解码时按4个字符为一组来进行解码for (int i = 0; i < s.length(); i=i+4) {if(s.charAt(i+2) == '='){// 最后一组有两个=的情况// 将字符转为Base64编码byte w = convert(s.charAt(i));byte x = convert(s.charAt(i+1));// 获取比特串String bits = byteToBinaryString(w).substring(2, 8) + byteToBinaryString(x).substring(2, 4);// 会得到一个字节for (int j = 0; j < bits.length(); j=j+8) {bytes.add(binaryStringToByte(bits.substring(j,j+8)));}}else if(s.charAt(i+3) == '='){// 最后一组有一个=的情况// 将字符转为Base64编码byte w = convert(s.charAt(i));byte x = convert(s.charAt(i+1));byte y = convert(s.charAt(i+2));// 获取比特串String bits = byteToBinaryString(w).substring(2, 8) + byteToBinaryString(x).substring(2, 8)+byteToBinaryString(y).substring(2, 6);// 会得到两个字节for (int j = 0; j < bits.length(); j=j+8) {bytes.add(binaryStringToByte(bits.substring(j,j+8)));}}else{// 无=的情况// 将字符转为Base64编码byte w = convert(s.charAt(i));byte x = convert(s.charAt(i+1));byte y = convert(s.charAt(i+2));byte z = convert(s.charAt(i+3));// 获取比特串String bits = byteToBinaryString(w).substring(2, 8) + byteToBinaryString(x).substring(2, 8)+ byteToBinaryString(y).substring(2, 8) + byteToBinaryString(z).substring(2, 8);// 会得到三个字节for (int j = 0; j < bits.length(); j=j+8) {bytes.add(binaryStringToByte(bits.substring(j,j+8)));}}}byte[] arr = new byte[bytes.size()];for (int i = 0; i < bytes.size(); i++) {arr[i] = bytes.get(i);}// 按指定字符集将byte数组转为字符串return new String(arr,charset);}/*** 将Base64编码转为对应字符的Ascii编码,进而得到对应字符* 字符的Ascii编码与Base64编码的映射关系如下:* System.out.println((int)'A'); //65 0* System.out.println((int)'a'); //97 26* System.out.println((int)'0'); //48 52* System.out.println((int)'+'); //43 62* System.out.println((int)'/'); //47 63*/private static char convert(int w){if(w >=0 && w <= 25){return (char)(w+65);}else if(w >= 26 && w <= 51){return (char)(w+71);}else if(w >= 52 && w <= 61){return (char)(w-4);}else if(w == 62){return '+';}else if(w == 63){return '/';}throw new RuntimeException("convert error");}/*** 将字符转为对应的Ascii编码,然后再转为对应的Base64编码*/private static byte convert(char w){if(w >=65 && w <= 90){return (byte)(w-65);}else if(w >= 97 && w <= 122){return (byte)(w-71);}else if(w >= 48 && w <= 57){return (byte)(w+4);}else if(w == 43){return 62;}else if(w == 47){return 63;}throw new RuntimeException("convert error");}/*** 计算任意位数二进制比特串的十进制的值*/private static int compute(String bits){int sum = 0;for (int j = 0; j < bits.length(); j++) {if(bits.charAt(j) == '1'){sum+= (int)Math.pow(2,bits.length()-1-j);}}return sum;}/*** byte转8位比特串*/private static String byteToBinaryString(byte b){// -128特殊处理if(b == -128){return "10000000";}byte tmp = b;if(tmp<0){// 将符号位从1转为0。如-127的二进制为10000001,只需要用乘积取余法得到1的比特串,再将高位0变成1就得到-127的比特串tmp = (byte)(tmp & 127);}// 存储8个比特位int index = 7;byte[] bits = new byte[8];// 开始转换byte x = tmp;do {byte y = (byte) (x%2);x = (byte) (x >> 1);bits[index--] = y;}while (x != 0);if(b < 0){// 如果是负数,则高位的0变为1bits[0] = 1;}StringBuilder sb = new StringBuilder();for (int i = 0; i < bits.length; i++) {sb.append(bits[i]);}return sb.toString();}/*** 8位比特串转byte*/private static byte binaryStringToByte(String bits){if(bits.length() != 8){throw new RuntimeException("binaryStringToByte error");}if(bits.charAt(0) == '0'){return (byte) compute(bits);}else if(bits.charAt(0) == '1'){if(bits.equals("10000000")){return -128;}else{// 计算负数补码的值byte v1 = (byte)compute(bits.substring(1));// 计算负数原码的值byte v2 = (byte)(~(v1-1));int v3 = compute(byteToBinaryString(v2).substring(1));return (byte)-v3;}}throw new RuntimeException("binaryStringToByte error");}public static void main(String[] args) throws UnsupportedEncodingException {System.out.println(encode("你不要过来啊?!ok","UTF-8"));System.out.println(decode("5L2g5LiN6KaB6L+H5p2l5ZWKPyFvaw==","UTF-8"));}
}

运行结果如下所示:

Base64编解码原理并用Java手工实现Base64编解码相关推荐

  1. java 解码 encodeuri_js与java encodeURI 进行编码与解码

    //网上一堆废话 内置不用写一堆文件处理真是蛋疼 JS escape()使用转义序列替换某些字符来对字符串进行编码 JavaScript 中国 编码后 JavaScript%u4E2D%u56FD u ...

  2. 彻底弄懂base64的编码与解码原理

    作者介绍 背景 base64的编码原理网上讲解较多,但解码原理讲解较少,并且没有对其中的内部实现原理进行剖析.想要彻底了解base64的编码与解码原理,请耐心看完此文,你一定会有所收获. 涉及算法与逻 ...

  3. [原创]桓泽学音频编解码(7):MP3 和 AAC 中huffman解码原理,优化设计与参考代码中实现...

    1 不同标准中的huffman解码原理 1.1标准MP3的huffman解码原理 在MP3即mpeg-1 audio标准中,无噪声编码模块的输入是一组576个己量化的频谱数据.无噪声编码首先对频谱进行 ...

  4. Java 8实现BASE64编解码

    Java 8实现BASE64编解码 作者:chszs,转载需注明.博客主页:http://blog.csdn.net/chszs Java一直缺少BASE64编码 API,以至于通常在项目开发中会选用 ...

  5. c# java base64编码解码_C#教程之Base64编码解码原理及C#编程实例

    一. Base64编码由来 为什么会有Base64编码呢?因为有些网络传送渠道并不支持所有的字节,例如传统的邮件只支持可见字符的传送,像ASCII码的控制字符就不能通过邮件传送.这样用途就受到了很大的 ...

  6. base64解码_一份简明的 Base64 原理解析

    书接上回,在 记一个 Base64 有关的 Bug 一文里,我们说到了 Base64 的编解码器有不同实现,交叉使用它们可能引发的问题等等. 这一回,我们来对 Base64 这一常用编解码技术的原理一 ...

  7. Base64与Java -- Base64简介与原理

    Base64与Java – Base64简介与原理 文章目录 Base64与Java -- Base64简介与原理 简介与用途 为什么叫Base64? 编码流程 常规处理 特殊处理 剩余1个字节 剩余 ...

  8. Base64编码解码原理

    一. Base64编码由来 为什么会有Base64编码呢?因为有些网络传送渠道并不支持所有的字节,例如传统的邮件只支持可见字符的传送,像ASCII码的控制字符就不能通过邮件传送.这样用途就受到了很大的 ...

  9. 哈夫曼编解码原理与实现【转载】

    1. 哈夫曼编解码原理 霍夫曼编码(Huffman Coding)是一种编码方法,霍夫曼编码是可变字长编码(VLC)的一种. 霍夫曼编码使用变长编码表对源符号(如文件中的一个字母)进行编码,其中变长编 ...

最新文章

  1. SSHDroid及sshpass简介
  2. python学到什么程度可以做兼职-Python学到什么程度才可以去找工作?掌握这4点足够了!...
  3. golang中的测试命令
  4. Fencing the Cows [USACO]
  5. Azure开发者任务之一:解决Azure Storage Emulator初始化失败
  6. Hadoop 2.2.0源码浏览:4. NodeManager
  7. golang 结构体断言_Golang中的reflect原理
  8. 获取windows所有端口
  9. 暮色森林模组_《我的世界》暮色森林VS天启之境 到底谁才是冒险模组一哥
  10. python public_python中private、protectedamp;public
  11. WinServer 2012 R2 搭建域控服务器、文件服务器并配置权限
  12. WebStorm 汉化教程-Mac
  13. 电力载波通信模块JST-HPLC-S-C在物联网通信领域的应用
  14. C# ManualResetEvent 类分析
  15. macOS下统计pdf字数
  16. matlab 汽车理论,汽车理论matlab作业
  17. 计算机配置的详细信息,如何查看电脑的配置参数,看电脑详细配置的方法
  18. rclone 实现 GoogleDrive 同步至 OneDrive
  19. opencv与openmv?
  20. 一场云端的“神仙打架”:BAT加华为的影响未来之争

热门文章

  1. PhotoShop画漫画的基本设置
  2. 红帽RHCE认证含金量怎么样?学红帽难吗?
  3. 深天马A去年实现净利润9.84亿元 同比减少35.88%
  4. autodesk系列产品无法安装解决方案
  5. 【raid5数据恢复】服务器RAID5中一块硬盘亮黄灯被踢出导致raid崩溃的数据恢复
  6. 净现值、投资回收期例题讲解
  7. 黑衣人---走出软件作坊:三五个人十来条枪 如何成为开发正规军(三十四)
  8. 同程旅游网开放平台SDK开发完成
  9. 阿里云 RAM 企业上云实战
  10. anycast隧道_隧道服务器是什么意思