Java 8 java.util.Base64 是 sun.misc.BASE64 的替代品吗?

Is Java 8 java.util.Base64 a drop-in replacement for sun.misc.BASE64?

问题

Java8java.util.Base64 MIME 编码器和解码器是否为不受支持的 , 内部 Java API sun.misc.BASE64Encodersun.misc.BASE64Decoder?

编辑(澄清):通过直接替换 我的意思是我可以将使用 sun.misc.BASE64Encodersun.misc.BASE64Decoder 的旧代码透明地切换到 Java 8 MIME Base64 Encoder/Decoder 以用于任何现有的其他客户端代码。

到目前为止我的想法和原因

根据我的调查和快速测试(见下面的代码)它应该是一个替代品 因为

假设 RFC 1521 和 2045 没有重大变化(我找不到任何变化)并且基于我使用 Java 8 Base64 MIME Encoder/Decoder 的快速测试应该没问题。

我在找什么

  • 权威来源确认或反驳“直接替换”点或
  • 一个反例,显示 java.util.Base64 与 sun.misc.BASE64Encoder OpenJDK Java 8 implementation (8u40-b25) (BASE64Decoder) OR
  • 具有不同行为的情况
  • 随便你怎么想就回答上面的问题绝对

供参考

我的测试代码

public class Base64EncodingDecodingRoundTripTest {

    public static void main(String[] args) throws IOException {
        String test1 = " ~!@#$%^& *()_+=`| }{[]\;: \"?><,./ ";
        String test2 = test1 + test1;

        encodeDecode(test1);
        encodeDecode(test2);
    }

    static void encodeDecode(final String testInputString) throws IOException {
        sun.misc.BASE64Encoder unsupportedEncoder = new sun.misc.BASE64Encoder();
        sun.misc.BASE64Decoder unsupportedDecoder = new sun.misc.BASE64Decoder();

        Base64.Encoder mimeEncoder = java.util.Base64.getMimeEncoder();
        Base64.Decoder mimeDecoder = java.util.Base64.getMimeDecoder();

        String sunEncoded = unsupportedEncoder.encode(testInputString.getBytes());
        System.out.println("sun.misc encoded: " + sunEncoded);

        String mimeEncoded = mimeEncoder.encodeToString(testInputString.getBytes());
        System.out.println("Java 8 Base64 MIME encoded: " + mimeEncoded);

        byte[] mimeDecoded = mimeDecoder.decode(sunEncoded);
        String mimeDecodedString = new String(mimeDecoded, Charset.forName("UTF-8"));

        byte[] sunDecoded = unsupportedDecoder.decodeBuffer(mimeEncoded); // throws IOException
        String sunDecodedString = new String(sunDecoded, Charset.forName("UTF-8"));

        System.out.println(String.format("sun.misc decoded: %s | Java 8 Base64 decoded:  %s", sunDecodedString, mimeDecodedString));

        System.out.println("Decoded results are both equal: " + Objects.equals(sunDecodedString, mimeDecodedString));
        System.out.println("Mime decoded result is equal to test input string: " + Objects.equals(testInputString, mimeDecodedString));
        System.out.println("\n");
    }
}

rfc1521 和 rfc2045 之间的 base64 规范没有变化。

所有 base64 实现都可以被认为是彼此的直接替换,base64 实现之间的唯一区别是:

  1. 使用的字母表。
  2. API 提供(例如,有些可能只对完整的输入缓冲区采取行动,而其他可能是有限状态机,允许您继续通过它们推送输入块,直到完成) .

MIME base64 字母表在 RFC 版本之间保持不变(它 或旧软件会损坏)并且是:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/

Wikipedia 所述,只有最后 2 个字符在 base64 实现之间可能会发生变化。

作为 更改最后 2 个字符的 base64 实现示例,IMAP MUTF-7 规范使用以下 base64 字母表:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+,

更改的原因是 / 字符经常用作路径分隔符,并且由于 MUTF-7 编码用于将非 ASCII 目录路径扁平化为 ASCII,因此 / 编码段中需要避免字符。

假设两个编码器都没有错误,那么 RFC 要求对每个 0 字节、1 字节、2 字节和 3 字节序列进行不同的编码。较长的序列根据需要被分解成尽可能多的 3 字节序列,然后是最终序列。因此,如果这两个实现正确处理了所有 16,843,009 (1+256+65536+16777216) 个可能的序列,那么这两个实现也是相同的。

