Base64算法原理及实现

2020-03-07  本文已影响0人  咸鱼0907

Base64算法的由来

Base64算法最开始是被用于解决电子邮件数据传输问题。在早期,由于历史原因问题,电子邮件只允许使用ASCII字符,如果在邮件中出现了非ASCII字符,在通过某些网关进行数据转发的时候,网关会对这些非ASCII字符做出调整,例如,把ASCII码8位二进制码的最高位置为0。此时接收方在收到邮件时就会出现乱码。基于这个原因,产生了Base64算法。

Base64算法定义

Base64编码的思路说白了,就是把传输数据的每个字节映射成ASCII码表中的某些字符,这样在传输的过程中,就不会出现乱码的问题了。Base64算法定义了一个映射表,如下所示。

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

由上表可以看出,之所以称为Base64编码,实际上是把原数据映射成了ASCII码表中的64个字符。但是,64个字符最多能映射的位数是6bit。但是每个数据是8bit的,那怎么转换呢?Base64编码的基本思想:将原数据每3个字节(24bit)分为一组,然后将这24bit数据按照每6bit一组,重新划分为4组,分组完成之后,再将每每6bit数据为单元进行映射。
Base64编码的基本流程如下:

  1. 将给定的数据转换成二进制编码,转换成二进制编码的方式可以是ASCII,UTF-8等。
  2. 对给定的编码做分组转换操作,每3个字节(24bit)分为一组,然后将这24bit划分为4组6bit。
  3. 对获得的4组6bit编码进行补位,向6bit编码的高位补2bit 0,变成4组8bit编码。
  4. 将每个8bit编码转换为十进制编码。
  5. 以十进制编码为索引,映射为上表中对应的字符。

Base64编码举例

当原文字节长度是3的整数倍

例如,将字符串"ABC"进行Base64编码流程如下。

  1. 使用ASCII编码方式将字符串"ABC"转换成二进制数据 01000001 | 01000010 | 01000011
  2. 将步骤1的二进制数据进行分组,每个分组6bit 010000 | 010100 | 001001 | 000011
  3. 将步骤2的4组6bit二进制编码数据进行补位(高位补0),变成4组8bit二进制 00010000 | 00010100 | 00001001 | 00000011
  4. 将步骤3中的4组8bit转换成十进制。16 | 20 | 9 | 3
  5. 以步骤4的十进制数据为索引,去Base64编码映射表中寻找对应的字符。16在编码表中映射的字符是Q,20映射的字符是U,9映射的字符是J,3映射的字符是D。

所以,字符串"ABC"经过Base64编码后的数据是"QUJD"。

当原文字节长度不是3的整数倍

从Base64编码的原理可以看到,Base64实际上就是把原来数据中的每3个字节一组进行Base64编码转换,编码之后变成4个Base64字符。但是如果原文数据长度不是3的整数倍的时候该怎么办呢?Base64算法规定,如果待加密数据不是3的整数倍,就在原文数据后面补0,直到长度凑够3的整数倍为止,然后再进行Base64编码转换。待编码转换完成之后,在结果末尾补充相同个数的"="。
例如,将字符串"ABCD"进行Base64编码流程如下。

  1. 使用ASCII编码方式将"ABC"转换成二进制 01000001 | 01000010 | 01000011 | 01000100
  2. 将步骤1的二进制数据进行分组,由于数据长度是4个字节,不是3的整数倍,无法直接进行分组,所以分组之前要在数据末尾进行补0,凑够23的整数倍,这里需要补充两个0字节,补充0之后,待编码数据变为01000001 | 01000010 | 01000011 | 01000100 | 00000000 | 00000000
  3. 将步骤2中的数据按每组6bit进行分组,010000 | 010100 | 001001 | 000011 | 010001 | 000000 | 000000 | 000000
  4. 将步骤3的每组6bit分组数据进行高位补0,变成8bit二进制数据分组。 00010000 | 00010100 | 00001001 | 00000011 | 00010001 | 00000000 |00000000 | 00000000
  5. 将步骤4中的8bit数据分组转换成十进制。16 | 20 | 9 | 3 | 17 | 0 | 0(填充) | 0(填充)
  6. 以步骤5中的十进制数据为索引,去Base64编码映射表中寻找对应的字符。16映射的字符是Q,20映射的字符是U,9映射的字符是J,3映射的字符是D,17映射的字符是R,0映射的字符是A,这里需要注意,上述数据最后两个字节也是0,但是,这里不能直接映射字符A,因为这两个0字节是都是填充数据,Base64规定,对于这种情况使用=来代替填充位的0字节。所以,映射的结果是"QUJDRA=="。

