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

C++(Cpp)での Base64 エンコード:完全ガイド

逆問題の方が見出しが大きい:あなたの手元にはバイトがある - 証明書、画像、ランダムなブロブ、署名 - それらは文字しか話さない何かの中を通らなければならない:JSONフィールド、メールヘッダ、URL、環境変数です。このサイトのホームページはフォーマットを詳しく説明しているので、これは短縮版だけです:バイト3つが文字表の文字4つになり、短い末尾には = が1つか2つつき、エンコードされた形は元の約33パーセント大きくなります。エンコードは伸びる方向なので、この記事のすべてのバッファはそのサイズになっており、計算は1行で - 4 * ((n + 2) / 3) - どのエンコーダーを選んでも変わりません。

デコード側と同じように、C++そのものが1バイトたりともエンコードしてくれません。標準ライブラリにはbase64関数を育む30年があったのに、そのすべてを他のことに使ってきたので、C++プログラムはどれも、個性がまったく違う4体のベンチから自分のエンコーダーを持ち込み、約40行を自分で書くという選択肢もあります。ひとつは1990年代からTLSを担ってきた主力で、パディングもNUL終端も行折返しも、許可を聞かずにやる。ひとつは、著者が「detail」とラベルを貼った名前空間に潜む、高速なヘッダーオンリーのコーデック。ひとつは、どうやらパディング文字に一度も会ったことのない2002年のイテレータ。ひとつは、OSが何十年も同梱してきた関数で、あなたのトークンの最後にCRLFを付けてくれる。そして5つ目の選択肢は、あなたのものです。それぞれが何を付け、何を拒み、黙って何を付加するかを知れば、エンコードは1つズレバグの源ではなくなります。詰める作業に入りましょう。

標準はかつてパッカーを出したことがない

C++98以降のすべての標準 - C++26まで8つある - が64文字の文字表を見て先へ進みました。<base64> もなく、std::base64 もなく、あなたのバイトを詰めてくれるものは <string> や <vector> にもありません。C++26の技術作業は完了し、2026年3月のイギリスCroydonでのISO C++会議で採択されました(114-12-3)。テキストコーデック作業のための <text_encoding> ヘッダは確かに追加されます。委員会の次の会議、2026年6月(ブルノ)と2026年11月(ブジウス、ブラジル)は、C++26を再検討するのではなくC++29の作業草案を開きます。Base64は標準にありません。委員会を責めるのも難しいです:テキストエンコードは文字セットの話で、base64はバイトの話なので、新しいヘッダはそもそも正しい住処ではありませんでした。実務ではエコシステムが仕事をしました。OpenSSLのEVP base64ルーチンはすべてのOpenSSLリリースにあり、Boostライブラリは2つの独立したエンコーダーを持ち、Windowsはこの仕事のためのフラグテーブル付きCryptoAPI関数を同梱し、40行のスニペットは2008年以来この言語のどこでもコピー&ペーストされています。プロジェクトがCMakeベースなら、依存のセットアップ全体は3行です:

find_package(OpenSSL REQUIRED)
find_package(Boost REQUIRED)
target_link_libraries(my_app PRIVATE OpenSSL::Crypto)

注意すべきBoostリリースは、1998年に創設され最初のリリース1999年からライブラリを出し続けてきたプロジェクトの、2026年8月版1.92.0です。下記の2つのBoostエンコーダーはどちらもヘッダーオンリーで - リンクするものはまったくない - OpenSSLは -lcrypto を欲しがりますが、TLSに触れるC++プログラムの大半はすでにバイナリの中に持っています。

まずは計算:この記事の全バッファのサイズ決め

Base64はバイトを3つずつグループにします。そのため、出力長には知れば決して驚かない形があります:入力バイト3つに対して文字4つが出て、短い末尾は1つのグループまでパディングされます。入力 n バイトの正確な個数は:

4 * ((n + 2) / 3)

この +2 が切り上げのテクニックです:整数除算は切り捨てるので、先に2を足すと3の次の倍数に切り上がります。そこから、この記事のすべてのバッファサイズは代入で済みます。OpenSSLのワンショット関数は、エンコード済みデータと最後に付けられるNULの両方が入るバッファを要求します - manページは契約を例で示していて、入力16バイトがエンコード済み24バイトとNUL 1つ、合計25バイトになり、関数はNULを含まない長さを返します。ストリーミングパスは48バイトブロックで入力を処理し、manページは出力をブロックごとに65バイトと定めています(64文字に、各ブロックが必ず生む改行を1つ)プラスNUL用に1バイト。Boost.Beastのヘッダは正確な式を constexpr 関数として渡してくれます。そしてあなたのコードは (n + 2) / 3 * 4 をreserveして終了です。実際にヒットする数値はこちら:

入力 出力(パディング付き) 注目すべき点
1バイト 4文字 最小のパディング付き形:QQ==
2バイト 4文字 データ文字3つとパッド1つ
3バイト 4文字 グループ1つ分で、パディングはまったくない
48バイト 64文字 OpenSSLストリーミングブロックちょうど1つ
500バイト 668文字 64で折り返すと11行、改行込みで679文字
1 GB 約1.33 GB 列、ファイル、ワイヤのすべてに税金分を予算付ける

受け手が固定サイズの列、バッファ、テキストファイルの行である場合、この公式が設計書全体です。刺さりうるのはもう一方の方向だけです:デコード側は 3n/4 からパッドを引く必要があり、エンコードの公式でサイズを決めたデコードバッファは、メモリバグのチケットにまで成長する古典的な過剰割り当てです。縮む方向のサイズ決めは姉妹ガイドの問題で、ここでは伸びることしかありません。

全体像を示しましょう。違いはすべて「おまけ」 - パディング、改行、NUL - にあり、コアの詰み処理はどの行も同一に実装しているからです:

エンコーダー 出所 パディング 予算付ける追加バイト 覚えておくべきクセ
EVP_EncodeBlock <openssl/evp.h>、リンクは -lcrypto 常に 1(バッファ内のNUL) manページの16バイト例が契約
EVP_EncodeUpdate + Final 同上 常に 48バイトブロックごとに65 64文字でハード折返し、全ブロックが改行で終わる
Boost.Beast encode boost/beast/core/detail/base64.hpp、ヘッダーオンリー 常に 0 detail という名前空間の中にいる
Boost.Serializationイテレータ boost/archive/iterators/base64_from_binary.hpp、ヘッダーオンリー 決して 0 - パッド1つか2つは自分で付ける 工具箱で最も古いエンコーダー、2002年
CryptBinaryToStringA wincrypt.h、crypt32.lib 常に NOCRLF でなければ2(CRLF) 工具箱の他が持たないURLセーフフラグを持つ
自分で書く40行 どこにもない:あなたのものだから あなたの自由 あなたの自由 全エッジケースを永遠に所有する

コアアルゴリズムはすべての行で同一です - 1987年のフォーマットとして安心できる部分です。異なるのは、各実装がペイロードの周りに何を追加するかで、この記事の罠のほとんどは、その追加のひとつがそれを期待しない受け手に出会う話です。

OpenSSL: TLSスタックがすでにリンクしているエンコーダー

プログラムがTLSのためにOpenSSLをリンクしているなら、追加するものは何もありません。ワンショット関数は呼び出し1つです:

int EVP_EncodeBlock(unsigned char *t, const unsigned char *f, int n);

ソースバイトと長さを渡すと、パディング付きの1行エンコードを書き込みます。契約は暗記する価値があります。manページが例とともに述べているからです:入力バイト3つに対して出力4バイト。3で割り切れない末尾はパディングされ、出力は常に4で割り切れる。その上にNUL終端文字が加わります。ドキュメント化された例は、入力16バイト、エンコード済み24バイトとNUL 1つ、バッファ内合計25バイト、関数は24 - NULを含まない長さ - を返します。バッファをそれに応じてサイズ決めすれば、ラッパーは数行で済みます:

#include <cstddef>
#include <cstdio>
#include <string>
#include <openssl/evp.h>

std::string openssl_encode(const std::string &in) {
  std::string out;
  out.resize(4 * ((in.size() + 2) / 3) + 1);
  int n = EVP_EncodeBlock(reinterpret_cast<unsigned char *>(out.data()),
                          reinterpret_cast<const unsigned char *>(in.data()),
                          static_cast<int>(in.size()));
  if (n < 0) return {};
  out.resize(static_cast<size_t>(n));
  return out;
}

int main() {
  std::printf("%s\n", openssl_encode("Mane").c_str());
  std::printf("%s\n", openssl_encode("M").c_str());
  std::printf("%s\n", openssl_encode("").c_str());
}

C言語なら強要されることを、std::string がやってくれるのに注目してください:返り値の長さにちょうど伸びるので、OpenSSLが付けたNULは管理される長さの外側にあって、ペイロードの一部になることはありません。「Mane」をエンコードすると TWFuZQ==、パッド1つを従えた古典的な4文字の尾。1バイトをエンコードすると、2文字のパッドのコスチュームをまとった2文字のデータペア。何もエンコードすると空文字列が得られ、base64エンコーダーが恒等関数とまったく同じように振る舞う唯一のケースです。関数全体で本当のロジックは1行だけ、それがresizeです:「書き込んだバイト数にNUL」を「ちょうどペイロード」に変えます。

断片的に届くデータ - ファイル、ソケット、バッファリングしたくないストリーム - には、OpenSSLが投入して完了させるコンテキストを持ち、manページのブロック計算は異様に明示的です。48バイトの完全なブロックのみが即座に処理され、残りはコンテキストの中で保持され、後の呼び出しか最終呼び出しで解放されます。処理された各ブロックは64文字と改行 - 65バイト - を書き、最終呼び出しが部分ブロックを扱うので、そのドキュメント化された上限は65バイトとNULです。呼び出す前に知るべき帰結:このAPIは64文字で折り返します。設定できません。それがストリーミングエンコーダーです。

#include <algorithm>
#include <cstdio>
#include <string>
#include <vector>
#include <openssl/evp.h>

std::string openssl_encode_wrapped(const std::string &in) {
  EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new();
  EVP_EncodeInit(ctx);
  std::string out;
  out.reserve(4 * ((in.size() + 2) / 3) + in.size() / 48 + 2);
  std::vector<unsigned char> buf(128);
  int outl = 0;
  for (size_t pos = 0; pos < in.size();) {
    size_t take = std::min<size_t>(48, in.size() - pos);
    EVP_EncodeUpdate(ctx, buf.data(), &outl,
                     reinterpret_cast<const unsigned char *>(in.data()) + pos,
                     static_cast<int>(take));
    out.append(reinterpret_cast<const char *>(buf.data()), outl);
    pos += take;
  }
  EVP_EncodeFinal(ctx, buf.data(), &outl);
  out.append(reinterpret_cast<const char *>(buf.data()), outl);
  EVP_ENCODE_CTX_free(ctx);
  return out;
}

int main() {
  std::string s = openssl_encode_wrapped(std::string(500, 'A'));
  std::printf("500 bytes -> %zu chars\n", s.size());
  int lines = 0;
  size_t longest = 0, run = 0;
  for (char c : s) {
    if (c == '\n') { lines++; run = 0; }
    else run++;
    longest = std::max(longest, run);
  }
  std::printf("lines=%d longest=%zu lastchar=%c\n", lines, longest, s.back());
}

文字Aの500バイトを与えると、会計はmanページが約束したとおりに出てきます:エンコード済み文字668。出力は64文字行に切られるので、11行、合計679文字、そして最後の文字は改行です。その末尾改行こそが受け手を壊すやつです。結果をJSON文字列に貼り付けると、クォートが来るべき場所に制御文字があり、トークンのセグメントに使ったら新しいセグメントを発明したことになります。経験則:1行ペイロード(トークン、ヘッダ、設定値)にはブロックAPI、受け手がMIME風の折り返し出力を望むならストリーミングAPI。迷ったら、ペイロードがそれを期待しない境界を渡る前に while (out.back() == '\n') ループで末尾改行を剥がしてください。

Boost.Beast: detail:: 名前空間にいる高速パッカー

BoostのHTTPライブラリは boost/beast/core/detail/base64.hpp という意外な住所にbase64コーデックを搭載しています。detail:: 名前空間はBoostの「これは内部の話だ」という言い方で、メンテナたちはコーデックの公開APIへの昇格を断ってきました。それでも誰もが使います:小さい、速い、ヘッダーオンリー(includeの前に BOOST_BEAST_HEADER_ONLY を定義すればリンクするものは何もありません)、そしてBoost.Beast自身のWebSocketハンドシェイクが Sec-WebSocket-Accept 計算に使っているのと同じコーデックです。つまり、何年も本物のトラフィックをかじり続けてきたということです。

エンコード側では、APIは呆れるほど穏やかです。constexpr ヘルパーが正確な出力サイズを与えてくれます - 4 * ((n + 2) / 3)、計算セクションと同じ公式で、今度はコンパイラがチェックしてくれます - encode 関数はパディング済みの結果をあなたのバッファに書き、何文字使ったかを教えてくれます。エラーチャネルはありません。エンコードは失敗しないからです:どのバイトも正当な入力で、出力長は入力長の純関数です。ラッパー:

