Base64 形式を扱う必要がありますか?それならこのサイトが最適です!データをエンコードまたはデコードするために便利なオンラインツールをご利用ください。

R での Base64 デコード:完全ガイド

あなたの R セッションに1つの文字列が着地しました。シャッフルされたアルファベットのスープのようで、文字と数字、ときどき現れるプラスやスラッシュ、そして末尾にぶら下がっているイコール記号のペア。送ってくれた相手は、それがかつてはごく普通の文、JPEG、あるいは JSON ドキュメントだったと断言します。その文字列こそ Base64 で、このページはそれを元の姿に戻すためのレシピです。

簡単な復習から。このサイトのホームページではフォーマットを隅々まで詳しく説明しています:Base64 は3つの入力バイトを、64記号のアルファベットから選んだ4文字として書き、末尾の = 1つか2つが本当のデータの終わりを示します。この4対3の交換が、エンコードされたテキストが元より約3分の1大きくなる理由であり、デコードとはその交換を逆方向に回すだけです。問題の形を頭の片隅に置いて、封筒をいくつか開けていきましょう。

R が他の多くの言語と少し違うのはここです:base R には Base64 が一切ありません。base パッケージのどこかに base64_decode() が隠れていたり、1行で使えるビルットインがあったりしません。パッケージを持ってくる必要があります。朗報は、エコシステムには個性の異なるいくつかのパッケージがあり、この記事の終わりまでに、どれに手を伸ばせばよく、どれを警戒すべきかが確実に分かるようになることです。

デコーダー一覧

重労働をこなすのは5つのパッケージで、2つの大きな陣営に分かれます:汚れた入力をふんわり受け止める寛容派と、RFCを契約書として扱う厳格派。これが出演者たちの顔ぶれで、2026年時点のものです:

パッケージ バージョン(2026) デコードのエントリポイント 個性
base64enc 0.1-6 base64decode() デフォルトは寛容。2026年2月に strict モードが追加された
openssl 2.4.2 base64_decode() 空白をスキップするが、無言で失敗する仕方には厳しく見届けさせたい
b64 0.1.7 decode(), decode_as_string() 厳格、ベクトル化、Rust 製、速い
base64 2.0.2 decode() ファイルからファイルへ、openssl を包んだお手軽ラッパー
base64url 1.4 base64_urldecode() URL 安全なアルファベット、パディングなし、静かに寛大

惜しくも主役を外れた3つも、触れておく価値があります。jsonlite パッケージは自分専用のヘルパー base64_enc と base64_dec、それに URL 安全ペアをエクスポートしているので、すでに JSON をパースしていれば、手元にデコーダーがあるかもしれません。jose パッケージは JWT 作業用の base64url_decode() を同梱しています。そして古参の RCurl パッケージは今も libcurl を包んだ base64() 関数を持ち続けています:動きます、文字単位で処理し、新しいコード向けというより忠実な祖父のような雰囲気です。

キャストのインストール

まだマシンに R が入っていなければ、オペレーティングシステムが用意しています:Debian と Ubuntu では r-base、Fedora では R、macOS と Windows ではパッケージかインストーラー。その次はパッケージで、CRAN からまっすぐ:

install.packages("base64enc")
install.packages("openssl")
install.packages("b64")
install.packages("base64url")

ビルドに関する注意点が2つあります。ここがインストールで横滑りしやすい場所です。openssl パッケージはシステムの OpenSSL に対してコンパイルされるので、なま物の Linux マシンでは先に開発用ヘッダーを入れたいかもしれません:

sudo apt install libssl-dev

b64 パッケージは extendr で包んだ Rust エンジンのため、ソースからビルドするには Rust ツールチェーンが必要です(sudo apt install cargo を実行すると rustc も一緒に入ります)。Windows と macOS では CRAN からプリビルド済みバイナリが得られるので、これらは一切当てはまりません。パッケージマネージャーが好みなら、pak::pkg("base64enc") や remotes::install_cran("b64") が同じ仕事を、リポジトリへの意見が少なくてしてくれます。

最初のデコード:3行の儀式

デコードの日常の90%は3行で収まります。有名な TWFu 文字列を使った、定石のスモークテストがこれです:

library(base64enc)
packed <- "TWFu"
bytes <- base64decode(packed)
bytes
#> [1] 4d 61 6e
rawToChar(bytes)
#> [1] "Man"

あの小さな儀式には、注目するべきことが3つあります。まず、base64decode() が返すのは常に raw ベクトルであり、決して文字列ではありません。それは仕様であり、偶然ではありません:Base64 が運ぶのは文でも JPEG でも証明書でもよく、手にしたものを知るまでは、どれも同じ扱いにするべきなのです。第二に、バイトからテキストに戻すのは rawToChar() 经由の別個で意図的なステップで、文字セットを決めるのはこのステップです(後ほど詳しく)。第三に、TWFu は単語「Man」にデコードされます:文字3つ、ドラマゼロ。この文字列をポケットの奥に入れておきましょう。どんなデコードコードも TWFu を Man に変えられたら、そのマシンは誠実です。

