仕事でJDK 8使ってますが、そのうちやるであろうJDK 11化に向けて、既に使えなくなっているsun.misc.BASE64Encoderjava.util.Base64に置き換える作業した時のメモ。

環境

$ java -version
openjdk version "1.8.0_181-1-redhat"
OpenJDK Runtime Environment (build 1.8.0_181-1-redhat-b13)
OpenJDK 64-Bit Server VM (build 25.181-b13, mixed mode)

今までの実装

String str = "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめも";

String result = new sun.misc.BASE64Encoder().encode(str.getBytes());

String originalVal = new String(new sun.misc.BASE64Decoder().decodeBuffer(result));

ずっと無視してきましたが警告出てましたね。Java 9より前のJavaは内部クラスを隠蔽できなくて不自由な言語でしたね。

Base64test.java:5: 警告: BASE64Encoderは内部所有のAPIであり、今後のリリースで削除される可能性があります
        String result = new sun.misc.BASE64Encoder().encode(str.getBytes());
                                    ^
Base64test.java:7: 警告: BASE64Decoderは内部所有のAPIであり、今後のリリースで削除される可能性があります
        String originalVal = new String(new sun.misc.BASE64Decoder().decodeBuffe
r(result));
                                                    ^
警告2個

最初の失敗

メソッド名からしてこれに違いないと、超適当にBase64.getEncoder().encodeToStringしてみました。

String str = "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめも";
String result = Base64.getEncoder().encodeToString(str.getBytes("UTF-8"));
System.out.println(result);

<出力結果>
44GC44GE44GG44GI44GK44GL44GN44GP44GR44GT44GV44GX44GZ44Gb44Gd44Gf44Gh44Gk44Gm44Go44Gq44Gr44Gs44Gt44Gu44Gv44Gy44G144G444G744G+44G/44KA44KB44KC

動いた動いたと喜んでましたが、sun.misc.BASE64Encoderでエンコードしたデータをjava.util.Base64.getDecoder()で読み込んでみると例外発生。

Exception in thread "main" java.lang.IllegalArgumentException: Illegal base64 character d
	at java.util.Base64$Decoder.decode0(Base64.java:714)
	at java.util.Base64$Decoder.decode(Base64.java:526)
	at java.util.Base64$Decoder.decode(Base64.java:549)
	at Scratch.main(scratch.java:9)

Process finished with exit code 1

ここで真面目にドキュメントを見始めました。

https://docs.oracle.com/javase/jp/8/docs/api/java/util/Base64.html


上記URLから引用。

このクラスは、Base64エンコーディング・スキーム用のエンコーダおよびデコーダを取得するためのstaticメソッドのみで構成されています。このクラスの実装では、RFC4648、及びRFC2045で規定された次のタイプのBase64をサポートしています。

基本

エンコードおよびデコード操作に、RFC 4648およびRFC 2045の表1に明記された「Base64アルファベット」を使用します。エンコーダは、改行文字(行区切り文字)を追加しません。デコーダは、base64アルファベットの範囲外の文字を含むデータを拒否します。

URLおよびファイル名で安全

エンコードおよびデコード操作に、RFC 4648の表2に明記された「URLおよびファイル名で安全なBase64アルファベット」を使用します。エンコーダは、改行文字(行区切り文字)を追加しません。デコーダは、base64アルファベットの範囲外の文字を含むデータを拒否します。

MIME

エンコードおよびデコード操作に、RFC 2045の表1に明記された「Base64アルファベット」を使用します。エンコードされた出力は、それぞれが76文字以下からなる行で表現する必要があって、キャリッジ・リターン「\r」の直後に改行「\n」を続けたものが行区切り文字として使用されます。エンコードされた出力の末尾に行区切り文字は追加されません。デコード操作では、base64アルファベット表で見つからない行区切り文字またはその他の文字はすべて無視されます。

引用ここまで。