这些测试只需要几分钟 运行。通过稍微更改您的测试代码,我已经做到了,并且我的 Java 8 安装通过了所有测试。因此,public 实现可用于安全地替换 sun.misc 实现。

这是我的测试代码:

import java.util.Base64;
import java.util.Arrays;
import java.io.IOException;

public class Base64EncodingDecodingRoundTripTest {

    public static void main(String[] args) throws IOException {
        System.out.println("Testing zero byte encoding");
        encodeDecode(new byte[0]);

        System.out.println("Testing single byte encodings");
        byte[] test = new byte[1];
        for(int i=0;i<256;i++) {
            test[0] = (byte) i;
            encodeDecode(test);
        }
        System.out.println("Testing double byte encodings");
        test = new byte[2];
        for(int i=0;i<65536;i++) {
            test[0] = (byte) i;
            test[1] = (byte) (i >>> 8);
            encodeDecode(test);
        }
        System.out.println("Testing triple byte encodings");
        test = new byte[3];
        for(int i=0;i<16777216;i++) {
            test[0] = (byte) i;
            test[1] = (byte) (i >>> 8);
            test[2] = (byte) (i >>> 16);
            encodeDecode(test);
        }
        System.out.println("All tests passed");
    }

    static void encodeDecode(final byte[] testInput) throws IOException {
        sun.misc.BASE64Encoder unsupportedEncoder = new sun.misc.BASE64Encoder();
        sun.misc.BASE64Decoder unsupportedDecoder = new sun.misc.BASE64Decoder();

        Base64.Encoder mimeEncoder = java.util.Base64.getMimeEncoder();
        Base64.Decoder mimeDecoder = java.util.Base64.getMimeDecoder();

        String sunEncoded = unsupportedEncoder.encode(testInput);
        String mimeEncoded = mimeEncoder.encodeToString(testInput);

        // check encodings equal
        if( ! sunEncoded.equals(mimeEncoded) ) {
            throw new IOException("Input "+Arrays.toString(testInput)+" produced different encodings (sun=\""+sunEncoded+"\", mime=\""+mimeEncoded+"\")");
        }

        // Check cross decodes are equal. Note encoded forms are identical
        byte[] mimeDecoded = mimeDecoder.decode(sunEncoded);
        byte[] sunDecoded = unsupportedDecoder.decodeBuffer(mimeEncoded); // throws IOException
        if(! Arrays.equals(mimeDecoded,sunDecoded) ) {
            throw new IOException("Input "+Arrays.toString(testInput)+" was encoded as \""+sunEncoded+"\", but decoded as sun="+Arrays.toString(sunDecoded)+" and mime="+Arrays.toString(mimeDecoded));
        }

    }
}

这是一个小测试程序,它说明了编码字符串的差异:

byte[] bytes = new byte[57];
String enc1 = new sun.misc.BASE64Encoder().encode(bytes);
String enc2 = new String(java.util.Base64.getMimeEncoder().encode(bytes),
                         StandardCharsets.UTF_8);

System.out.println("enc1 = <" + enc1 + ">");
System.out.println("enc2 = <" + enc2 + ">");
System.out.println(enc1.equals(enc2));

它的输出是:

enc1 = <AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
>
enc2 = <AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA>
false

请注意,sun.misc.BASE64Encoder 的编码输出在末尾有一个换行符。它不会总是追加换行符,但如果编码字符串的最后一行刚好有 76 个字符,它就会这样做。 (java.util.Base64 的作者认为这是 sun.misc.BASE64Encoder 实现中的一个小错误——参见 review thread)。

这看似微不足道,但如果您的程序依赖于此特定行为,则切换编码器可能会导致格式错误的输出。因此,我得出结论 java.util.Base64 而不是 sun.misc.BASE64Encoder.

的替代品

当然,java.util.Base64 意图 是功能等效、符合 RFC、高性能、完全支持和指定的替代品,旨在支持从 sun.misc.BASE64Encoder 迁移代码。不过,您在迁移时需要注意一些像这样的边缘情况。

我遇到了同样的问题,当我从 sun 移动到 java.util.base64 时,但是 org.apache.commons.codec.binary.Base64 解决了我的问题