いつか自分がエンコードしたものをデコードすることになるでしょうから、2つの方向が一致することを証明する完全なラウンドトリップを示しておきます:

text <- "Hello, world!"
packed <- base64encode(charToRaw(text))
packed
#> [1] "SGVsbG8sIHdvcmxkIQ=="
identical(text, rawToChar(base64decode(packed)))
#> [1] TRUE

raw ベクトルの境界

R は2つの小さな関数でバイトとテキストの境界を越え、それを知ればこの記事の残りは当たり前に感じられます。charToRaw() が文字列をバイトに変え、rawToChar() がその逆を行い、そのあいだには raw ツールキット全体があります:バイト数を数える length()、ぞっとして見る head()、ファイルへの入出りを扱う writeBin() と readBin()。この記事のすべてのデコーダーは、わざとその境界で足を止めています。

bytes <- base64decode("SGVsbG8s")
length(bytes)
#> [1] 6
head(bytes)
#> [1] 48 65 6c 6c 6f 2c
rawToChar(bytes)
#> [1] "Hello,"

だから [1] 48 のような結果を見たら、それを「1バイト、値72、文字 H」と読んで、そのバイトがどの文字セットを身につけているかを分かってから先に進まないでください。あの一区切りが、デコードという規律のすべてです。

汚れた入力:デコーダーが分かたれる瞬間

午前2時にあなたを救うのがこのセクションです。入力が少し汚れていると何が起きるのか、デコーダー同士で意見が割れているからです。現実世界の Base64 は、内部にスペースを孕んでやってきたり、神経質になったコピペにパディングを削られたり、それを食べたクリップボードが残した浮遊記号を伴ったり、末尾にジャンクを引きずったりします。同じ一族の違反者に対して、各デコーダーはこう答えます:

入力 base64enc(デフォルト) base64enc(strict = TRUE) openssl b64
"SGVs bG8s IHdvcmxkIQ=="(内部にスペース) 「Hello, world!」(スペースをスキップ) エラー:不正な文字、位置が示される 「Hello, world!」(スペースをスキップ) エラー
"SGVsbG8sIHdvcmxkIQ"(パディングが削られた) 「Hello, world!」(パディング不足は容認) エラー:パディング不足 エラー:デコード失敗 エラー:パディング不正
"SGVsbG8s!IHdvcmxkIQ=="(内部に「!」) 「Hello, world!」(不正な文字をスキップ) エラー:不正な文字 空の raw ベクトル、エラーなし エラー
"SGVsbG8sIHdvcmxkIQ==xx"(末尾にジャンク) 「Hello, world!」(ジャンクを無視) エラー:末尾の余分な内容 空の raw ベクトル、エラーなし エラー
"TQ=="(クリーン) M M M M

あの表を2回読みましょう。全部の物語が入っているからです。デフォルトの base64decode() は親切な税関係員です:アルファベット外の文字をスキップし、パディング不足を容認し、末尾の内容を無視するので、メールの形やクリップボードの形の文字列はそのまま通ってしまいます。strict = TRUE モードは、長き沈黙のあと 2026年2月のリリース 0.1-5 でやってきた法医学の検査官です:文字列全体を検証し、違反者の正確な位置を名指しし、教科書通りでないものはすべて拒否します。b64 はデフォルトで厳格で、半分成功することはありません。そして openssl は居心地の悪い中間に座っています:空白のスキップは喜んで行い、パディング不足には正しくエラーを出しますが、本物の違法な文字や末尾ジャンクに遭遇すると、空の raw ベクトルを返して、まったく何も言いません。あの無言の空の結果こそが、この記事全体で最も危険な行動なので、実際に起きたところを見てみましょう:

dirty <- "SGVsbG8s!IHdvcmxkIQ=="
openssl::base64_decode(dirty)
#> raw(0)   # 空。エラーなし。警告なし。ヒントもなし。

openssl に対してコードを書くなら、それを信頼する前に手元に来たものの長さを調べましょう。「データはどこへ行ったのか」という謎の一族を丸ごと防いでくれる、小さな習慣です。

厳格派のために、base64enc が正確を期するところを、あなたのコンソールに現れるのとまったく同じエラーメッセージ付きで示します:

base64enc::base64decode("TWF u", strict = TRUE)
#> v=30000, pad=0, org='u'
#> Error: Invalid character (' ') at position 4 in base64 string (not allowed in strict mode)
base64enc::base64decode("TWF", strict = TRUE)
#> v=10000, pad=3, org=''
#> Error: Missing padding (1 characters) at the end of the base64 string (not allowed in strict mode)
base64enc::base64decode("TWF=xx", strict = TRUE)
#> v=20000, pad=0, org='xx'
#> Error: Trailing content 'xx' after padding at position 4 in base64 string (not allowed in strict mode)

