仕事でJDK 8使ってますが、そのうちやるであろうJDK 11化に向けて、既に使えなくなっているsun.misc.BASE64Encoder
をjava.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の文字コード表です。RFC4648
とRFC2045
の表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に向き合う良い機会でした。