继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Base64编码原理解析与Java实现

SoyaDokio
关注TA
已关注
手记 2
粉丝 4
获赞 90

声明:本文首发在CSDN的个人博客(点此直达首发),慕课网的手记功能目前还存在一些小瑕疵,所以文章格式怪怪的。虽然CSDN的格式也略怪,但好歹没有漏掉一些符号,建议看原文吧。另,晚些时候再尝试慕课网的手记功能好了。

一、前言

目前还在找工作,工作日时投投简历面面试,这周末难免就闲来无事了,那就只好看看慕课逛逛CSDN了,正巧看到一个关于Base64的课程《Java实现Base64加密》,点进去看看,完了发觉完全不是我想的那回事儿,人给的实现方式还不唯一,给了3个API,但是没有实现原理。我这个愣头青没别的优点,就是喜欢死钻牛角尖,于是抱着试试的心态,查了官网RFC2045下载完整PDF),了解了下相关情况,这才有了本文。

二、Base64是什么

这种高度概括的事情我相信Base64的百度百科比我靠谱多了,如下:
Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一,大家可以查看RFC2045~RFC2049,上面有MIME的详细规范。Base64编码可用于在HTTP环境下传递较长的标识信息。例如,在JavaPersistence系统Hibernate中,就采用了Base64来将一个较长的唯一标识符(一般为128-bit的UUID)编码为一个字符串,用作HTTP表单和HTTPGETURL中的参数。在其他应用程序中,也常常需要把二进制数据编码为适合放在URL(包括隐藏表单域)中的形式。此时,采用Base64编码具有不可读性,即所编码的数据不会被人用肉眼所直接看到。
标准的Base64并不适合直接放在URL里传输,因为URL编码器会把标准Base64中的“/”和“+”字符变为形如“%XX”的形式,而这些“%”号在存入数据库时还需要再进行转换,因为ANSISQL中已将“%”号用作通配符。
为解决此问题,可采用一种用于URL的改进Base64编码,它在末尾填充'='号,并将标准Base64中的“+”和“/”分别改成了“-”和“”,这样就免去了在URL编解码和数据库存储时所要作的转换,避免了编码信息长度在此过程中的增加,并统一了数据库、表单等处对象标识符的格式。
另有一种用于正则表达式的改进Base64变种,它将“+”和“/”改成了“!”和“-”,因为“+”,“*”以及前面在IRCu中用到的“[”和“]”在正则表达式中都可能具有特殊含义。
此外还有一些变种,它们将“+/”改为“
-”或“.”(用作编程语言中的标识符名称)或“.-”(用于XML中的Nmtoken)甚至“:”(用于XML中的Name)。
Base64要求把每三个8Bit的字节转换为四个6Bit的字节(38=46=24),然后把6Bit再添两位高位0,组成四个8Bit的字节,也就是说,转换后的字符串理论上将要比原来的长1/3。

三、规则与原理

原理(这回觉得百度百科不靠谱,引用的是Base64的维基百科)
在MIME格式的电子邮件中,base64可以用来将binary的字节序列数据编码成ASCII字符序列构成的文本。使用时,在传输编码方式中指定base64。使用的字符包括大小写字母各26个,加上10个数字,和加号“+”,斜杠“/”,一共64个字符,等号“=”用来作为后缀用途。
完整的base64定义可见RFC1421和RFC2045。编码后的数据比原始数据略长,为原来的4/3。在电子邮件中,根据RFC822规定,每76个字符,还需要加上一个回车换行。可以估算编码后数据长度大约为原长的135.1%(算式1(4/3)((76+1)/76)约等于1.351)。
转换的时候,将三个byte的数据,先后放入一个24bit的缓冲区中,先来的byte占高位。数据不足3byte的话,于缓冲器中剩下的bit用0补足。然后,每次取出6(因为26=64)个bit,按照其值选择ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/中的字符作为编码后的输出。不断进行,直到全部输入数据转换完成。
当原数据长度不是3的整数倍时,如果最后剩下一个输入数据,在编码结果后加2个“=”;如果最后剩下两个输入数据,编码结果后加1个“=”;如果没有剩下任何数据,就什么都不要加,这样才可以保证数据还原的正确性。

上面大段的文字概括来讲,可以总结为以下3点:
①把3个8位字节(3
8=24)转化为4个6位的字节(46=24),之后在6位的前面补两个0,形成8位一个字节的形式。
如果剩下的字符不足3个字节,则用0填充,输出字符使用'=',因此编码后输出的文本末尾可能会出现1或2个'=';
②每76个字节数据后加一个换行符;
③若数据长度除以3余1,则在编码结束时加2个“=”,若数据长度除以3余2,则在编码结束时加1个“=”。

规则
一个是ASCII编码表,如下:
ASCII编码表
另一个是Base64编码表,如下:
Base64编码表

四、实例

我们还是来看下面这3个例子比较直观(例子下载)。
例子1、假设我们的明文为“Base64”(数据长度为6,正好是3的倍数),则其编码计算方式如下:

例子1
例子2、假设我们的明文为“test”(数据长度为4,4%3=1),则其编码计算方式如下:
例子2
例子3、假设我们的明文为“JiaMi”(数据长度为5,5%3=2),则其编码计算方式如下:
例子3

五、编程思路

思路什么的,我觉得就不必要写了,代码里注释得很充分了。

六、代码
package com.dikio.base64;

import java.util.List;
import java.util.ArrayList;

public class Base64 {

    private static String base64Code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

    public static void main(String[] args) {
        System.out.println(encode("Base64"));
        System.out.println(decode("QmFzZTY0"));
    }

    public static String encode(String srcData) {
        if(srcData == null  srcData.length() == 0) {
            return srcData;
        }
        char[] chArr = srcData.toCharArray();
        String asciiBin = null;
        StringBuilder asciiBin_all = new StringBuilder();
        for(int i= 0; i< chArr.length; i++) {
            //将字符转换成ASCII编码再转换成对应二进位
            asciiBin = Integer.toBinaryString((int)chArr[i]);
            //给不足8位的在高位补0直到补足8位
            while(asciiBin.length()< 8) {
                asciiBin= "0"+ asciiBin;
            }
            //最后把所有二进位拼接成一个字串
            asciiBin_all.append(asciiBin);
        }
        //若长度不能被6整除,则在低位补0到能被6整除为止
        while(asciiBin_all.length()% 6!= 0) {
            asciiBin_all.append("0");
        }
        String asciiBinStr = asciiBin_all.toString();
        //按6个一组拆分成字串数组
        List<String> bin6List = new ArrayList<String>();
        String temp = null;
        while(asciiBinStr.length()/ 6> 0) {
            temp = asciiBinStr.substring(0, 6);
            asciiBinStr = asciiBinStr.substring(6);
            bin6List.add(temp);
        }
        String[] bin6Str = bin6List.toArray(new String[bin6List.size()]);
        int[] index = new int[bin6Str.length];
        //确定最终补位长度
        int overLen = 0;
        if(srcData.length()% 3 != 0) {
            overLen = 3- srcData.length()% 3;
        }
        //设定存放最终编码的容器
        char[] code = new char[index.length+ overLen];
        for(int i= 0; i< index.length; i++) {
            //将二进位转换成十进制数字
            index[i] = Integer.parseInt(bin6Str[i], 2);
            //Base64 : Value -> Encoding
            code[i] = base64Code.charAt(index[i]);
        }
        switch(overLen) {
            case 2:code[code.length- 2] = '=';//不需要break
            case 1:code[code.length- 1] = '=';
            default:
        }
        return String.valueOf(code);
    }

    public static String decode(String srcData) {
        //检测元数据中“=”的个数,并将之去除
        int counter = 0;
        if(srcData.contains("=")) {
            counter = 1;
            if(srcData.substring(srcData.length()- 2, srcData.length()- 1).equals("=")) {
                counter = 2;
            }
        }
        srcData = srcData.replaceAll("=", "");
        //将密文根据Base64编码表转换成对应Value,再转换成二进位 ,然后将所有二进位补足6位,最后将所有二进位存进一个字串
        char[] srcCh = srcData.toCharArray();
        StringBuffer bin6SB = new StringBuffer();
        int index;
        String bin6Str;
        for(int i= 0; i< srcCh.length; i++) {
            //获得Base64编码表的Value
            index = base64Code.indexOf(srcCh[i]);
            //将Value转为二进位
            bin6Str = Integer.toBinaryString(index);
            //在长度不足6位的二进位的高位上补0直到补足6位,再保存进字串
            while(bin6Str.length()< 6) {
                bin6Str = "0"+ bin6Str;
            }
            bin6SB.append(bin6Str);
        }
        String bin6Str_all = bin6SB.toString();
        //如果二进位字串后有多补的0,将之去除
        if(counter == 1) {
            bin6Str_all = bin6Str_all.substring(0, bin6Str_all.length()- 2);
        } else if(counter == 2) {
            bin6Str_all = bin6Str_all.substring(0, bin6Str_all.length()- 4);            
        }
        //按8个一组拆分成字串数组
        List<String> bin8List = new ArrayList<String>();
        String temp;
        while(bin6Str_all.length()/ 6> 0) {
            temp = bin6Str_all.substring(0, 8);
            bin6Str_all = bin6Str_all.substring(8);
            bin8List.add(temp);
        }
        String[] bin8Str = bin8List.toArray(new String[bin8List.size()]);
        //将该字串数组的每个元素(即一组二进位)转成十进制数,再强制转换成char类型
        char[] ascii = new char[bin8Str.length];
        for(int i= 0; i< ascii.length; i++) {
            ascii[i] = (char)Integer.parseInt(bin8Str[i], 2);
        }
        return String.valueOf(ascii);
    }

}
打开App,阅读手记
11人推荐
发表评论
随时随地看视频慕课网APP

热门评论

总算找到一个不自相矛盾的代码

查看全部评论