技术频道


Base64 与 URLEncode 简介

Base64 简介


Base64 是最常见的一种基于 64 个可打印字符来表示二进制数据的方法


Base64 原理

  • 首先, Base64 基于 64 个可打印字符, 这 64 个字符有 A~Z, a~z, 0~9, +, /
  • ['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/']
    
  • 然后, 对二进制数据进行处理, 每 3 个字节一组, 一共是 3x8=24bit, 划为 4 组, 每组正好 6 个bit:

    这样我们得到 4 个数字作为索引, 然后查表, 获得相应的 4 个字符, 就是编码后的字符串。

    Base64 编码会把 3 字节的二进制数据编码为 4 字节的文本数据, 长度增加 33%, 好处是编码后的文本数据可以在邮件正文、网页等直接显示。

  • 另外, 如果要编码的二进制数据不是3的倍数, 最后会剩下1个或2个字节, Base64会先用 \x00 在末尾补足后, 再在编码的末尾加上 1 个或 2 个 =, 表示补了多少字节, 解码的时候, 会自动去掉。

  • 由于 = 在 URL、Cookie 里面会造成歧义, 所以, 很多 Base64 编码后会把 = 去掉。因为 Base64 是把3个字节变为4个字节, 所以, Base64 编码的长度永远是 4 的倍数, 因此, 加上 = 把 Base64 字符串的长度变为 4 的倍数,就可以正常解码了

Base64 字母索引表


如果要编码的字节数不能被3整除,最后会多出1个或2个字节,那么可以使用下面的方法进行处理:先使用0字节值在末尾补足,使其能够被3整除,然后再进行Base64的编码。在编码后的Base64文本后加上一个或两个=号,代表补足的字节数。也就是说,当最后剩余两个八位(待补足)字节(2个byte)时,最后一个6位的Base64字节块有四位是0值,最后附加上两个等号;如果最后剩余一个八位(待补足)字节(1个byte)时,最后一个6位的base字节块有两位是0值,最后附加一个等号。
数值 字符   数值 字符   数值 字符   数值 字符
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 /

URL Safe 的 Base64 编码


由于标准的 Base64 编码后可能出现字符 +/, 在URL中就不能直接作为参数, 所以又有一种 url safebase64 编码, 其实就是把字符 +/ 分别变成 -_

        // 下面这段代码来自于JDK1.8中的 java.util.Base64

        /**
         * This array is a lookup table that translates 6-bit positive integer
         * index values into their "Base64 Alphabet" equivalents as specified
         * in "Table 1: The Base64 Alphabet" of RFC 2045 (and RFC 4648).
         */
        private static final char[] toBase64 = {
            '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', '+', '/'
        };

        /**
         * It's the lookup table for "URL and Filename safe Base64" as specified
         * in Table 2 of the RFC 4648, with the '+' and '/' changed to '-' and
         * '_'. This table is used when BASE64_URL is specified.
         */
        private static final char[] toBase64URL = {
            '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 工具类


  • 在 Guava 中有 BaseEncoding
  • 在 JDK8 中有专门的工具类 java.util.Base64
  • 在 JDK7 中也有 sun.misc.BASE64Encodersun.misc.BASE64Decoder 两个类
  • 在 Spring 中, 也提供了一个 Base64Utils, 它自动根据反射来决定是使用 Java 8 的 java.util.Base64 还是 Apache Commons Codecorg.apache.commons.codec.binary.Base64
  • 除了 JDK7, 其他的工具类中都有 url safeBase64 编码方法, 而且 JDK7 中会产生换行符!

    // guava 工具类的使用
    public void testBase64() {
        // 原串
        String origin = "abc";
        // encode
        String encodeString = BaseEncoding.base64().encode(origin.getBytes());
        // decode
        String result = new String(BaseEncoding.base64().decode(encodeString));
        // result = origin
        Assert.assertEquals(origin, result);
    }

    // Spring 工具类的使用
    public void testBase64() {
        // 原串
        String origin = "abc";
        // encode
        String encodeString = Base64Utils.encodeToString(origin.getBytes());
        // decode
        String result = new String(Base64Utils.decodeFromString(encodeString));
        // result = origin
        Assert.assertEquals(origin, result);
    }

    // JDK8 工具类的使用
    public void testBase64() {
        // 原串
        String origin = "abc";
        // encode
        String encodeString = Base64.getEncoder().encodeToString(origin.getBytes());
        // decode
        String result = new String(Base64.getDecoder().decode(encodeString.getBytes()));
        // result = origin
        Assert.assertEquals(origin, result);
    }

    // JDK7 工具类的使用(解码时会抛出 IOException)
    public void testBase64() {
        // 原串
        String origin = "abc";

        // encode, 如果原串比较长, 这个方法得到的签名会有换行符, 所以最好不要用JDK7的这个工具
        String encodeString = new BASE64Encoder().encodeBuffer(origin.getBytes());

        // decode
        String result = null;
        try {
            result = new String(new BASE64Decoder().decodeBuffer(encodeString));
        } catch (IOException e) {
            logger.error("Base64解码失败", e);
        }

        // result = origin
        Assert.assertEquals(origin, result);
    }

URLEncode 简介


URLEncoderURLDecoder 用于完成普通字符串和 application/x-www-form-urlencoded MIME 类型的字符串之间的相互转换。

编码规则

  • 字母 (a-z, A-Z), 数字 (0-9), 点(.), 星号(*), 横线(-), 下划线(_)不变
  • 空格( )变为加号 (+)
  • 其他字符变为 %XY 形式, XY 是两位 16 进制数值
  • 在每个 name=value 对之间放置 & 符号(这条规则跟编码没关系)

URLEncode 工具类

JDK 自带了两个工具类 URLEncoderURLDecoder, 下面是用法:

    @Test
    public void testURLEncode() {
        String str = "*. -_~!";
        System.out.println(str);     // *. -_~!
        String encode = URLEncoder.encode(str);
        System.out.println(encode);  // *.+-_%7E%21
        encode = URLEncoder.encode(encode);
        System.out.println(encode);  // *.%2B-_%257E%2521
        // 注意编码两次是不一样的
        String decode = URLDecoder.decode(encode);
        System.out.println(decode);  // *.+-_%7E%21
        decode = URLDecoder.decode(decode);
        System.out.println(decode);  // *. -_~!
    }

Base64 和 URLEncode 扩展阅读: