ファイルを選択
  1. base64hub
  2. 各言語での Base64
  3. Kotlin
コード

Kotlin での Base64

Kotlin にはマルチプラットフォーム対応の kotlin.io.encoding.Base64 オブジェクトがあり、Kotlin 2.2 から安定版になりました。JVM では java.util.Base64 も使えます。以下の出力は Java 21 上の Kotlin 2.4.21 のものです。

文字列のエンコードとデコード

Base64.encode は ByteArray を受け取って String を返します。UTF-8 の変換は、encodeToByteArray() と decodeToString() が両方向で担当します。

import kotlin.io.encoding.Base64

val enc = Base64.encode("héllo wörld".encodeToByteArray())
println(enc)                                // aMOpbGxvIHfDtnJsZA==
println(Base64.decode(enc).decodeToString()) // héllo wörld

Base64 自体が標準のインスタンス(Base64.Default)です。ほかに Base64.UrlSafe と Base64.Mime があります。

Kotlin 2.2 より古いバージョンでは、この API に @OptIn(ExperimentalEncodingApi::class) が必要です。古いバージョンから移行できない場合は、Java での Base64 と同じように java.util.Base64 が使えます。

java.util.Base64.getEncoder().encodeToString("hi".toByteArray())  // aGk=

典型的な用途は HTTP ヘッダーと Data URI です。

val token = Base64.encode("user:pass".encodeToByteArray())
println("Authorization: Basic $token")       // Authorization: Basic dXNlcjpwYXNz

val png = byteArrayOf(0x89.toByte(), 0x50, 0x4E, 0x47)
println("data:image/png;base64," + Base64.encode(png))  // data:image/png;base64,iVBORw==

URLセーフな Base64 とパディングのオプション

Base64.UrlSafe は +/ を -_ に置き換えます。パディングは、設定済みの新しいインスタンスを返す withPadding で制御します。

val d = "<<???>>".encodeToByteArray()

println(Base64.UrlSafe.encode(d))                  // PDw_Pz8-Pg==

val urlNoPad = Base64.UrlSafe.withPadding(Base64.PaddingOption.ABSENT)
println(urlNoPad.encode(d))                        // PDw_Pz8-Pg
println(urlNoPad.decode("PDw_Pz8-Pg").decodeToString())  // <<???>>

4 つのオプション:

オプションエンコード時デコード時
PRESENT(デフォルト)= を付ける= を要求
ABSENT= なし= を拒否
PRESENT_OPTIONAL= を付ける両方を受理
ABSENT_OPTIONAL= なし両方を受理

デフォルトのインスタンスはこの点に厳格です。

Base64.UrlSafe.decode("PDw_Pz8-Pg")
// IllegalArgumentException: The padding option is set to PRESENT,
// but the input is not properly padded

JWT のパートやその他のトークンでは、生成側によってパディングの扱いがまちまちなので、たいていは ABSENT_OPTIONAL か PRESENT_OPTIONAL が適切です。背景については Base64 と Base64URL の違い と パディングの解説 をご覧ください。

たとえば JWT のペイロードを読むには次のようにします。

val jwt = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.sig"
val url = Base64.UrlSafe.withPadding(Base64.PaddingOption.ABSENT_OPTIONAL)
println(url.decode(jwt.split(".")[1]).decodeToString())   // {"sub":"1234"}

MIME の折り返し

Base64.Mime は 76 文字の行を \r\n で区切って出力し、デコード時には行区切りを無視します。

Base64.Mime.encode("x".repeat(60).encodeToByteArray())
// "eHh4eHh4...eHh4" (76 chars) + "\r\n" + "eHh4"

Base64.Mime.decode("aGVs\r\nbG8K").decodeToString()  // "hello\n"

比べるには、同じ入力を MIME Base64 エンコーダー に貼り付けてください。

ファイルのエンコードとデコード

小さなファイルはメモリに収まります。

import java.io.File

println(Base64.encode(File("hello.txt").readBytes()))   // aGVsbG8K
File("copy.txt").writeBytes(Base64.decode("aGVsbG8K"))

大きなファイルのストリーム処理

JVM では、encodingWith と decodingWith でストリームをラップします。Kotlin 2.4 でもまだ実験的機能なので、オプトインと個別のインポートが必要です。