期待すべき奇妙さが1つあります:strict 呼び出しは、成功も失敗も問わず、v=30000, pad=0, org='u' のような C レベルのステータス行をコンソールに喋り散らします。コンパイルされたコードからの直接書き出しであり、R の警告ではないので、suppressWarnings() では触れられません。無害ですが、テストでコンソール出力を比較しているなら、余計な行がある理由が今に分かったはずです。

R 流の癖:NA、ベクトル、無言の連結

Base64 に癖があるように、R も独自に癖を加えます。まず刺さるのが NA 経由です。NA_character_ をデコーダーに渡すと、デコーダーが見る前に R がこっそりそれを文字列 "NA" に型変換してしまいます。しかも "NA" は完全な Base64 グループです。結果はエラーでも空ベクトルでもありません。バイト 0x34、つまり文字 "4" です:

base64decode(NA_character_)
#> [1] 34
rawToChar(base64decode(NA_character_))
#> [1] "4"

つまり、欠損値の列は欠損値にデコードされるわけではありません。文字 4 の列にデコードされ、下流のコードがにこにこしながらそれを処理してしまうのです。入力に NA が混ざりうるのであれば、先に絞り込みましょう:

values <- c("TWFu", NA_character_, "TQ==")
clean <- values[!is.na(values)]
out <- vapply(clean, function(x) rawToChar(base64decode(x)), character(1))
unname(out)
#> [1] "Man" "M"

2つ目の癖はベクトル入力で、デコーダーは完全に異なる方向に曲がります。base64decode() は文字列ベクトルを1つの文字列の断片たちとして扱い、連結してしまうので、2要素が1つの raw ベクトルとして返ってきます。openssl::base64_decode() はそこまでたどり着くこともできません:引数を潰してから2要素の論理判定にぶつかり、R で最も正直なエラーメッセージのひとつを生み出します。そして b64::decode() は本当にベクトル化された唯一の存在で、入力ごとにデコード済みブロブを1つ返します:

base64decode(c("TWFu", "TQ=="))
#> [1] 4d 61 6e 4d
openssl::base64_decode(c("TWFu", "TQ=="))
#> Error in if (is.na(text)) : the condition has length > 1
out <- b64::decode(c("TWFu", "TQ=="))
rawToChar(out[[1]])
#> [1] "Man"
rawToChar(out[[2]])
#> [1] "M"

要点:ループでもベクトル化でも、意図的に行うこと。入力のベクトルから出力のベクトルが返ってくるとは、決して思わないこと。

URL 安全なアルファベット

標準 Base64 はアルファベットの最後の2枠を + と / に使い、それはまさに URL が嫌がる文字です。プラスは %2B に、スラッシュは %2F に、パディングは %3D になり、どこにでも貼れるはずのトークンがパーセント記号を身につけ始めるのです。RFC 4648 セクション 5 で定義される URL 安全な変体は、その2文字を - と _ に交換し、たいていパディングも捨てます。R にはその世界への扉が3つあります。

扉1つ目は b64 のエンジンです。エンジンとは、設定済みのアルファベットとパディング方針のことで、パッケージは必要な4つを同梱しています:

library(b64)
std <- encode(as.raw(c(0xfb, 0xef, 0xbe)))
std
#> [1] "++++"
url <- encode(as.raw(c(0xfb, 0xef, 0xbe)), engine("url_safe"))
url
#> [1] "----"
decoded <- decode(url, engine("url_safe"))[[1]]
toString(decoded)
#> [1] "fb, ef, be"

エンジンは "standard"(デフォルト)、"standard_no_pad"、"url_safe"、"url_safe_no_pad" の4つで、同じエンジンオブジェクトが両方向で動くので、コードは対称のまま保てます。アルファベットについても厳格で、"----" を標準エンジンに渡すと "Invalid byte 45, offset 0." と答えてくれます。これには安堵します。

扉2つ目は専用の base64url パッケージで、小さくて単一目的です:URL 安全なアルファベット、パディングなし、常時。1つ注意点:そのデコーダーは寛容なので、破損した文字列の有効な前接辞を喜んでデコードして、そこに何も言わずに止まります。URL 安全な値が信頼できない来源から来たなら、b64 を選びましょう。

base64url::base64_urlencode("hello world")
#> [1] "aGVsbG8gd29ybGQ"
base64url::base64_urldecode("aGVsbG8g!!!")
#> [1] "hello "   # 残りは静かに捨てられた

扉3つ目は jose で、JWT 作業用の base64url_encode() と base64url_decode() をエクスポートし、望む通り raw ベクトルを返します。

たまに1回だけ使いたいだけで base64enc の中に留まりたいなら、文字列のメス仕事にパディング修正を加えれば十分です:

url <- "----"
fixed <- gsub("_", "/", gsub("-", "+", url), fixed = TRUE)
padded <- switch(as.character(nchar(fixed) %% 4L),
  "0" = fixed,
  "2" = paste0(fixed, "=="),
  "3" = paste0(fixed, "="))
