Java Tips | 基本ユーティリティ:Base64デコード

Java Java
スポンサーリンク

Base64デコードは「文字列を元の世界に連れ戻す」技

Base64エンコードが「バイナリを文字列の世界に連れてくる」技だとしたら、
Base64デコードはその逆で、「Base64文字列から、元のバイト列や文字列の世界に戻す」技です。

ここを雑にやると、「文字化けする」「バイト列の長さが合わない」「そもそも Base64 じゃない文字列で落ちる」といった事故が起きます。
だからこそ、“どこからでも同じルールで Base64 を戻せるユーティリティ” を一つ持っておくと、コードがかなり安定します。


基本の考え方:「バイト列に戻してから、必要なら文字列にする」

Base64 はあくまで「バイト列 ↔ 文字列」の橋渡し

Base64 は、「バイト列」を「ASCIIだけで構成された文字列」に変換する仕組みです。
つまり、デコードするときの本質は「Base64文字列 → バイト列」に戻すことです。

そこから先は用途次第です。

バイナリファイルに戻したいなら、そのバイト列をファイルに書き出す。
テキストに戻したいなら、そのバイト列を「正しい文字コード」で文字列に変換する。

この「まずバイト列に戻す → そこからどう使うかは目的次第」という流れを意識しておくと、迷いにくくなります。


Java標準の Base64 デコードユーティリティを押さえる

java.util.Base64.Decoder をラップする

Java 8 以降では、java.util.Base64 にデコード用の Decoder が用意されています。
これをそのまま使ってもいいのですが、エンコードと同じく「入口を一箇所にまとめる」ためにユーティリティ化しておきます。

import java.nio.charset.StandardCharsets;
import java.util.Base64;

public final class Base64s {

    private Base64s() {}

    public static byte[] decode(String base64) {
        if (base64 == null) {
            return null;
        }
        return Base64.getDecoder().decode(base64);
    }

    public static String decodeUtf8(String base64) {
        byte[] bytes = decode(base64);
        if (bytes == null) {
            return null;
        }
        return new String(bytes, StandardCharsets.UTF_8);
    }
}
Java

ここで深掘りしたい重要ポイントは二つです。

一つ目は、「Base64デコードの“本体”は byte[] を返すメソッドにしておく」ことです。
decode は「Base64文字列 → byte[]」だけを担当し、
decodeUtf8 は「その byte[] を UTF-8 文字列として解釈する」という“用途別のラッパー”になっています。

二つ目は、「文字コードを必ず明示する」ことです。
new String(bytes) と引数なしで書くと、環境依存のデフォルトエンコーディングになってしまいます。
エンコード側で UTF-8 を使っているなら、デコード側も必ず UTF-8 を指定してペアにしておく必要があります。


例題:文字列を Base64 → 元の文字列に戻す

エンコードとデコードがペアになっていることを確認する

まずは、前回の Base64エンコードとセットで「往復」させてみます。

public class Base64RoundTripExample {

    public static void main(String[] args) {
        String original = "こんにちは、Base64!";

        String encoded = Base64s.encodeUtf8(original);
        String decoded = Base64s.decodeUtf8(encoded);

        System.out.println("original = " + original);
        System.out.println("encoded  = " + encoded);
        System.out.println("decoded  = " + decoded);
    }
}
Java

期待するのは、「original と decoded が完全に一致する」ことです。

ここでの重要ポイントは、「エンコード側とデコード側で“文字コードと方式がペアになっている”ことを確認する」ことです。
エンコード時に UTF-8 でバイト列にしているなら、デコード時も UTF-8 で文字列に戻す。
標準 Base64 を使っているなら、デコード側も標準 Base64 の getDecoder() を使う。

このペアが崩れると、文字化けやデコードエラーの原因になります。


例題:Base64文字列からファイルを復元する

JSONや設定に埋め込んだバイナリを元に戻す

よくあるパターンとして、「画像ファイルを Base64 にして JSON に載せたものを、サーバ側で受け取ってファイルに戻す」というケースがあります。

例えば、クライアントからこんな JSON が来たとします。

{
  "fileName": "logo.png",
  "contentBase64": "iVBORw0KGgoAAAANSUhEUgAA..."
}

これをファイルに復元するコードはこう書けます。

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public final class FileBase64 {

    private FileBase64() {}

    public static void decodeToFile(String base64, Path outputPath) throws IOException {
        byte[] bytes = Base64s.decode(base64);
        Files.write(outputPath, bytes);
    }
}
Java

使い方はこうです。

String base64 = /* JSON から取り出した contentBase64 */;
Path output = Path.of("restored-logo.png");