#define BOOST_BEAST_HEADER_ONLY
#include <boost/beast/core/detail/base64.hpp>
#include <cstddef>
#include <cstdio>
#include <string>

namespace b64 = boost::beast::detail::base64;

std::string beast_encode(const std::string &in) {
  std::string out(b64::encoded_size(in.size()), '\0');
  std::size_t n = b64::encode(out.data(), in.data(), in.size());
  out.resize(n);
  return out;
}

int main() {
  std::printf("%s\n", beast_encode("Mane").c_str());
  std::printf("%s\n", beast_encode("M").c_str());
}

「Mane」をエンコードすると TWFuZQ==、単独のMバイトをエンコードすると TQ== - OpenSSLラッパーが生んだのと同じバイトで、心配するNULもなく、剥がす行もない。2つのことを整理しておきましょう。第一に出所:ソースはVinnie Falcoの2016-2019年著作権で、フッターは2004-2008年のRene Nyffeneggerのスニペットの一部を帰属性しています - C++のbase64の物語を始めた同じ民謡で、今やBoostの中に、あなたのバイナリの中に、ウェブ全体のWebSocketハンドシェイクのために搭載されています。第二に、実務的なこと:このコーデックはパディングを付け、折り返さない、なので、1行でなければならないもの - トークン、ヘッダ、APIペイロード - に正しい道具です。そして encoded_size の公式はちょうど正しいバッファを与えてくれ、決して近似にはなりません。

Boost.Serialization:パディングの存在を忘れたイテレータ

C++エコシステムで最も古いbase64は、関数ではなく組み合わせ可能なイテレータアダプターのセットで、2002年にRobert RameyがBoostのシリアライズライブラリのために書きました。エンコード方向は2アダプタのチェーンです:生のバイトを8対6で再グループ化する幅トランスフォーマーと、各再グループ化値を文字表の文字に変えるイテレータ:

#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <cstddef>
#include <cstdio>
#include <string>

namespace it = boost::archive::iterators;

std::string boost_iter_encode(const std::string &in) {
  using enc =
      it::base64_from_binary<it::transform_width<const char *, 6, 8>>;
  std::string out(enc(in.data()), enc(in.data() + in.size()));
  switch (in.size() % 3) {
    case 1: out += "=="; break;
    case 2: out += '=';  break;
    default: break;
  }
  return out;
}

int main() {
  std::printf("%s\n", boost_iter_encode("Mane").c_str());
  std::printf("%s\n", boost_iter_encode("M").c_str());
}

イテレータはコアの詰み処理だけをやって、それ以外はしません - パディングも、NULも、改行も、エラーチャネルもありません。コアの詰み処理は失敗しないからです。「Mane」をエンコードすると、イテレータはまっとうな顔で6文字、TWFuZQ を渡します:4バイトの本当のエンコードは8文字で、2002年のイテレータにそれを気にする念頭もありませんでした。switch 文が構造材であり装飾ではない理由がここにあります:グループ1バイト足りないのにパッド2つ、2バイト足りないのにパッド1つ。switch を除いた同じチェーンは、そのステップを忘れたときの手元にあるもので、結果は寛容なデコーダーでは問題なくデコードされ、厳格なデコーダーでは失敗し、あなたのAPI受け手のエラーメッセージを謎にする文字列です。(この同じイテレータファミリーのデコード側が、たった1つの放って置かれたスペースに例外を投げる方で - 姉妹ガイドに詳しく書いてあります。)

40行、依存ゼロ

Base64は小さく、正しいエンコーダーを持つことは立派なことです。そしてC++では、どんな言語よりリターンが良い:std::string がバッファ管理を快適にしてくれるし、公式が最初に正確なサイズを与えてくれ、手作りエンコーダーは何の意見も持たない唯一のもの - NULも、改行も、プラットフォームの癖もない - それは設定ファイルやAPI境界の下でまさに欲しいものです。この版は、64文字のテーブルに対して3バイトグループで詰め込みます:

#include <cstddef>
#include <cstdio>
#include <string>

std::string base64_encode(const std::string &in) {
  static const char *table =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  std::string out;
  out.reserve((in.size() + 2) / 3 * 4);
  const unsigned char *p =
      reinterpret_cast<const unsigned char *>(in.data());
  size_t n = in.size();
  for (size_t i = 0; i < n; i += 3) {
    unsigned v = p[i] << 16;
    if (i + 1 < n) v |= p[i + 1] << 8;
    if (i + 2 < n) v |= p[i + 2];
    out.push_back(table[(v >> 18) & 63]);
    out.push_back(table[(v >> 12) & 63]);
    out.push_back(i + 1 < n ? table[(v >> 6) & 63] : '=');
    out.push_back(i + 2 < n ? table[v & 63] : '=');
  }
  return out;
}

int main() {
  std::printf("%s\n", base64_encode("Mane").c_str());
  std::printf("%s\n", base64_encode("M").c_str());
  std::printf("%s\n", base64_encode("M\312\277").c_str());
}

各部分を見ていきましょう。reserve の行は計算セクションです:(n + 2) / 3 * 4 文字、ぴったりなので、ループ中に再割り当てはありません。const unsigned char * への reinterpret_cast は儀式ではありません - char が符号付きのプラットフォームでは、127を超えるバイトはさもないと負の数字になり、テーブルインデックスに触れた瞬間に実験室コートを着た未定義の挙動になります。各反復は最大3バイトを24ビット値に引き上げ、4つの6ビットの断片をテーブルに通し、末尾にはなかったはずのバイトの代わりに = を出します - i + 1 < n と i + 2 < n のガードがパディングロジックの全部です。「Mane」を与えると TWFuZQ==。単独のMを与えると TQ==。127を超えるバイト - 例の3行目の 0xCA 0xBF ペア - を与えると、出力は純粋なASCIIのままです(Tcq/)。127を超えるバイトはただのバイトで、テーブルはその意味を気にしないからです。40行、依存ゼロ、全エッジケースがあなたが書いた行です。それが全部の要点です。

Windows CryptoAPI: OS内蔵のパッカー

Windowsには、この記事の大半のフレームワークより古い、オペレーティングシステムそのものにbase64エンコーダーがあります:wincrypt.h の CryptBinaryToStringA、crypt32.lib にあり、何十年もWindowsに同梱されてきたCryptoAPIの一部です。バイト配列をフォーマットされた文字列に変換し、そのフラグテーブルはフォーマット全体の歴史のメニューのように読めます:

