编程知识 | 实现 Base64 编解码

1,516 阅读5分钟

编程知识.png

相关推荐:

0. Thanks

1. 概述

  • 在邮件时代,人们发的邮件中,会有一些不可见的字符,如换行符,制表符等等,在邮件传输过程中,一些 路由器或者是一些DNS会把这些不可见的字符给处理掉,到达收件人手术就是一堆乱码了。于是人们想,只要 把这些不可见不可打印的字符给映射成可见字符不就可以了吗?

  • 于是就有个这个Base64算法。严格来说,Base64不是一个加密的算法,它只是把二进制数据映射成64个可打印的字符而已,虽然非明文,但是根据其编码方式还是可以还原数据,因此它只算是一种编码方式。

2. 编码

  • 64个可打印的字符,大写A-Z,小写a-z,数字0-9,一共是26+26+10=62,加上+/来凑数,凑够64。
编号字符编号字符编号字符编号字符
00A16Q32g48w
01B17R33h49x
02C18S34i50y
03D19T35j51z
04E20U36k520
05F21V37l531
06G22W38m542
07H23X39n553
08I24Y40o564
09J25Z41p575
10K26a42q586
11L27b43r597
12M28c44s608
13N29d45t619
14O30e46u62+
15P31f47v63/
  • 我们知道,网络中所有的数据都是二进制进行传输,那么给出一段数据,需要我们转换成如上的64个中的一个, 由于2^6=64,所以我们把二级制数据中,每6位取出,映射到上面的表中,这样不断下去,就能得到我们的 base64编码。

  • 而,计算机中,每8个bit为一个Byte(字节),所以3个字节有24个bit刚好可以映射出4个 base64编码。咦,那样来说,如果我一个文件本来的就是3个字节,而用了base64编码后,不就 变成了4个字符,一个字符8个bit,也就是32个bit,由原来的24bit->32bit,数据量变大了! 是的没错,大了1/3。所以,使用base64后,其大小是原来的4/3。

  • 在实际编码中,我们的文本中的字符个数不一定是8的个数,总的bit数也不一定是6的倍数。所以 有以下的约定:

  • 文本结尾,剩余2个字符:2*8=16bit,16%6=4,余4个二进制位就补一个=

  • 文本结尾,剩余1个字符:1*8=8bit,8%6=2,余2个二进制位就补2个=

示例:

  • 对字符个数是3的倍数(N*8%6=0)的字符串进行Base64编码
原文:         p           h         p
对应ASCII: 01110000   01101000  011100006位分割:  011100  000110  100001 110000
对应10进制值:  28      6       33     48
Base64对应的字符:c     G        h      w
  • 对字符个数为N*8%6=2的字符串,即剩余2个字符,进行base64编码
原文:        r        a         n       l
ASCII值:01110010 01100001 01101110 011011006位分割:011100  100110 000101 101110  011011  000000  xxxxxx  xxxxxx
对应10进制值:28      38    5       46    27      0         =       =
对应base64字符: c    m     F       u     b       A         =      =
  • 对字符个数为N*8%6=4的字符串,即剩余1个字符,进行base64编码
原文           h          a         c         k          e
ASCII值:    01101000  01100001  01100011  01101011   011001016位分割:  011010  000110  000101  100011  011010  110110  0101 00    xxxxxx
对应10进制值: 26       6      5       35      26      54       20         =
对应base64字符: a      G      F        j       a       2        U         =

3. 解码

知道了编码的原理,解码就很容易了。是其逆过程。 大概是:

  • 根据编码表,找出对应的10进制编号,
  • 然后对编号进行2进制化,
  • 把所有的2进制进行每八位拼接,得到ASCII值,即可。

4. Talk is cheap show me the code!

  • 编码,知道了原理,编码就很容易实现,如下(Java版):
