1. 概述

  • 在邮件时代,人们发的邮件中,会有一些不可见的字符,如换行符,制表符等等,在邮件传输过程中,一些 路由器或者是一些DNS会把这些不可见的字符给处理掉,到达收件人手术就是一堆乱码了。于是人们想,只要 把这些不可见不可打印的字符给映射成可见字符不就可以了吗?
  • 于是就有个这个Base64算法。严格来说,Base64不是一个加密的算法,它只是把二进制数据映射成64个可打印的字符而已,虽然非明文,但是根据其编码方式还是可以还原数据,因此它只算是一种编码方式。

2. 编码

  • 64个可打印的字符,大写A-Z,小写a-z,数字0-9,一共是26+26+10=62,加上+/来凑数,凑够64。

编号

字符

编号

字符

编号

字符

编号

字符

00

A

16

Q

32

g

48

w

01

B

17

R

33

h

49

x

02

C

18

S

34

i

50

y

03

D

19

T

35

j

51

z

04

E

20

U

36

k

52

0

05

F

21

V

37

l

53

1

06

G

22

W

38

m

54

2

07

H

23

X

39

n

55

3

08

I

24

Y

40

o

56

4

09

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

/

  • 我们知道,网络中所有的数据都是二进制进行传输,那么给出一段数据,需要我们转换成如上的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  01110000
每6位分割:  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 01101100
每6位分割: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   01100101
每6位分割:  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;
    }
复制代码