所以,字符串"ABC"经过Base64编码后的字符串是"QUJDRA=="。

其实这里有个规律,当原文的数据长度除以3余数为0时,编码之后后面没有"=";当余数为1时,后面有两个"=",当余数是2时,后面有一个"=","="的个数也就是补充的字节数。

传输效率

通过Base64的原理可以看到,Base64编码实际上是把原数据的3个字节映射成了4个字节,所以相比于原数据长度,编码后的长度会增加1/3。这也会降低传输效率。

Url Base64算法

Get方式和Post方式是Http请求常用的两种方式,某些情况下会要求使用Get方式来传递二进制数据。这时,可以先通过Base64编码来将二进制数据转换成字符串数据。由于符号"+"和符号"/"是不允许出现在Url中的,所以,产生了Url安全的Base64算法,所谓的Url安全的Base64算法,其实主要包含两个方面。

  1. 首先,"+"和"/"是不能出现在Url中的,所以Url安全的Base64算法将原映射表中的"+"和"/"替换成了"-"和"_"。
  2. 其次,在原来的Url算法中,当数据长度不能被3整除时,编码结果会在末尾填充"=",而在Url中,"="是有特殊含义的,所以"="不能出现在结果中。对于这个问题,目前有两种解决方案。
    (1) 将"="替换为其他字符,例如,可以用其他符号替代,例如可以用"","."等符号替代,但是""与文件系统冲突,不能使用,有的文件系统会认为连续的两个"."是错误。
    (2) 去掉后面的填充的"=",去掉”=“后怎么解码呢?因为Base64是把3个字节变为4个字节,所以,Base64编码的长度永远是4的倍数,因此,解码时,如果数据长度不是4的整数倍,在数据后面填充"=",把Base64字符串的长度变为4的倍数,就可以正常解码了。
    由于Url Base64算法并没有形成统一的规范,有的软件可能会使用自定义的映射表。

Java Base64算法实现

目前,在Java中,我们可以通过以下方式来是使用Base64算法。

JDK类库中的Base64

在java8之前,JDK官方库中都没有内置Base64算法,其实Base64实现很简单,这个不知道为什么。但是Java8内置了Base64编码器和解码器。
在Java8中,Base64工具类提供了三种BASE64编解码器:
1.基本Base64编码
也就是完全按照标准Base64的映射规则来编解码,不添加任何行标。

public static void javaUtilBase64() {
    try {
        Base64.Encoder encoder = Base64.getEncoder();
        String text = "AB>CD?";
        byte[] textByte = text.getBytes("UTF-8");
        String encodedText = encoder.encodeToString(textByte);
        System.out.println(encodedText); 
    } catch (Exception e) {
    }
}

// 输出结果:
 QUI/Q0Q+

2.Url Base64编码
JDK标准类库中的Url Base64编码是用"-"和"_"取代了"+"和"/"

public static void javaUtilUrlBase64() {
    try {
        Base64.Encoder encoder = Base64.getUrlEncoder();
        String text = "AB>CD?";
        byte[] textByte = text.getBytes("UTF-8");
        String encodedText = encoder.encodeToString(textByte);
        System.out.println(encodedText); 
    } catch (Exception e) {
    }
}

//输出结果:
QUI-Q0Q_

3.MIME Base64编码
Java类库中还提供了一种格式更友好的Base64编码,这种编码输出每行不超过76字符,并且使用'\r'并跟随'\n'作为分割。