rawToChar(base64decode(padded))
#> [1] "\xfb\xef\xbe"

JWT:覗き込みと信頼

多くの人が URL 安全な Base64 と出会う理由は、JSON Web Token です。JWT はドットで結ばれた3つの base64url パートからなります:それがどう署名されたかを説明するヘッダー、クレームのペイロード、そして全体を信頼できるものにする署名。最初の2つのパートを R でデコードするのは5行程度の仕事です。strsplit() の fixed = TRUE に注意してください:ドットは正規表現のワイルドカードで、あのフラグがないと R はにこやかにトークンを1文字ずつ切り刻みます。現実の R コードにおける Base64 バグで最も多いのがこれです。

jwt <- jose::jwt_encode_hmac(jose::jwt_claim(sub = "1234567890", iat = 1516239022, name = "John Doe"), "0123456789abcdef")
parts <- strsplit(jwt, ".", fixed = TRUE)[[1]]
header <- base64url::base64_urldecode(parts[1])
header
#> [1] "{\"typ\":\"JWT\",\"alg\":\"HS256\"}"
payload <- jsonlite::fromJSON(base64url::base64_urldecode(parts[2]))
payload$name
#> [1] "John Doe"

正直な注意書きを2つ。第一に、JWT をデコードするのは覗き込みであり、信頼ではありません:3番目のパートは署名で、発行体のキーに対して検証されて初めて意味を持ちます。そのために、jose パッケージが一族全体を扱ってくれます。その 2.0 リリース(2026年4月)は ED25519 サポートを追加し、typ ヘッダーを任意にしました。1.x の jwk_read()/jwk_write() ヘルパー(またはその read_jwk()/write_jwk() エイリアス)を紹介している古いチュートリアルは、2.0 でも同じ JWK の読み書きヘルパーが同梱されているため、引き続き有効です:

library(jose)
claims <- jwt_decode_hmac(jwt, "0123456789abcdef")
claims$name
#> [1] "John Doe"
claims$sub
#> [1] "1234567890"
sp <- jwt_split(jwt)
sp$header
#> $alg
#> [1] "HS256"
#>
#> $typ
#> [1] "JWT"
sp$payload$name
#> [1] "John Doe"

jwt_decode_hmac() は署名を検証し、exp と nbf クレームも強制するので、トークンが期限切れならエラーを投げます。そして第二に:base64url::base64_urldecode() と仲間はヘッダーとペイロードなら問題なくデコードしますが、署名のパートはバイナリなので、文字列ではなく raw を返す関数(例えば "url_safe_no_pad" エンジン付きの b64::decode())でデコードしてください。

文字セット:そのバイトは何という文字か

この記事のすべてのデコーダーは、わざと raw ベクトルの境界で足を止めます。「あれは何という文字だったのか」という答えは、バイトが詰め込まれたときの文字セットに依存するからです。先に朗報を:送った側が UTF-8 を使っていれば(現代の Web のほとんどがそうです)、あなたの人生は短くて幸せです。base64enc にはそれ専用のゲートまで同梱されています:

packed <- base64encode(charToRaw("caf\u00e9"))
bytes <- base64decode(packed)
checkUTF8(bytes, quiet = TRUE)
#> [1] TRUE
rawToChar(bytes)
#> [1] "café"

checkUTF8() は「このバイト列は UTF-8 として読めるか」と、確約せずに問いかけます。quiet = TRUE で TRUE か FALSE が返り、デフォルトではさらに直接的で、不正バイトにはエラーを上げて、問題のバイトを名指しします。例えば INVALID byte 0xff at 0x0 (0, line 1): invalid start byte (FE/FF) のように。NUL バイトを含む文字列には FALSE を報告しますが、そんな文字列が有効な UTF-8 の R 文字列の一部になることは絶対にありません。ゲートとして使い、あとは続くだけです。

なぜそのゲートが大切なのか。UTF-8 ロケールでは、バイトが有効な UTF-8 でなくても rawToChar() はエラーを投げません。Encoding() == "unknown" と R が印をつけた文字列を渡してくるだけで、ロケールや R のビルドによっては、同じ呼び出しが「invalid multibyte sequence」エラーで死ぬこともあります。後者は少なくとも正直です。どちらにせよ、見知らぬバイトに対してガードなしの rawToChar() を使うのが、もじばけが生まれる方法です:

broken <- as.raw(c(0xc3, 0x28))   # 切断された UTF-8 ペア
checkUTF8(broken, quiet = TRUE)
#> [1] FALSE
rawToChar(broken)                  # エラーは出ない。そこが問題だ。
#> [1] "\xc3("

そして送った側がまったく別のもの、Latin-1 や Windows-1252 や Shift-JIS を使っていたなら、レシピは raw へデコードしてから iconv() で翻訳することです。名前付きの文字セットを理解してくれます:

latin1_bytes <- as.raw(c(0x63, 0x61, 0x66, 0xe9))   # Latin-1 での「café」
iconv(rawToChar(latin1_bytes), from = "latin1", to = "UTF-8")
#> [1] "café"

イモジにも1つ触れておきます。ここには R の物語があるからです。最新の R は U+FFFF 以上のコードポイント(イモジが住む場所)を本物の UTF-8 として保存するので、ラウンドトリップは機能し、バイト数は他のすべての言語が期待するものと同じになります。古い R リリースでは、同じ文字がサロゲートペアとして保存されていました。これはしばしば CESU-8 と呼ばれる方式で、1つのイモジは 6 バイトの不正な UTF-8 として境界を越えたのです。イモジが線の上で壊れて到着する古い R コードを受け継いだなら、第一の疑いはそこです:nchar(x, type = "bytes") を確認して、送った側の言語が作るものと比べてみてください。

emoji <- "\U0001F600"
nchar(emoji, type = "bytes")
#> [1] 4
round_trip <- base64decode(base64encode(charToRaw(emoji)))
identical(emoji, rawToChar(round_trip))
#> [1] TRUE

目安となるルール:UTF-8 と仮定し、checkUTF8() で検証し、別の文字セットであるという確かな知識があるときにだけ iconv() に手を伸ばすこと。決して推測しないでください。

ファイル:折り返されたテキストから本物のバイトへ

文字列は簡単です。ファイルこそ Base64 が本領を発揮する場所です。どのパッケージでも動く手動パイプラインから始めましょう:エンコードされたテキストを読み、デコードし、バイトを書く。

cat("SGVsbG8sIHdvcmxkIQ==", file = "hello.b64")
bytes <- base64decode(paste(readLines("hello.b64"), collapse = ""))
writeBin(bytes, "hello.txt")
readLines("hello.txt")
#> [1] "Hello, world!"

次は利便性レイヤー。base64enc は読み書きを代行してくれます:file 引数は Base64 テキストを持つファイルを指し、output はデコードされたバイトが行き着くべき場所を指します。戻り値は書かれたバイト数で、美しい無料の健全性チェックになります:

n <- base64decode(file = "hello.b64", output = "hello.txt")
n
#> [1] 13
readLines("hello.txt")
#> [1] "Hello, world!"

メールや PEM アーマーを生き延びてきたような折り返された入力は、寛容なデコーダーにとっては問題になりません:改行がどこに落ちていても素直に見通し、残りが有効な Base64 である限りです。MIME は行間に CRLF を挟んで 76 文字で折り返し、PEM ブロックは 64 で折り返します。もちろん strict エンジンは、その折り返しそのものを断固拒否します。

wrapped <- paste0("VGhlIHF1aWNrIGJ", "\r\n",
  "yb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw==")
rawToChar(base64decode(wrapped))
#> [1] "The quick brown fox jumps over the lazy dog"
rawToChar(openssl::base64_decode(wrapped))
#> [1] "The quick brown fox jumps over the lazy dog"
b64::decode(wrapped)
#> Error: Invalid byte 13, offset 15.

b64 パッケージには最も名前が明快なファイル対があり、Rust の速さゆえにファイルが大きいときはこれに手を伸ばすべきです。ただし尖った刃が1つあります:decode_file() はファイルをバイト単位で読み、容赦がありません。末尾の改行(writeLines() は足すのに cat() は決して足さない、あの種の)が Rust エンジンをパニックにさせ、User function panicked: decode_file_ という形のキャッチできるエラーとして顔を出します。エンコードされたテキストは cat() か writeBin(charToRaw(enc), path) で書けば、あの刃は钝いままです。

writeBin(charToRaw("file payload bytes"), "payload.bin")
enc <- b64::encode_file("payload.bin")
cat(enc, file = "payload.b64")
bytes <- b64::decode_file("payload.b64")
rawToChar(bytes)
#> [1] "file payload bytes"

base64 パッケージは3つ目の入り口です:純粋にファイル志向で、対応する関数のペアが備わっています:

base64::encode("payload.bin", "payload.b64")
base64::decode("payload.b64", "payload.out")
readLines("payload.out")
#> [1] "file payload bytes"

実践で出会うファイルの形にもう1つ:エンコードされたブロブで埋まったデータフレームの列。API ダンプでは非常に多い形です。数百行程度なら、シンプルなベクトル化呼び出しで十分です:

df <- data.frame(content = c(b64::encode("alpha"), b64::encode("beta"),
  b64::encode("gamma")))
decoded <- b64::decode(df$content)
df$decoded <- vapply(decoded, function(x) rawToChar(x), character(1))
df$decoded
#> [1] "alpha" "beta"  "gamma"

API と Web 応答

JSON API はバイナリをテキストに押し込むのが好きで、Base64 はそのお得意のスーツケースです。R でのパターンは毎回同じです:取得、パース、デコード、そしてそのバイトが何かを判断する。現代的な HTTP クライアントは httr2、JSON 側は jsonlite です:

library(httr2)
library(jsonlite)
res <- req_perform(request("https://httpbin.org/get?attachment=TWFuIGlzIGhlcmU%3D&name=man.txt"))
data <- fromJSON(rawToChar(res$body))
bytes <- base64decode(data$args$attachment)
writeBin(bytes, data$args$name)
length(bytes)
#> [1] 11

道中のための注意が3つ。リクエストのコンストラクタは request() で、httr2 の最初のリリースからずっとその名前です。レスポンスボディは raw ベクトルで到着するので、JSON としてパースする前に rawToChar()。そして API は方言を話すこともあります:デコーダーに渡す前に、その Base64 が URL 安全なのか、パディングが剥がされているのかを確認してください。Base64 の Base64 という二重エンコードをする API もあります。期待していたバイトの代わりに、デコード1回でさらに Base64 が返ってきたら、それがそれです。

data: URI と自己完結型ドキュメント

data: URI スキーム(RFC 2397)は、ドキュメントが自身のコンテンツを持ち歩くことを可能にします:MIME タイプ、ペイロードがエンコードされている場合の「base64」という単語、そしてペイロードそのもの。スクレイプした HTML では常にこれらに出会いますが、1つをデコードするのは2ステップの文字列操作のあとに普通のデコードを行うだけです:

img_tag <- "<img src=\"data:image/png;base64,iVBORw0KGgoAAA\" />"
uri <- regmatches(img_tag, regexec("src=\"([^\"]*)\"", img_tag))[[1]][2]
uri
#> [1] "data:image/png;base64,iVBORw0KGgoAAA"
payload <- sub("^data:[^,]*,", "", uri)
bytes <- base64decode(payload)
length(bytes)
#> [1] 10

同じ形は CSS にも、PDF の注釈にも、自己完結型の R Markdown レポートにも現れます。そこではプロットが <img> タグに平坦化されており、HTML は脇のファイルなしで旅ができるのです。そんなドキュメントをレンダリングしていて埋め込み画像を抽出したいなら、トリックはこれだけで完了です:data: 文字列を見つけ、最初のコンマで切り、デコードする。

データベース、メール、設定

誰かがテキスト列にバイナリを入れたいと思ったとき、Base64 はデータベースに現れます。デコード側では、文字列の列がバイトに戻ります。DBI と RSQLite を経由した SQLite でのラウンドトリップがこれです:エンコード値を保存し、クエリで取り出し、デコードすれば、バイトが手元に戻ります:

library(DBI)
library(RSQLite)
db <- dbConnect(SQLite(), ":memory:")
dbExecute(db, "CREATE TABLE files (name TEXT, payload TEXT)")
stored <- base64encode(charToRaw("stored in a database"))
dbExecute(db, paste0("INSERT INTO files VALUES ('note.txt', '", stored, "')"))
row <- dbGetQuery(db, "SELECT * FROM files")
rawToChar(base64decode(row$payload))
#> [1] "stored in a database"

SQLite は BLOB としてバイナリをネイティブに保存することもでき、その場合 Base64 はまったく不要で、列は raw ベクトルとして R に戻ってきます。例えば class(dbGetQuery(db, "SELECT payload FROM blobs")$payload[[1]]) == "raw" のように。TEXT 中の Base64 という変種が存在するのは移植性のためです:テキストエディタで中身を見られるし、他のどんな言語でもバイナリドライバなしで読めます。

メールこそ、折り返された Base64 の発生源です。MIME のパートは行間に CRLF を挟んで 76 文字で折り返され、あなたが今まで読んできたあらゆるメールシステムは、ちょうどその形で添付ファイルを運んできました。R に第一級のメールクライアントはありませんが、.eml ファイルを受け取ったときは、添付の base64 パートはただの折り返されたテキストです:寛容なデコーダーはデコードしながら勝手に解きほぐしてくれます。mime パッケージが、手にしているものが何かを特定する手を貸してくれます:

mime::guess_type("photo.jpg")
#> [1] "image/jpeg"
part <- "SGVsbG8s\r\nCnRoaXMgaXMgYW4g\r\nZW1haWwgYXR0YWNobWVudA=="
rawToChar(base64decode(part))
#> [1] "Hello,\nthis is an email attachment"

設定ファイルでツアーは完結します。証明書やブロブが YAML か JSON の設定、あるいは環境変数に Base64 エンコードで保存されているとき、R はそれを普通の文字列として読み、必要になったらデコードします:

config_line <- "api_cert: TFRTU0VDUkVU"
cert_b64 <- sub("^api_cert: ", "", config_line)
raws <- base64decode(cert_b64)
rawToChar(raws)
#> [1] "LTSSECRET"

大容量ペイロード

R の文字列には 2^31 - 1 バイトという硬い上限があり、エンコード後の形式は元より約3分の1大きくなるため、十分に大きな Base64 文字列がそれにぶつかることがあります。base64enc パッケージは 2022 年のリリース以来これを処理しています:base64encode() に行幅を与えると、1つの巨大な文字列の代わりに行のベクトルを返し、デコード側では file = 引数がファイルを行単位で読んでデコードするので、巨大な文字列を1つの変数に抱え続ける必要はありません。

