Base64算法原理及实现

Base64算法的由来

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

Base64算法定义

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

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

由上表可以看出,之所以称为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=

h

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容

  • 每个文本编辑器都有默认的编码方式(比如 UTF-8 编码),当我们保存文档的时候,可以选择编码方式,如果没有特意选...
    _于曼丽_阅读 1,500评论 0 1
  • 在介绍加密算法之前, 先介绍一下 base64: 0. base64 Base64要求把每三个8Bit的字节转换为...
    reboot_q阅读 12,658评论 3 8
  • 一、什么是对称加密技术? 对称加密采用了对称密码编码技术,它的特点是文件加密和解密使用相同的密钥。信息接收双方都需...
    Djbfifjd阅读 1,881评论 2 8
  • 扶 助 文/夏文明 这是一年内最炎热的一天。中午时分,太阳热烈的把激情释放给大地,地表也...
    艾夜读阅读 486评论 2 11
  • 《惊奇队长》陪你一起“女神节快乐”,首日票房将破2亿 明天是3月8号,是漫威首部以女性超级英雄为主角的影片《惊奇队...
    黎明说影视阅读 111评论 0 0