web-dev-qa-db-ja.com

Sun.misc.BASE64からJava 8 Java.util.Base64への移行

質問

Java 8 Java.util.Base64 MIMEエンコーダーおよびデコーダードロップインの置換は、サポートされていない内部Java API Sun.misc.BASE64EncoderおよびSun.misc.BASE64Decoder

これまでの考えと理由

私の調査と迅速なテストに基づいて(以下のコードを参照)それはドロップイン置換である必要があります

  • Sun.misc.BASE64Encoderは、そのJavaDocに基づいてRFC1521で指定されているBASE64文字エンコーダーです。このRFCは[〜#〜] mime [〜#〜]仕様の一部です...
  • Java.util.Base64JavaDocに基づいてRFC 2045の表1で指定されている「The Base64 Alphabet」をエンコードおよびデコード操作に使用します。 。[〜#〜] mime [〜#〜]の下

RFC 1521および2045に大きな変更がないと想定し(何も見つかりませんでした)、Java 8 Base64 MIMEエンコーダー/デコーダーを使用したクイックテストに基づいて問題ありません。

探しているもの

  • 「ドロップイン置換」ポイントを確認または反証する信頼できるソースOR
  • java.util.Base64がSun.misc.BASE64Encoder OpenJDK Java 8 implementation(8u40-b25) (BASE64Decoder)と異なる動作をする場合を示す反例
  • 上記の質問に答えると思うものは何でも間違いなく

参考のために

私のテストコード

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");
    }
}
28
Ivo Mori

エンコードされた文字列の違いを示す小さなテストプログラムを次に示します。

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のエンコードされた出力には、最後に改行があることに注意してください。 alwaysは常に改行を追加しませんが、エンコードされた文字列の最後の行に正確に76文字が含まれている場合は追加します。 (Java.util.Base64の作成者は、これをSun.misc.BASE64Encoder実装の小さなバグであると見なしました。 レビュースレッド を参照してください)。

これはささいなことのように思えるかもしれませんが、この特定の動作に依存するプログラムがある場合、エンコーダーを切り替えると出力が不正になる可能性があります。したがって、Java.util.Base64notであり、Sun.misc.BASE64Encoderのドロップイン置換ではないと結論付けます。

もちろん、Java.util.Base64intentは、コードの移行をサポートすることを目的とした、機能的に同等で、RFC準拠、高性能、完全にサポートされ、指定された代替品であることですSun.misc.BASE64Encoderから離れています。ただし、移行時には、このようなEdgeのいくつかのケースに注意する必要があります。

46
Stuart Marks

Rfc1521とrfc2045の間でbase64仕様に変更はありません。

すべてのbase64実装は、お互いのドロップイン置換と見なすことができます。base64実装間の唯一の違いは次のとおりです。

  1. 使用されるアルファベット。
  2. aPIが提供されています(たとえば、一部は完全な入力バッファーでのみ動作する場合がありますが、その他は有限状態マシンであり、完了するまで入力チャンクをプッシュし続けることができます)。

MIME base64アルファベットは、RFCバージョン間で一定のままであり(それhasまたは古いソフトウェアが壊れます)、次のとおりです:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/

Wikipedia notesのように、base64実装間で変更できるのは最後の2文字のみです。

doesが最後の2文字を変更するbase64実装の例として、 IMAP MUTF-7 仕様は次のbase64を使用しますアルファベット:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+,

変更の理由は、/文字がパス区切り文字としてよく使用され、非ASCIIディレクトリパスをASCIIにフラット化するためにMUTF-7エンコードが使用されるため、/文字がエンコードされたセグメントでは回避されます。

3
jstedfast

SunからJava.util.base64に移動したときに同じ問題が発生しましたが、org.Apache.commons.codec.binary.Base64は問題によって解決しました

2
user369151

両方のエンコーダにバグがないと仮定すると、RFCでは、0バイト、1バイト、2バイト、および3バイトのシーケンスごとに個別のエンコーディングが必要です。長いシーケンスは、必要な数の3バイトシーケンスに分割され、最後にシーケンスが続きます。したがって、2つの実装が16,843,009(1 + 256 + 65536 + 16777216)のすべての可能なシーケンスを正しく処理する場合、2つの実装も同一です。

これらのテストの実行には数分しかかかりません。テストコードをわずかに変更することで、これを実行し、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));
        }

    }
}
1
Simon G.