フラグ 値 得られるもの
CRYPT_STRING_BASE64HEADER 0x0 証明書のBEGIN/ENDヘッダ行で包まれたBase64
CRYPT_STRING_BASE64 0x1 ヘッダなしの素のbase64
CRYPT_STRING_BASE64URI 0xD RFC 4648セクション5に従い、+ が - に、/ が _ になるURLセーフ文字表
CRYPT_STRING_NOCRLF 0x40000000 末尾に改行を付けない
CRYPT_STRING_NOCR 0x80000000 デフォルトのCRLFの代わりに素のLF

まず知っておくべきはデフォルトです:CRYPT_STRING_NOCRLF を渡さない限り、関数は文字列の最後にキャリッジリターン/改行のペアを付けます - ドキュメント化された動作は、すべてのバイナリ以外のフォーマットが改行シーケンスを得る、というものです - だから1行に収まらなければならないbase64トークンは BASE64 | NOCRLF を望み、その組み合わせが慣例的な呼び出しです。第二に、呼び出し規約です。これは古典的なWindowsの2ステップです:NULLバッファで呼び出して必要な空間を問い合わせる(答えは終端NULを含む)、割り当て、もう一度呼び出して、NULを含まない長さを読み戻す:

#include <windows.h>
#include <wincrypt.h>
#include <cstddef>
#include <string>

std::string win32_encode(const std::string &in,
                         DWORD flags = CRYPT_STRING_BASE64) {
  DWORD need = 0;
  if (!CryptBinaryToStringA(reinterpret_cast<const BYTE *>(in.data()),
                            static_cast<DWORD>(in.size()),
                            flags | CRYPT_STRING_NOCRLF,
                            nullptr, &need))
    return {};
  std::string out(need, '\0');
  DWORD got = 0;
  if (!CryptBinaryToStringA(reinterpret_cast<const BYTE *>(in.data()),
                            static_cast<DWORD>(in.size()),
                            flags | CRYPT_STRING_NOCRLF,
                            out.data(), &got))
    return {};
  out.resize(got);
  return out;
}

さらに2つの注。URIフラグは、この記事全体で唯一のネイティブbase64urlです - Windowsではトークンの文字表を直接エンコードでき、下記のセクションのトランスコード手法は厳密には他のプラットフォーム用です。そして値0の CRYPT_STRING_BASE64HEADER エントリは、ゼロを渡したときにも得られるフラグなので、「フラグなし」を意味したつもりで呼び出すと、ペイロードが証明書ヘッダ行で静かに包まれます - PEM時代のフレーミングの癖で、.pemファイルの生成には役立ち、それ以外のすべてにはサプライズです。crypt32.lib にリンクすれば、その関数はプログラムの残りの人生あなたのものです。

Base64url:トークンとURLのための文字表

標準文字表には、URLを生き延びられない文字が2つあります:+ はクエリ文字列ではスペースを意味し、/ はパスではディレクトリを意味します。RFC 4648セクション5は、2つの文字交換でこれを直します - + が - に、/ が _ に - そして結果については率直です:このエンコードは「base64エンコードと同じものとして扱われてはならない」。これはJWT、OAuth PKCEコードチャレンジ、YouTubeの動画識別子、そしてほとんどのAPIトークンの文字表で、= パディングも普通に落とされます。トークンでは長さは暗黙に知られており、パッドはただパーセントエスケープを待ち望んでいるだけだからです。

この記事のエンコーダーの中で、この文字表をネイティブで出すのはWindowsフラグだけです - OpenSSLにはURLセーフモードがなく、Boostのどちらの風味にもありません - だからほとんどのプラットフォームでは、レシピはこうです:標準でエンコードし、2つの文字を交換し、パッドを落とす。12行で済みます:

#include <cstddef>
#include <cstdio>
#include <string>

/* 「40行、依存ゼロ」セクションの base64_encode */

std::string base64url_encode(const std::string &in, bool pad = false) {
  std::string out = base64_encode(in);
  for (char &c : out) {
    if (c == '+') c = '-';
    else if (c == '/') c = '_';
  }
  if (!pad)
    while (!out.empty() && out.back() == '=')
      out.pop_back();
  return out;
}

int main() {
  std::printf("%s\n", base64url_encode("M\312\277").c_str());
  std::printf("%s\n", base64url_encode("M").c_str());
  std::printf("%s\n", base64url_encode("M", true).c_str());
}

出力の1行目は Tcq_ で、標準文字表ならここで / と書いた場所です。2行目と3行目はパッドスイッチの動きを見せます - デフォルトでパディングなしの TQ、受け手がそれを要求するなら戻ってくる TQ==。考えるべきは pad 引数です。受け手の意見が分かれるからです:JWTセグメントはパッドを望まず、PKCEチャレンジもパッドを望みません。でも、デコーダーが長さを厳格に扱うフィールドに到達するbase64url値はそれを戻し望むかもしれず、スイッチは書き直しではなくboolです。そして逆方向に覚えておくべき失敗モード:標準文字表のペイロードの中の - はただ不正なので、2つの文字表はバイトレベルで相互交換できません - 間違った文字表でエンコードされたトークンはデコードできず、失敗します。セキュリティ境界では、それが欲しい失敗です。

行折返し:64、76、あるいはしない

折り返されたbase64には野に3つの行長があり、それぞれに歴史があります。OpenSSLのストリーミングエンコーダーは64文字でハードに折ります - PEMの癖で、1987年のPrivacy-Enhanced Mail標準が64で折り返したものです。MIMEが1993年にメール向けエンコードを標準化したとき、76文字に移りました。そしてその数字は、coreutilsの base64 コマンド(-w フラグが幅を設定し、-w 0 が折返しを完全に無効化)やエコシステムの大抵のツールのデフォルトです。RFC 4648自体はどちらにも肩入れしません:MIMEの制限として76を引用し、参照する仕様が指示しない限り折り返してはならないと実装に言います。どちらを出すかは受け手次第で、設計上の制約になるのはフォーマットではなく受け手です。

折り返しはエンコード済み文字列への後処理ステップであり、決して入力側のステップではありません:意味の単位は4文字グループなので、幅の倍数で文字列を切ることは安全な切り口です - すべての行境界はグループの間に落ちます。C++版はループです:

#include <cstddef>
#include <cstdio>
#include <string>

/* 「40行、依存ゼロ」セクションの base64_encode */