FileBase64.decodeToFile(base64, output);
Java

ここで深掘りしたいのは、「Base64デコードの結果は“そのままファイルに書ける生のバイト列”である」という感覚です。
画像でも PDF でも ZIP でも、元が何であれ、Base64 から戻ってくるのは「元のバイト列そのもの」です。

つまり、「Base64 → byte[] → Files.write」で、ほぼそのまま復元できる、というシンプルさを覚えておくと使いやすくなります。


URLセーフ Base64 のデコード

エンコード時と同じ「種類」でデコードする

URL やトークンで使う「URLセーフ Base64」は、+/ の代わりに -_ を使い、パディング = を省略することが多い形式です。
エンコード時に Base64.getUrlEncoder() を使ったなら、デコード時も getUrlDecoder() を使う必要があります。

import java.util.Base64;

public final class Base64s {

    private Base64s() {}

    public static byte[] decodeUrlSafe(String base64) {
        if (base64 == null) {
            return null;
        }
        return Base64.getUrlDecoder().decode(base64);
    }
}
Java

例えば、トークンを復元するコードはこうです。

String token = TokenGenerator.randomToken(32); // URLセーフ Base64 で生成したとする
byte[] raw = Base64s.decodeUrlSafe(token);
System.out.println("raw length = " + raw.length);
Java

ここでの重要ポイントは、「“標準 Base64”と“URLセーフ Base64”は互換ではないので、必ずペアで使う」ことです。
getEncoder() でエンコードしたものは getDecoder() で、
getUrlEncoder() でエンコードしたものは getUrlDecoder() で戻す。

ここを混ぜると、IllegalArgumentException: Illegal base64 character のような例外が飛びます。


Base64デコードで気をつけたいエラーと設計

不正な Base64 文字列が来たときどうするか

Base64.getDecoder().decode(...) は、不正な文字列が渡されると IllegalArgumentException を投げます。
例えば、Base64 に使われない文字(日本語やスペースなど)が混ざっている場合です。

ユーティリティ側でそのまま例外を投げる設計にしておくと、呼び出し側で「入力が壊れている」ことに気づきやすくなります。

public static byte[] decode(String base64) {
    if (base64 == null) {
        return null;
    }
    try {
        return Base64.getDecoder().decode(base64);
    } catch (IllegalArgumentException e) {
        throw new IllegalArgumentException("不正な Base64 文字列です: " + base64, e);
    }
}
Java

呼び出し側では、例えばこう扱います。

try {
    byte[] data = Base64s.decode(inputBase64);
    // 正常処理
} catch (IllegalArgumentException e) {
    // 400 Bad Request を返す、ログに入力を出す(マスクしつつ)、など
}
Java

ここでのポイントは、「“壊れた Base64”を無理やり扱おうとしない」ことです。
「とりあえず null を返す」「空配列を返す」といった実装にすると、
どこで入力が壊れているのか分からなくなり、調査がつらくなります。


Base64デコードと文字コードのペアリング

「エンコード時の文字コード」を必ず意識する

文字列を Base64 にしていた場合、デコードして文字列に戻すときには、
「エンコード時にどの文字コードでバイト列にしたか」を必ず思い出す必要があります。

例えば、エンコード側がこうだったとします。

byte[] bytes = text.getBytes(StandardCharsets.UTF_8);
String base64 = Base64.getEncoder().encodeToString(bytes);
Java

この場合、デコード側は必ずこうでなければいけません。

byte[] bytes = Base64.getDecoder().decode(base64);
String text = new String(bytes, StandardCharsets.UTF_8);
Java

ここで new String(bytes) としてしまうと、環境によっては Shift_JIS などで解釈され、文字化けします。

つまり、「Base64 は“バイト列をそのまま保存しているだけ”なので、文字列に戻すときの文字コードは自分で合わせる必要がある」という感覚が大事です。


まとめ:Base64デコードユーティリティで身につけたい感覚

Base64デコードは、「よく分からない英数字の塊」を、元の世界(バイト列・文字列・ファイル)に連れ戻す技です。

押さえておきたい感覚は、まず「Base64デコードの本体は“Base64文字列 → byte[]”であり、そこから先は用途ごとにラッパーを用意する」こと。
次に、「標準 Base64 と URLセーフ Base64 をペアで使い、エンコード時と同じ種類・同じ文字コードでデコードする」こと。
そして、「不正な Base64 文字列は例外として扱い、“入力が壊れている”というシグナルをきちんと呼び出し側に伝える」ことです。

タイトルとURLをコピーしました