Миграция с sun.misc.BASE64 на Java 8 java.util.Base64

Вопрос

Являются ли Java 8 java.util.Base64 MIME Encoder and Decoder заменой замены для неподдерживаемого внутреннего API Java sun.misc.BASE64Encoder и sun.misc.BASE64Decoder?

Что я думаю до сих пор и почему

Основываясь на моих исследованиях и быстрых тестах (см. код ниже) , это должна быть замена замены, потому что

  • sun.misc.BASE64Encoder на основе своего JavaDoc - это кодировщик символов BASE64, как указано в RFC1521. Этот RFC является частью спецификации MIME...
  • java.util.Base64 на основе JavaDoc Использует "Алфавит Base64", как указано в таблице 1 RFC 2045 для операции кодирования и декодирования... в MIME

Предполагая каких-либо существенных изменений в RFC 1521 и 2045 (я не смог их найти) и на основе моего быстрого теста с использованием кодировщика/декодера Java 8 Base64 MIME должно быть хорошо.

Что я ищу

  • авторитетный источник, подтверждающий или опровергающий точку "замены замены" OR
  • контрпример, в котором показан случай, когда java.util.Base64 имеет другое поведение, чем sun.misc.BASE64Encoder OpenJDK Java 8 реализация (8u40-b25) (BASE64Decoder) ИЛИ
  • что бы вы ни думали, ответы на вопрос определенно

Для справки

Мой тестовый код

public class Base64EncodingDecodingRoundTripTest {

    public static void main(String[] args) throws IOException {
        String test1 = " [email protected]#$%^& *()_+=`| }{[]\\;: \"?><,./ ";
        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");
    }
}

Ответ 1

Здесь небольшая тестовая программа, которая иллюстрирует разницу в закодированных строках:

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 - см. Раздел обзор).

Это может показаться тривиальностью, но если у вас есть программа, основанная на этом конкретном поведении, коммутаторы могут привести к искажению вывода. Поэтому я заключаю, что java.util.Base64 не заменяемая замена для sun.misc.BASE64Encoder.

Конечно, цель java.util.Base64 заключается в том, что это функционально эквивалентная RFC-совместимая, высокопроизводительная, полностью поддерживаемая и указанная замена, которая предназначена для поддержки переноса кода в сторону от sun.misc.BASE64Encoder. Тем не менее, вы должны знать о некоторых случаях с краем, например, при переносе.

Ответ 2

Никаких изменений в спецификации base64 между rfc1521 и rfc2045 нет.

Все реализации base64 можно рассматривать как замены для замены друг на друга, единственными отличиями между реализациями base64 являются:

  • используемый алфавит.
  • предоставленный API (например, некоторые из них могут действовать только на полный входной буфер, в то время как другие могут быть конечными машинами, позволяющими вам продолжать нажимать куски ввода через них, пока вы не закончите).

Алфавит MIME base64 оставался постоянным между версиями RFC (он должен был или более раннее программное обеспечение сломался) и: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/

Как Wikipedia, только последние 2 символа могут меняться между реализациями base64.

В качестве примера реализации base64, которая меняет последние 2 символа, спецификация IMAP MUTF-7 использует следующий алфавит base64: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+,

Причина изменения заключается в том, что символ / часто используется в качестве разделителя путей, и поскольку кодировка MUTF-7 используется для выравнивания путей каталогов, отличных от ASCII, в ASCII, символ / необходимо избегать в закодированных сегментах.

Ответ 3

Предполагая, что оба кодировщика не содержат ошибок, для RFC требуются разные кодировки для каждой последовательности в 0 байт, 1 байт, 2 байта и 3 байта. Более длинные последовательности разбиваются на столько 3 байтовых последовательностей, сколько необходимо, за ними следует заключительная последовательность. Следовательно, если две реализации корректно обрабатывают все возможные возможные последовательности 16 843 009 (1 + 256 + 65536 + 16777216), то две реализации также идентичны.

Эти тесты занимают всего несколько минут. Немного изменив тестовый код, я сделал это, и моя установка на Java 8 прошла весь тест. Следовательно, публичная реализация может быть использована для безопасной замены реализации 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));
        }

    }
}

Ответ 4

У меня была та же проблема, когда я перешел от солнца к java.util.base64, но org.apache.commons.codec.binary.Base64 это решило проблему