std::string wrap_lines(std::string s, size_t width = 76) {
  std::string out;
  for (size_t i = 0; i < s.size(); i += width)
    out += s.substr(i, width) + "\r\n";
  return out;
}

int main() {
  std::string mime = wrap_lines(base64_encode(std::string(200, 'x')));
  int lines = 0;
  for (char c : mime)
    if (c == '\n') lines++;
  std::printf("mime: %d lines, %zu chars\n", lines, mime.size());
}

会計です:200バイトは268文字にエンコードされ、76でCRLF終端で折り返すと4行 - 3つの完全な行と40文字の尾 - ワイヤ上276文字です。スニペットのCRLFの選択はメールの選択です。それ以外は、LFが現代的なデフォルトで、交渉の余地のない唯一のルールは一貫性です - CRLFを期待するデコーダーは、厳格であれば素のLFをデータ文字として読んでしまいます。(MIMEのルールはデコーダーは改行を無視しなければならない、というもので、それが理由で、メールはその違いに苦しんだことがありません。)知っておくべき3つ目の癖:openssl base64 コマンド - トレンチコートの enc プログラムで、argv[0] で自分の名前をチェックする - は -A なしで64で折り返し、-A で1行を出し、コマンドラインの中で、挙動を記憶から信頼するのではなく実行ごとに確認する唯一のツールです。

JSONと設定の中のバイナリ

JSON文字列には、生のまま含められない文字の小さなリストがあります:クォート、バックスラッシュ、0x20未満の制御文字です。証明書、ランダムな鍵、署名 - それらはすべて、素の文字列フィールドの中に乗ろうとすればエスケープの滝になるようなバイトで満ちていて、制御文字は一部のパーサーをそのまま詰まらせます。Base64がその直しで、バイナリを運ばなければならないすべての設定フォーマットが与えるデフォルトの答えです:値は文字表のみの文字が1行で保存され、JSONライブラリのクォーティング規則には残された仕事が何もない。

C++のパターンが実装全部です:バイトを読み(もちろんバイナリモードで)、エンコードし、文字列を保存する。受け手が向こう側でデコードします。JSON特有の罠は1つ、それは折り返された文字列です:76文字で折り返された証明書をJSONファイルにそのまま貼り付けると、文字列の中にリテラルの制御文字が満ち、それはパーサーの機嫌次第でパースエラーか、沈黙した破壊のどちらかになります。値が人間の目のために折り返されなければならないなら、エスケープするか、1行にするか - そしてマシン間設定では、1行が答えです。もう一つの罠はラベルのない値です:2014年のmanページでbase64と書かれた設定列は、たいていパディング付きの標準文字表ですが、API時代のトークンはパディングなしのURLセーフで、デコードガイドの4文字テスト - + または /、- または _ を含むか、末尾に = があるか - が診断全部です。

data URI:ページに貼り付けられるファイル

data URIは、ペイロードがアドレスの中にそこそこにあるURLです:data:、任意のメディアタイプ、任意の ;base64 マーカー、カンマ、そしてデータ自体 - RFC 2397のスキーム全体です。ブラウザはこれを使って、追加のリクエストなしに画像、フォント、小さなスクリプトをHTMLやCSSに直接埋め込み、ネットワークを無効にしてもページが動き続けるとき、data URIは有力な容疑者です。C++側では、エンコードの仕事は文字列を組み立てることです。定数1つを使った文字列結合です:

#include <cstdio>
#include <string>

/* 「40行、依存ゼロ」セクションの base64_encode */

std::string make_data_uri(const std::string &mime_type,
                          const std::string &binary) {
  return "data:" + mime_type + ";base64," + base64_encode(binary);
}

int main() {
  std::printf("%s\n", make_data_uri("text/plain", "hi").c_str());
}

罠はすべて詳細の中にあります。;base64 マーカーはちょうど7文字で、1つズレのバグが狙う長さです:6文字をチェックするパーサーは data:text/plain;base4,... を受け入れて、ゴミをまっとうな顔でデコードするパーサーになります。そしてbase64 data URIのペイロードは1行です - 改行はURIの文法の一部ではないので、エンコーダーが画像を76で折り返した場合(MIME風のエンコーダーはデフォルトでそうします)、URIはブラウザに届く前に壊れています。この受け手のためのルール:エンコードし、折り返さず、メディアタイプを正確に保つ - JPEGに image/png とつけるのは、午前2時の壊れたサムネイルとしてしか現れない種類の嘘です。

トークン:JWT、PKCE、APIキー

インターネットで最も身の危険があるbase64は、トークンの中にいます。JSON Web Tokenはドットでくっつけられた3つのbase64urlセグメントです:ヘッダのJSON、claimsのJSON、そして header.claims という文字列に対して計算された署名。C++には組み込みのJWT型がありませんが、ひとつを組み立てるのは、上記のbase64urlエンコーダーとHMAC呼び出し1つです。トークンは全部base64urlで、署名である瞬間まで - 署名になると、もうbase64urlではなくなる、ということです:

#include <cstddef>
#include <cstdio>
#include <string>
#include <openssl/evp.h>
#include <openssl/hmac.h>

/* 前のセクションの base64_encode と base64url_encode */

std::string jwt_hmac256(const std::string &signing_input,
                        const std::string &secret) {
  unsigned char digest[EVP_MAX_MD_SIZE];
  unsigned int len = 0;
  HMAC(EVP_sha256(), secret.data(), static_cast<int>(secret.size()),
       reinterpret_cast<const unsigned char *>(signing_input.data()),
       signing_input.size(), digest, &len);
  return std::string(reinterpret_cast<const char *>(digest), len);
}

int main() {
  const std::string header_json = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
  const std::string claims_json =
      "{\"sub\":\"1234567890\",\"name\":\"John Doe\",\"iat\":1516239022}";
  std::string head = base64url_encode(header_json);
  std::string claims = base64url_encode(claims_json);
  std::string signing_input = head + "." + claims;
  std::string sig = base64url_encode(jwt_hmac256(signing_input, "secret"));
  std::printf("token: %s\n", (signing_input + "." + sig).c_str());
}