数百メガバイトのファイルでは、実用的なパターンは揃ったチャンクで読むことです。Base64 のグループは独立しているので、長さが4文字の倍数であるチャンクはすべて自分の力でデコードでき、端数の受け皿には小さなバッファがあれば十分です:

chunk <- 65536L
con <- file("huge.b64", "rb")
on.exit(close(con))
leftover <- ""
out <- raw(0)
while (TRUE) {
  piece <- readChar(con, chunk, useBytes = TRUE)
  if (length(piece) == 0) break
  piece <- gsub("[\r\n]", "", piece)
  ready <- paste0(leftover, piece)
  take <- floor(nchar(ready) / 4) * 4
  if (take > 0) {
    out <- c(out, base64decode(substr(ready, 1, take)))
    leftover <- substr(ready, take + 1, nchar(ready))
  } else {
    leftover <- ready
  }
}
if (nchar(leftover) > 0) out <- c(out, base64decode(leftover))
length(out)

この地帯では b64 パッケージがスピードチャンピオンです:その Rust エンジンは 50 メガバイトのファイルをわずかな秒でデコードし、ベクトル化された decode() は多くのエンコード値の列を1回の呼び出しで処理します。行ごとにループするのとは桁違いです。文字列がたくさんあるなら、自分で簡単な system.time() 比較を実行してみてください。行ごとのループと1回のベクトル化呼び出しの差は、たいていそれだけの価値があります。

コマンドライン

すべてにフルの R セッションが必要というわけではありません。クラシックな Unix ツールは Base64 をネイティブで話し、R は仕事を彼らに渡したり、彼らから受け取ったりできます。Linux では base64 -d がデコードします(macOS とその他の BSD システムでは -D):

echo "SGVsbG8sIHdvcmxkIQ==" | base64 -d
#> Hello, world!

1行の Rscript で、スクリプトで使っている同じパッケージを使いながら同じ仕事をすることもできます:

Rscript -e 'library(base64enc); writeLines(rawToChar(base64decode("TWFu")))'
#> Man

素早い確認やパイプにはシェルを使い、結果がデータフレーム、ファイル、レポートの中に住む必要があるときには R を使ってください。注意が1つ:コマンドライン引数にはサイズ制限(ARG_MAX)があるので、何メガバイトもの文字列をターミナルに貼り付けたりしないでください。代わりにファイル経由でパイプ送りにしてください。

知っておきたい落とし穴

これが R 開発者を刺す仕方の短いリストです。すべては Base64 全般ではなく、エコシステム自体に根ざしたものです:

  • openssl は無言で失敗する。 base64_decode() は、アルファベット外の文字や末尾ジャンクに遭遇すると、エラーも警告もなく空の raw ベクトルを返します。結果を信じる前に必ず length() を確かめましょう。
  • デコーダーにとって NA は欠損値ではない。 NA_character_ は文字列 "NA" に型変換され、それはバイト 0x34 にデコードされます。デコードする前に NA を絞り込みましょう。
  • ベクトルは期待通り動かない。 base64decode() は入力を1つの raw ベクトルに連結する;openssl::base64_decode() は複数要素の入力で the condition has length > 1 で死に;本当にベクトル化されたのは b64::decode() だけ。
  • rawToChar は無言の破壊者。 不正な UTF-8 バイトは UTF-8 ロケールではエラーを投げず、"unknown" と印のついた文字列になります。先に checkUTF8() でガードしましょう。
  • JWT のドットは正規表現のワイルドカード。 fixed = TRUE なしに strsplit(jwt, ".") とすると、すべての文字で分割されます。これが R コードで最も多い Base64 バグです。
  • b64 のファイルは容赦しない。 エンコード済みファイルの末尾改行が b64::decode_file() をパニックにします。writeLines() ではなく cat() で書きましょう。
  • 寛容は信頼には寛容すぎる。 base64decode() のデフォルトモードはどこにいても不正な文字をスキップするので、破損した文字列がエラーなくありそうなゴミにデコードされることがあります。信頼境界では strict = TRUE を使いましょう。
  • b64 のエラーメッセージは Rust を話す。 望まないタイプを渡すと Both cases of Either errored のような文を期待してください。メッセージはわざと役に立たない;Rust の型システムが肩をすくめているのです。