private static final char[] baseChars = {
        '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', '+', '/',
};
    /**
     * Base64编码
     * @param s 字符串
     * @return 结果
     */
    public static String enCode64(String s) {
        int index = 0;
        StringBuilder strBuff = new StringBuilder();
        StringBuilder resultBuff = new StringBuilder();
        while (true) {
            //是否结束
            if (index == -1) {
                switch (s.length()*8%6) {
                    case 2:
                        resultBuff.append('=').append('=');
                        break;
                    case 4:
                        resultBuff.append('=');
                        break;
                }
                break;
            }
            //判断是否需要补零
            if (index>=s.length()) {
                int zeroCount = 6 - strBuff.length();
                for (int i = 0; i < zeroCount; i++) {
                    strBuff.append('0');
                }
                index = -1;
            }
            //更新二进制缓冲区
            if (strBuff.length()<6) {
                strBuff.append(getBitStr(s.charAt(index++)));
            }
            //从缓冲区取6个字符出来
            String temp2 = strBuff.substring(0,6);
            int temp10 = Integer.parseInt(temp2, 2);
            resultBuff.append(baseChars[temp10]);
            strBuff.delete(0, 6);
        }
        return resultBuff.toString();
    }

    /**
     * 获取对应字符的ASCII码的二级制序列
     *  八位,不足前面补零
     * @param i 字符
     * @return 二进制序列
     */
    public static String getBitStr(char i) {
        int ascii = (int) i;
        //其中0表示补零而不是补空格,8表示至少8位
        StringBuilder s = new StringBuilder(Integer.toBinaryString(ascii));
        if (s.length()<8) {
            int zeroCount = 8 - s.length();
            for (int j = 0; j < zeroCount; j++) {
                s.insert(0, '0');
            }
        }
        return s.toString();
    }

这个可以直接拿去用,我都测试过了。在Java平台上,一般是使用sun的包去做base64的编解码,如下:

    public static String encode(byte[] data){
        return new sun.misc.BASE64Encoder().encode(data);
    }
  • 解码,解码就是逆过程
    /**
     * Base64解码
     * @param s 编码好的base64
     * @return 结果
     */
    public static String decode(String s) {
        int index = 0;
        StringBuilder strBuff = new StringBuilder();
        StringBuilder resultBuff = new StringBuilder();
        while (true) {
            //补零
            if (index==s.length() || s.charAt(index)=='=') {
                int zeroCount = 8 - strBuff.length();
                for (int i = 0; i < zeroCount; i++) {
                    strBuff.append('0');
                }
                index = -1;
            }
            //更新缓冲区字符
            while (index!=-1 && strBuff.length()<8 && index<s.length()) {
                int chatIndex = getCharIndexInBaseChars(s.charAt(index++));
                strBuff.append(get6BitStr(chatIndex));
            }
            //从缓冲区取8个字符
            String temp2 = strBuff.substring(0,8);
            int temp10 = Integer.valueOf(temp2,2);
            resultBuff.append((char)temp10);
            strBuff.delete(0, 8);
            //判断是否结束
            if (index==-1)
                break;
        }
        return resultBuff.toString().trim();
    }
    /**
     * 得到某个字符,在编码表的位置
     * @param i 字符
     * @return 编号
     */
    private static int getCharIndexInBaseChars(char i) {
        int ascii = (int) i;
        if (ascii == 43) //'+'
            return 62;
        else if (ascii == 47) //'/'
            return 63;
        else if (ascii>=48 && ascii<=57) // 0-9
            return ascii+4;
        else if (ascii>=65 && ascii<=90) // A-Z
            return ascii-65;
        else if (ascii>=97 && ascii<=122) // a-z
            return ascii-71;
        else
            return -1;
    }
    private static String get6BitStr(int ascii) {
        StringBuilder s = new StringBuilder(Integer.toBinaryString(ascii));
        if (s.length()<6) {
            int zeroCount = 6 - s.length();
            for (int j = 0; j < zeroCount; j++) {
                s.insert(0, '0');
            }
        }
        return s.toString();
    }
  • 而一般Java上的解码也是通过sun包:
public static byte[] decode(String str) {
        byte[] bt = null;
        try {
            sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();
            bt = decoder.decodeBuffer(str);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bt;
    }

完整的代码在这里


码字不易,方便的话素质三连,或者关注我的公众号 技术酱,专注 Android 技术,不定时推送新鲜文章,如果你有好的文章想和大家分享,欢迎关注投稿!

技术酱