例を実行すると、出てくるトークンは教科書的なHS256トークンです:ヘッダは {"alg":"HS256","typ":"JWT"} にデコードされ、claimsはsubject、name、発行時刻タイムスタンプに、そして署名は2つのエンコード済みセグメントに対するHMAC-SHA256のbase64urlです。設計全部を担うのは3つの詳細です。署名入力として符号化する対象は、エンコード済みセグメントであり、生のJSONではありません - JSONに署名したら、間違ったバイトに署名したことになります。セグメントはパディングなしのbase64urlです - パッドはURLの真ん中に座ることになり、文字表の全部の目的はトークンを1つのきれいな文字列に保つことでした。そしてHS256は共有シークレットを意味し、それはサーバー間アルゴリズムです:クライアントのコードにいるシークレットはシークレットではなく、それが署名するトークンは認証情報ではありません。(OAuthのPKCEフローは1つ離れた場所で同じ文字表を使います:ランダムなverifierをSHA-256でハッシュし、パッドなしでbase64urlしてcode challengeにする - base64urlセクションのエンコーダーがクライアント側実装の全部です。)

HTTP Basic認証

HTTPで最も古いbase64は認証情報ヘッダです:Authorization: Basic の後に来るのは user:password のbase64で、JSONより古いほど古いスキームです。組み立ては結合です:

#include <cstdio>
#include <string>

/* 「40行、依存ゼロ」セクションの base64_encode */

std::string basic_auth_header(const std::string &user,
                              const std::string &pass) {
  return "Basic " + base64_encode(user + ":" + pass);
}

int main() {
  std::printf("%s\n", basic_auth_header("user", "password").c_str());
}

出力は、キャプチャしたリクエストで見たことがあるかもしれない文字列です:Basic dXNlcjpwYXNzd29yZA==。C++の注が2つ。結合 user + ":" + pass は、コロンを含むパスワードが向こう側の素朴なパーサーを混乱させる場所です - パースのルールは「最初のコロンで分割」なので、組み立て側はどちらのフィールドにも何でも入れるのが自由だからです。そして認証情報がASCIIでなければ、スキームの安全な読み方は、user-idとパスワードをbase64する前にUTF-8として扱うことで、C++ではあなたの std::string がすでにその仕事をしていることになります - UTF-8バイトで埋めていて、ロケールが何をかんでもで埋めていなければ。このスキームのすべての言及に属するのはセキュリティ注です:Basic認証は隠蔽であり、保護ではありません。ヘッダはネットワークを読める誰にとってもプレーンで乗っているので、TLSの後ろでのみ許容でき、それでもそれは人のためではなく、マシン間呼び出しのための選択です。(Boost.Beastのコーデック - detail:: 名前空間のやつ - はBoostのWebSocket実装の中で同じヘッダの仕事をし、Sec-WebSocket-Accept キーになるSHA-1ダイジェストをbase64化します。これはこのパターンが2017年からこれをやってきたという静かな証拠です。)

メール:7ビットのルール、Base64の回答

メールこそがbase64に癖を覚えさせた場所で、その癖は今日でも構造材です。SMTPは元の形では7ビットASCIIを運ぶよう作られていたので、バイナリは旅行できる前に印字可能なテキストに書き直さなければなりませんでした。Privacy-Enhanced Mailは1987年に64文字行と、末尾に貼り付けられたRSA-MD2/MD5メッセージ完全性チェックでそれを行い、MIMEが1993年にメール向けエンコードを標準化したとき、制限を76文字に緩め、準拠デコーダーは改行を無視すればよいというルールを追加しました。今日のメール添付ファイルもまだ76で折り返されたbase64で、正確な計算は 4/3 掛け 78/76 - 元のサイズの約137パーセント、それに約814バイトのヘッダが加わります。

C++側は、上記のエンコーダーと折返し関数です - 1行にエンコードし、76でCRLF折返し、完了。メール特有の詳細は2つ:最後の行に末尾改行があるかどうかは両方あり得る(デコーダーは無視することが義務づけられているので、どちらでも合法で、どちらも普通)、そして折り返された値はJSON値でも、環境変数でも、トークンでもない - それはMIMEボディに属するブロブで、それをどこかに移動するのが、折返しが習慣からバグに変わり始める場所です。逆方向 - 76で折り返されて到着する添付ファイル - は姉妹ガイドの領分で、4つのC++デコーダーが改行について4つの違う方法で意見が分かれます。

ファイル、ストリーム、そして2ギガバイトの天井

ファイルのエンコードは、デコードガイドのファイル作業の鏡像です:バイナリモードで開く(Windowsではテキストモードの読み取りがCRLFペアを単一の改行に変換し、エンコーダーがそれを見る前にデータを変えてしまう)、バイトを読み、エンコードし、バイナリで書き込む。小さいファイル版は1関数の仕事です:

#include <cstdio>
#include <fstream>
#include <iterator>
#include <string>
#include <vector>

/* 「40行、依存ゼロ」セクションの base64_encode */

std::string encode_file(const std::string &path) {
  std::ifstream in(path, std::ios::binary);
  if (!in) return {};
  std::vector<unsigned char> bytes{std::istreambuf_iterator<char>(in),
                                   std::istreambuf_iterator<char>()};
  return base64_encode(
      std::string(reinterpret_cast<const char *>(bytes.data()), bytes.size()));
}

int main() {
  std::string b64 = encode_file("/etc/hostname");
  std::printf("file -> %zu chars\n", b64.size());
}

天井は、セクションタイトルにあるC++特有の事実です:EVP APIのすべての長さパラメータは int です。そのため、単一の EVP_EncodeBlock 呼び出しがエンコードできるのは最大で約2 GBの入力で、その呼び出しの出力バッファ - 1.33倍大きい - は int に到底入りません。天井未満では、ブロックAPIはメモリに収まるファイルに問題ありません。天井を超えたり、メモリに置きたくないファイルには、チャンク化します - そしてチャンク化のルールは、ループへのbase64特有の唯一の制約です:チャンクは3バイトの倍数でなければなりません。グループ化が3つごとだからで、グループの真ん中にあるチャンク境界は出力を変えてしまいます。3072 - 1024バイトチャンク3つ - は快適なチャンクサイズで、ループはこうなります:

#include <algorithm>
#include <cstddef>
#include <string>

/* 「40行、依存ゼロ」セクションの base64_encode */

std::string encode_streamed(const std::string &data) {
  std::string out;
  for (size_t pos = 0; pos < data.size();) {
    size_t take = std::min<size_t>(3072, data.size() - pos);
    out += base64_encode(data.substr(pos, take));
    pos += take;
  }
  return out;
}

各チャンクは独立にエンコードされ、連結はワンショット結果と同一です - それがチャンク化を安全にする性質で、3バイトのグループ化からそのまま落ちてきます。(エンコーダーセクションのOpenSSLストリーミングコンテキストは同じ仕事を、64文字行折返しを無料付きでやり、受け手がMIMEの形を望むときの正しい道具です。)そして出力側は入力側と同じ予算があります:10 GBのファイルは13.3 GBの文字列になり、バッファ - または書き込んでいるファイル - は計算セクションの公式でサイズ決められ、int の天井は2 GB以上ではチャンク化パスが便宜ではなく、唯一のパスであると言っています。