ベストプラクティス

  • 日常の仕事では base64enc::base64decode() を既定にし、入力が信頼境界を越える場所では strict = TRUE をオンにしてください:信頼できない API、ユーザーのアップロード、署名されたもの何でも。
  • 速さ、本物のベクトル化、または URL 安全なノンパディングエンジンが必要なら b64 に手を伸ばし、それが設計上で厳格であることを受け入れましょう。
  • openssl がすでにプロジェクトにあるなら使いますが、長さを確かめるまで、すべての結果を疑わしいものとして扱ってください。
  • 文字セットは意図的に決めること:UTF-8 と仮定し、checkUTF8() で検証し、送った側が別のことを明言した場合にだけ iconv() を使いましょう。
  • JWT では strsplit() に fixed = TRUE を付けて覗き、信頼するのは jose が署名を検証したあとのみにしてください。
  • テストに TWFu を置いておきましょう:それはいずれのデコードパスでも3バイトのスモークテストになります。
  • Base64 が何ではないかを思い出してください:暗号化でも圧縮でもありません。それは梱包テープで、この記事を持っている人なら、それが行うことのすべてを逆さまにできます。自由にデコードし、選択的に信頼しましょう。

R における Base64 の簡単な歴史

この形式は古いものです。1987 年に Privacy-Enhanced Mail プロトコル向けに標準化され(RFC 989)、1993 年に MIME が採用し(RFC 1521、その後 1996 年に最終版 RFC 2045)、2003 年に RFC 3548 で整理され、2006 年の RFC 4648 で、URL 安全なアルファベットを含む現代的な形が与えられました。R の物語ははるかに短く、動きも速いです。base64enc パッケージ(Simon Urbanek による)は 2012 年9月に初めて CRAN に着き、以来ずっと主力です。2015 年に checkUTF8() を、2022 年に長ベクトルサポートを勝ち取りました。2024 年10月には古い base64 パッケージが、明示的に互換ラッパーとして再発行され、その自己紹介は今や新規アプリケーションを base64enc、openssl、または jsonlite へと導きます。そして 2024 年初頭に b64(現リリースは 0.1.7、2025年7月)が登場。extendr で作られた Rust エンジンで、真のベクトル化とアルファベットの安定軍団をもたらしました。2026 年2月に base64enc が待ちに待った strict モードをリリースし、2026 年4月には jose パッケージが jwt_* 関数を中心に再設計されました。他の言語が1つのライブラリで届けたものに10年かかりましたが、その結果は、すべてのデコーダーに明確な仕事と明確な個性がある工具箱です。

豆知識

完全なガイドは笑顔で終わるべきですから:

  • base64decode(NA_character_) は文字 "4" を返します。なぜなら NA_character_ は文字列 "NA" としてデコーダーに届き、"NA" は有効な Base64 グループだからです。この言語で最も R 固有の 1 バイトの驚き。
  • openssl::base64_decode() に不正な文字が1つだけ入った文字列を渡すと、raw(0) を完全な沈黙のうちに返します。エコシステムで最も危険な静けさ。
  • base64decode() への strict 呼び出しは、成功も失敗も問わず、v=30000, pad=0, org='u' のような C レベルのステータス行を喋り散らします。警告ではありません。C コードがのどを鳴らしているだけです。
  • b64 はあなたが一度も見たことのないアルファベットをデコードできます:BinHex、IMAP modified UTF-7、bcrypt と crypt のカスタムアルファベット。1980 年代の Macintosh 添付ファイルも、現代のパスワードハッシュも、同じエンジンで読めます。
  • R の文字列は 2^31 - 1 バイトで止まるので、base64enc は長い入力には行のベクトルを返すことを覚えました。形式が言語の壁にぶつかれば、パッケージは梯子を伸ばすのです。
  • 単語「base64」は YmFzZTY0 にエンコードされます。自分を説明できる形式は、モールス信号で話す鏡の技術版です。
  • NUL バイト1つでもまだ4文字のコストがかかります:"AA=="。Base64 では、なにものかも必ず何ものかになる。
  • あなたの R ビルドにはあだ名があります。R 4.5.0 は「How About a Twenty-Six」と呼ばれます。R のバージョンは Peanuts のコミックや映画の名前を取り、彼らが借りるコマは、それが名前をつけるリリースより何十年も古いのです。デコードに使っているバージョンすら、ユーモアのセンスを持っているのです。

まとめ

付き合う相手に応じてデコーダーを選びましょう:日常の仕事には base64enc、境界では strict = TRUE;すでにプロジェクトにあるなら結果をチェックする前提で openssl;速さ、ベクトル、URL 安全なエンジンが欲しいなら b64;ファイルの雑用と URL 安全な文字列には小さな専門家の base64 と base64url。バイトをテキストと呼ぶ前に checkUTF8() で守り、文字セットが別のものであると分かっているときは iconv() を使い、中央の raw ベクトルは仕様であることを忘れないでください:それはライブラリに推測させるのではなく、あなたがバイトの意味を決めさせるのです。すべてをデコードし、検証できるものだけを信頼しましょう。そして逆方向に行きたくなったとき - 解きほぐすのではなく、自分のバイトを旅の支度として文字列に詰めるとき - 姉妹記事が R での Base64 エンコードを詳しく扱っています。

最終更新: 2026-10-09

関連記事: R での Base64 エンコード:完全ガイド