public static void javaUtilMimeBase64() {
    try {
        Base64.Encoder encoder = Base64.getMimeEncoder();
        String text = "";
        for(int i=0;i<10;i++) {
            text += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        }
        byte[] textByte = text.getBytes("UTF-8");
        String encodedText = encoder.encodeToString(textByte);
        System.out.println(encodedText);
    } catch (Exception e) {
            
    }
}

//输出结果: 
QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWkFCQ0RF
RkdISUpLTE1OT1BRUlNUVVZXWFlaQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpBQkNERUZHSElK
S0xNTk9QUVJTVFVWV1hZWkFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaQUJDREVGR0hJSktMTU5P
UFFSU1RVVldYWVpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWkFCQ0RFRkdISUpLTE1OT1BRUlNU
VVZXWFlaQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=

4.去除填充符的Base64
在Java标准类库中,还提供了一种方式来去除编码末尾的"=",就是在构建Encoder 对象后调用withoutPadding()方法,例如:

public static void javaUtilBase64WithoutPadding() {
    try {
        Base64.Encoder encoder = Base64.getEncoder().withoutPadding();
        String text = "ABCD";
        byte[] textByte = text.getBytes("UTF-8");
        String encodedText = encoder.encodeToString(textByte);
        System.out.println(encodedText);
    } catch (Exception e) {     
    }
}

// 输出结果:
 QUJDRA,如果不调用withoutPadding(),输出结果为QUJDRA=
Commons Codec中 的Base64

Commons Codec是Apache为Java开发者提供的一个开源软件类库,该类库中主要是一些常用的编码工具类包,例如DES、SHA1、MD5、Base64,URL等。在使用该类库之前需要首先在Eclipse中添加依赖。Commons Codec提供了以下Base64编码方式。
1.基本Base64编码
Commons Codec和Java标准类库提供给的Base64编码方式是一样的。

public static void commonsCodecBase64() {
    try {
        String text = "AB>CD?";
        byte[] textByte = text.getBytes("UTF-8");
        String encodedText = Base64.encodeBase64String(textByte);
        System.out.println(encodedText);
    } catch (Exception e) {
    }
}

//输出结果:
QUI+Q0Q/

2.Url Base64编码
Url Base64编码和Java类库也是一样的,把"+"和"/"替换成了"-"和"_",有一个不同的地方是Commons Codec中的Url Base64默认去掉了后面的"=",相当于Java类库中调用了withouPadding方法,例如:

public static void commonsCodecBase64() {
    try {
        String text = "AB>CD?E";
        byte[] textByte = text.getBytes("UTF-8");
        String encodedText = Base64.encodeBase64URLSafeString(textByte);
        System.out.println(encodedText);
    } catch (Exception e) {
    }
}

//输出结果:
QUI-Q0Q_RQ

3.类MIME格式输出
Commons Codec中也提供了类似于Java类库中的MIME的格式化输出,在Commons Codec中有一个方法:

encodeBase64(final byte[] binaryData, final boolean isChunked)

这里的isChunked置为true,就表示是按照MIME格式输出编码结果。

public static void commonsCodecBase64() {
    try {
        String text = "";
        for(int i=0;i<10;i++ ) {
            text += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        }
        byte[] textByte = text.getBytes("UTF-8");
        byte[] encodedTextByte = Base64.encodeBase64(textByte, true);
        String encodedText= new String(encodedTextByte,"UTF-8");
        System.out.println(encodedText);
    } catch (Exception e) {
    }
}

//输出结果
QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWkFCQ0RF
RkdISUpLTE1OT1BRUlNUVVZXWFlaQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpBQkNERUZHSElK
S0xNTk9QUVJTVFVWV1hZWkFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaQUJDREVGR0hJSktMTU5P
UFFSU1RVVldYWVpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWkFCQ0RFRkdISUpLTE1OT1BRUlNU
VVZXWFlaQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=
上一篇下一篇

猜你喜欢

热点阅读