環境変数とコマンドライン

環境変数はJSON文字列と同じ問題を抱え、答えはもっと悪いです:NULバイトは全然運べず、制御文字とも友好関係にはなれません。標準のトリックはペイロードをbase64にしてシェルを生き延びさせることで、C++ではエンコード方向は1行で済みます:

#include <cstdio>
#include <cstdlib>
#include <string>

/* 「40行、依存ゼロ」セクションの base64_encode */

int main() {
  setenv("MY_PAYLOAD", base64_encode("hello, env").c_str(), 1);
  std::printf("env: %s\n", getenv("MY_PAYLOAD"));
}

環境に届く値は aGVsbG8sIGVudg== です:純粋な文字表で、シェルセーフ、.envファイルセーフ、CIダッシュボードセーフ、base64デコーダーがあるどんなマシンでもデコード可能。コマンドライン自体は、デコード側と同じ2ツールの話で、エンコード方向のフラグが付いています:coreutilsの base64(新しいディストリビューションが同梱するuutils再実装;base64 --version で確認)はデフォルトで76で折り返し、-w 0 が1行をくれます;openssl base64 - argv[0] で自分の名前をチェックしてbase64モードに切り替わる enc プログラム - は64で折り返し、1行には -A を取ります:

# 1行、トークンと設定用
base64 -w 0 < payload.bin > payload.b64
openssl base64 -A < payload.bin > payload.b64

# 折り返し、メールとテキストファイル用
base64 < payload.bin > payload-76.b64
openssl base64 < payload.bin > payload-64.b64

どちらもbase64urlをネイティブで話せないので、シェルで鋳造したトークンは、URLに入る前にトランスコードの扱いを受けます。そしてコマンドラインは、エンコード側の沈黙の失敗の癖が最も危険な場所です:受け手が1行を期待したのに折り返したエンコーダーはエラーを出しません。ただ、改行入りの文字列を生むだけです - それが今あなたが本番で追っているまさにその失敗です。大切なものには、プログラムの中でエンコードしてください。バッファは公式でサイズ決められ、行の形があなたが制御する変数になるからです。

罠:C++版

  • 頼んでもいないNUL。 EVP_EncodeBlock はペイロードの後にNUL終端を付けます。manページの例:入力16バイト、エンコード24バイトにNUL、バッファ内25、返り値24。追加バイト分をサイズ決めし、返り値に合わせてresizeしてください。しなければ、あなたのトークンはゼロバイトで終わります。
  • ハード64。 OpenSSLのストリーミングAPIは64文字で折り返し、すべてのブロックが改行で終わり、それを変えるフラグはありません。1行の受け手の中の折り返し済みエンコーダー出力は、制御文字バグです。
  • 48バイトブロック。 EVP_EncodeUpdate が出力を出すのは完全な48バイト入力ブロックに対してだけです。残りは EVP_EncodeFinal までコンテキストの中で座っています。ブロックごとに出力65バイトとNULを予算付け、*outl を「自分のペイロードのバイト数」として読まないでください - それはこの呼び出しが書き込んだバイト数で、小さい最初の呼び出しではゼロです。
  • イテレータの欠けたパッド。 Boost.Serializationのチェーンは = を絶対に出力しません。「Mane」をエンコードした2002年のイテレータは6文字をくれます。パッドを自分で付けなければ、厳格な受け手は文字列を拒否します。
  • 頼んでもいないCRLF。 CryptBinaryToStringA は CRYPT_STRING_NOCRLF を渡さない限りCR/LFペアを付けます。デフォルトフラグで組み立てたbase64トークンは、本来の長さより2文字長く、末尾から2番目の文字はキャリッジリターンです。
  • NULL呼び出しはNULを数える。 Windowsのサイズ探索は、終端ヌルを含む必要長を返し、本当の呼び出しはそれを含まない長さを返します。2つを混同するのは古典的な1つズレで、バッファを1バイト越えて書いたり、最後の文字を失ったりします。
  • 折り返しは後で、途中でなく。 行折返しはエンコード済み文字列への後処理ステップです。幅の倍数で切りなさい - 常に安全。4文字グループはどれも自己完結している - そして生のバイトを折り返さないでください。改行が属するのはそこではありません。
  • 3つごとのチャンク。 大きなペイロードを断片でエンコードするなら、断片の境界は3バイトグループに落ちなければなりません。そうでなければグループ化 - したがって出力 - が変わります。3072は好意的なチャンク、3071はバグです。
  • int、size_tではない。 すべてのEVP長さパラメータは int です。1回呼び出しの天井は入力で約2 GBで、その入力の出力は int に到底入りません。天井を超えたら、チャンク化またはストリーミングパスは好みではありません。
  • 符号付きchar。 unsignedキャストなしで char * から詰めると、char が符号付きのプラットフォームで127を超えるバイトは負の数字になり、それでのテーブルインデックスは未定義の挙動です。const unsigned char * は儀式ではありません。
  • 折り返されたJSON文字列。 76文字で折り返された値をJSONファイルに貼り付けると、リテラル制御文字の文字列になります。1行にするか、エスケープするか、JSONにいないかの3つです。
  • パッドは契約。 パディングを望む受け手(MIME、大半のデコーダー)もいれば、望まない受け手(JWT、PKCE、URLの中のトークン)もいて、いくつかの厳格な受け手は、欠けたパッドや非正規のパッドをそのまま拒否します。パッドは装飾ではなく、フォーマットの契約の一部です。
  • 2つの文字表。 標準文字表のペイロードの中の - や _ は不正で、URLセーフの中の + や / は不正です。文字表はバイトレベルで相互交換できません - 行き先に合わせて正しいほうでエンコードし、意図的にトランスコードしてください。
  • std::stringとstrlen。 std::string はゼロバイトを喜んで含みますが、C文字列をレガシーAPIに渡した瞬間、strlen は最初のNULで止まります。ポインタと長さを渡し、素のポインタだけを渡さないでください。
  • 予算。 出力は入力の4/3です:入力が1.5 GBなら、出力は2 GB - それは int の天井でもあります。受け手バッファ、列、ワイヤは推測ではなく公式でサイズ決めなさい。

C++がBase64を得るまで