import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.io.encoding.decodingWith
import kotlin.io.encoding.encodingWith
import java.io.File

@OptIn(ExperimentalEncodingApi::class)
fun encodeFile(src: File, dst: File) {
    dst.outputStream().encodingWith(Base64).use { out ->
        src.inputStream().use { it.copyTo(out) }
    }
}

@OptIn(ExperimentalEncodingApi::class)
fun decodeFile(src: File, dst: File) {
    src.inputStream().decodingWith(Base64).use { inp ->
        dst.outputStream().use { inp.copyTo(it) }
    }
}

エンコード用のストリームを閉じると、最後のグループとパディングが書き出されます。閉じる処理は use ブロックが代わりに行います。300 KB のテストファイルをストリーム処理した出力は Base64.encode(readBytes()) と同一で、往復変換でも元と同じバイト列が戻りました。実験的でない方法がよければ、java.util.Base64.getEncoder().wrap(outputStream) で同じことができます。

検証と例外メッセージ

Kotlin のデコーダーは Java のものより厳格です。Java を含むほとんどのライブラリが受け付ける 0 でないパディングビットを拒否します。失敗はすべて IllegalArgumentException です。

Base64.decode への入力メッセージ
PDw_Pz8-Pg==Invalid symbol '_'(137) at index 3
"aGVs\nbG8K"Invalid symbol '\n'(12) at index 4
aGk=!!Symbol '!'(41) at index 3 is prohibited after the pad character
aGk==Symbol '='(75) at index 3 is prohibited after the pad character
aGl=The pad bits must be zeros
aInput should have at least 2 symbols for Base64 decoding, startIndex: 0, endIndex: 1

括弧内の数値は文字コードを 8 進数で表したものです(137 は _、12 は改行)。検証用のヘルパーは 1 行で書けます。

fun isBase64(s: String) = runCatching { Base64.decode(s) }.isSuccess

特定の文字列がどこで壊れているかを見るには Base64 バリデーター を使ってください。よくある原因は Base64 のエラーの解説 にまとめています。

落とし穴

  • パディングのない入力をデフォルトのインスタンスに渡す。 JWT のセグメントや多くのトークンには = がありません。文字列を手作業でパディングするのではなく、PRESENT_OPTIONAL か ABSENT_OPTIONAL を設定してください。
  • Android の DEFAULT フラグ。 android.util.Base64.encodeToString(bytes, Base64.DEFAULT) は 76 文字ごとと末尾に改行を挿入します。HTTP ヘッダーではこれがリクエストを壊します。NO_WRAP を、トークンには NO_WRAP or URL_SAFE or NO_PADDING を使ってください。
  • String.toByteArray() の文字セット。 古い JDK での Java の getBytes() と違い、Kotlin ではデフォルトが UTF-8 なので、"héllo".toByteArray() は安全です。
  • バイナリを文字列にデコードする。 画像のバイト列に decodeToString() を使うと、不正なシーケンスが U+FFFD に置き換えられてデータが壊れます。ByteArray のまま扱ってください。

バージョンごとの注意

  • Kotlin 1.8.20:kotlin.io.encoding.Base64 が実験的機能として追加
  • Kotlin 2.0.20:withPadding と PaddingOption
  • Kotlin 2.2:コア API が安定版に。JVM のストリーム用ヘルパーは実験的機能のまま

よくある質問

@OptIn(ExperimentalEncodingApi::class) はまだ必要ですか?

Kotlin 2.2 以降の Base64.encode と Base64.decode には不要です。ストリーム用ヘルパーの encodingWith と decodingWith は、Kotlin 2.4 でもまだオプトインが必要です。

Kotlin でパディングのない Base64 をデコードするには?

設定済みのインスタンスを使います。パディングのない入力には Base64.UrlSafe.withPadding(Base64.PaddingOption.ABSENT) を、両方を受け付けるなら PRESENT_OPTIONAL を使ってください。デフォルトのインスタンスはパディングを要求します。

android.util.Base64 と kotlin.io.encoding.Base64 のどちらを使うべきですか?

新しいコードなら、Kotlin の API は標準ライブラリの一部なので、どの Android バージョンでも動作します。android.util.Base64 は Base64.NO_WRAP を渡さないと改行を入れるため、ヘッダーが壊れる原因としてよく知られています。

続けて読む