sun.misc.BASE64Encoderでエンコードすると改行コードが入る場合もあるんですが、基本のgetDecoder()は改行が来ない前提なので\r(x0d)が不正ですと怒られてしまったようです。
今までのsun.misc.BASE64Encoder基本 の動きは違うようです。
ちなみにURLおよびファイル名で安全はBase64にエンコードした文字列をファイル名として使えるように +/ の代わりに -_ を使ってエンコードするようですが、やはり改行コードも使えないしこれも違うようです。
消去法でMIMEを使うしかないことが分かりました。

・RFC 4648

                      Table 1: The Base 64 Alphabet

     Value Encoding  Value Encoding  Value Encoding  Value Encoding
         0 A            17 R            34 i            51 z
         1 B            18 S            35 j            52 0
         2 C            19 T            36 k            53 1
         3 D            20 U            37 l            54 2
         4 E            21 V            38 m            55 3
         5 F            22 W            39 n            56 4
         6 G            23 X            40 o            57 5
         7 H            24 Y            41 p            58 6
         8 I            25 Z            42 q            59 7
         9 J            26 a            43 r            60 8
        10 K            27 b            44 s            61 9
        11 L            28 c            45 t            62 +
        12 M            29 d            46 u            63 /
        13 N            30 e            47 v
        14 O            31 f            48 w         (pad) =
        15 P            32 g            49 x
        16 Q            33 h            50 y


         Table 2: The "URL and Filename safe" Base 64 Alphabet

     Value Encoding  Value Encoding  Value Encoding  Value Encoding
         0 A            17 R            34 i            51 z
         1 B            18 S            35 j            52 0
         2 C            19 T            36 k            53 1
         3 D            20 U            37 l            54 2
         4 E            21 V            38 m            55 3
         5 F            22 W            39 n            56 4
         6 G            23 X            40 o            57 5
         7 H            24 Y            41 p            58 6
         8 I            25 Z            42 q            59 7
         9 J            26 a            43 r            60 8
        10 K            27 b            44 s            61 9
        11 L            28 c            45 t            62 - (minus)
        12 M            29 d            46 u            63 _
        13 N            30 e            47 v           (underline)
        14 O            31 f            48 w
        15 P            32 g            49 x
        16 Q            33 h            50 y         (pad) =

・RFC 2045

                    Table 1: The Base64 Alphabet

     Value Encoding  Value Encoding  Value Encoding  Value Encoding
         0 A            17 R            34 i            51 z
         1 B            18 S            35 j            52 0
         2 C            19 T            36 k            53 1
         3 D            20 U            37 l            54 2
         4 E            21 V            38 m            55 3
         5 F            22 W            39 n            56 4
         6 G            23 X            40 o            57 5
         7 H            24 Y            41 p            58 6
         8 I            25 Z            42 q            59 7
         9 J            26 a            43 r            60 8
        10 K            27 b            44 s            61 9
        11 L            28 c            45 t            62 +
        12 M            29 d            46 u            63 /
        13 N            30 e            47 v
        14 O            31 f            48 w         (pad) =
        15 P            32 g            49 x
        16 Q            33 h            50 y

ちなみに、RFCの文字コード表です。RFC4648RFC2045の表1は同じ、RFC4648の表2がURLおよびファイル名で安全に対応するようです。

改行コードの違い

java.util.Base64.getMimeEncoder().encodeToString()でエンコードすると改行コードは CR+LF 固定になるのですが、 sun.misc.BASE64Encoder().encodeでは実行するOSによって改行コードは異なっていました。 例えば、WindowsではCR+LFだし、LinuxではLFで(それはそれで使いにくかったのですが)本番環境はLinuxで動いているので困ってしまいました。

この問題はBase64.getMimeEncoder(76, new byte[]{'\n'}).encodeToStringのように改行コードをLFを指定することで解決しました。
ちなみに76というのは何バイトごとに改行を入れるのかの指定で、デフォルトの76バイトを指定していますが、改行コードだけを指定できるようにして欲しかったな。

さいごに

ちゃんとドキュメントを読んで実装した方が結果早かったと思いました。 記事には書いてないですが、Base64がどのようなルールで変換してるかも調べましたし、 今まで適当に使っていたBase64に向き合う良い機会でした。