フォーマットの歴史は言語の現代より古く、C++の話は、言語が繰り返しそれを同梱しなかった話です。現在MIME base64と呼ばれるこのエンコードの最初の標準化された利用は、Privacy-Enhanced Mailプロトコルでした:1987年に提案され、64文字行と、末尾に貼り付けられたRSA-MD2/MD5メッセージ完全性チェックを持ち、「base64」という名前自体が初めてやって来たのは1993年で、MIME標準がそう名付けたときです。C++はC++98として1998年に登場しました - MIMEの5年後 - そしてこの言語の開発者が最初に手を伸ばしたbase64コードは、2004-2008年のRene NyffeneggerのCペアで、2008年12月4日のStack Overflowの質問がそれをウェブ中に広めました。その話で最高の部分:ある答えはNyffenegger自身のページにリンクし、ライセンスヘッダごと実装を運び、高評価の答えは彼のソリューションを他の出場者とベンチマークしました。その民謡にはライセンスヘッダがある - 作曲家本人はコメントに一度も現れなかったのです。

それからエコシステムは、エコシステムがやることをやりました。2002年、Robert RameyのBoost.Serializationがイテレータアダプターを搭載しました - C++の工具箱で最も古いbase64で、デコード方向は厳格で、エンコード方向は有名なパディングなしで、RFC 3548がすでに強制していた文字表ルールを条文にする1年前のことです。2017年、Boost 1.66がBeastを持ち込み、そのヘッダーオンリーのコーデックは今日でもフッターにNyffenegger帰属性とともに搭載されています。OpenSSLの EVP_EncodeBlock とその仲間はすべてのOpenSSLリリースにあるので、主力は、言語が標準に入れるべきか議論しているのと同じだけ工具箱にいます。Windowsでは、話は単にオペレーティングシステムが出した、ということです:関数1つ、フラグテーブル1つ、標準は関与しません。その間、標準自体はC++11、C++14、C++17、C++20、C++23(2024年発行)、そして今C++26を経て、それらすべての、すべてが64文字の文字表を見て先へ進みました。C++26の技術的な内容は2026年3月のイギリスCroydonでのISO C++会議で完成し、採択されました(114-12-3)。テキストコーデック作業のための新しい <text_encoding> ヘッダは確かに追加されます。委員会の続く会議、2026年6月(ブルノ)と2026年11月(ブジウス、ブラジル)は、C++26を再検討するのではなくC++29の作業草案を開きます。Base64は標準にありません。8つの標準、30年、テキストエンコードのためのヘッダは1つ - 委員会はbase64を追加するあらゆる言い訳が手元に来たのに、それをすべて断ってきたのです。C++におけるbase64の実用的な歴史は、そしてこれからも、そのライブラリの歴史です:EVPのペア、Boostの2つの風味、Windowsのフラグ、そしてあなたが持つ40行のスニペット。

知っておくとよい雑学

  • OpenSSLのストリーミングエンコーダーの48バイトブロックは、どのRFCにも登場しない数字です。それは16のbase64グループで、出力行がちょうど64文字 - PEMの癖 - になるよう選ばれ、1987年が2026年にもまだ構造材の仕事をしている最後の場所の1つです。
  • Boost.Beastの encoded_size は、計算セクションを constexpr 関数にしたものです:4 * ((n + 2) / 3) で、定数を渡せばコンパイル時に評価されます。標準ライブラリはこの1行を手に入れられず、Boostは代わりに detail:: 名前空間に搭載しました。
  • 考えられる最小のパディング付きbase64は4文字、QQ==:2文字のコスチュームをまとった1バイトです。最小のパディングなしは2文字、QQ。パッドの数もメッセージです:パッド2つは最後のグループが1バイトだったことを、パッド1つは2バイトだったことを、パッドなしは3バイトだったことを意味し、受け手は尾だけでも入力の長さを回復できます。
  • MIMEのオーバーヘッド計算は正確です:4/3 掛け 78/76。だからメール添付ファイルは元のサイズの約137パーセントで到着し、それに約814バイトのヘッダ。この記事のすべてのエンコーダーは同じ税金を払い、折返し幅が変わるのは請求のしかただけです。
  • 典型的なlibstdc++やMSVCでは、std::string は小文字列最適化により、割り当てずにスタックバッファで小さなペイロードを運びます。9バイトの入力は12文字にエンコードされ、ヒープには一切触れません。あなたのトークンのbase64形は、文字通りスタックフレームの中に住んでいるかもしれません。標準ライブラリは宣伝しないタイプの無料昼食です。
  • シェルで手を伸ばすかもしれない openssl base64 コマンドは、そもそもコマンドではありません。enc プログラムが argv[0] で自分の名前をチェックして、人格を切り替えているだけです。文字列比較によるアリアス。あれはC++流のやり方で、Cでやっているのです。
  • YouTubeの動画識別子はbase64urlです:11文字、パディングなし、URLの近くには + や / がどこにもない。地球上で最も視聴されているエンコードフォーマットは、RFC 4648が1ページに収まるセクションで追加した「URLとファイル名セーフ」のバリアントで動いています。
  • Aを4つ - AAAA - は3つのゼロバイトをエンコードします。Aは文字表のゼロだからです。1文字だけでできたbase64ブロブを見たことがあれば、今ならそれが何を言っていたかが分かります:何も、と。
  • 同じ関数のペアは、2008年のStack Overflowの質問の答えに、帰属性フッター付きのBoost.Beastのソースに、無数のプライベートコードベースのヘッダファイルに登場します。C++開発者にbase64の出所を聞くと、最も誠実な答えは「分からない。インターネットにも分からない」です。

もう一方の方向

あなたがさっき詰んだものは全部、向こう側で同じ工具箱が解かれます。解く側には自分なりの癖のセットがあります:末尾をゼロ埋めするOpenSSLのワンショット関数、パディング付き入力のストリーミングデコーダーの返り値を変えた2025年のバグ修正、放って置かれた文字で止まって一言も言わないBoost.Beastのdecode、1つのスペースに例外を投げるイテレータ、そして傷をつけた正確なバイトを指す40行の厳格なデコーダー。解くことの話 - 4つのデコーダーの気性、base64urlのトランスコード、ファイル、MIMEの76文字の癖、そして沈黙に失敗する2つのコマンドラインツール - は姉妹サイトのC++デコードガイドにあります。読みに行き、それから戻ってきて、何か大きいものを詰んでください。それが全部のゲームです:標準ライブラリなし、改行とNULについて4つの違う意見を持つ4人のベンダー、この記事の全バッファのサイズを決める1つの公式、そしてすべての受け手が返金してもらえる1つの33パーセントの税金。楽しい詰込みを。

最終更新: 2026-10-10

関連記事: C++(Cpp)での Base64 デコード:完全ガイド