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

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

バイトを手にしている。JSONレスポンスの中で旅行しなければならないPNG、URLに収まらねばならないトークン、文字と数字だけを受け入れるシステムに入ろうとしている一行のテキスト。手にしている byte[] と、渡らねばならないチャネルのその中間のどこかで、C#はBase64エンコーダーのメニューを提供し、そこから選ぶことがこの主題の本当の技術です。クラシックなワンライナは2003年からフレームワークの中にあり、スパンベースとURLセーフの選択肢はモダンなランタイムとともにやって来て、それぞれがサイズ、改行、文字表について違った約束をします。この記事はそのメニュー全体を歩き、エンコーダーに頼まれるすべての現実的な仕事に、動く例を備えています。

まずハウスのルールを、息継ぎなしに:このサイトのホームページがフォーマットを深く説明しているから。エンコーダーはバイト3つを取るたびに64文字の文字表から4文字を書き、末尾を1つか2つの = 文字でパディングする。だからあなたのデータは、到着したときより約33パーセント太って出て行きます。その数字こそが、どんなコードではなく、この記事で最も重要な事実で、以下のすべては、それを賢く支払う話です。

エンコーダーのメニュー:ツールを選ぼう

ここに、.NET世界のエンコードAPIのファミリー全体を、それぞれがどんな場面のために作られているかとともに示します。ここに挙げたものはすべてランタイムそのものの中にありますが、古いフレームワークでのURLセーフクラスだけは小さなNuGetパッケージに載ってやって来ます:

API 利用可能になった時期 用途
Convert.ToBase64String(byte[]) .NET Framework 1.1(2003) 定番。配列全体がはいり、パディング付きの文字列がでる。オプションもなければ、驚きもない。
Convert.ToBase64String(byte[], int, int) .NET Framework 1.1(2003) より大きな配列の一片を、先に切り出してコピーせずにエンコード。
Convert.ToBase64String(byte[], Base64FormattingOptions) .NET 2.0(2005) ダイヤル付きの定番:76文字ごとに改行を挿入するか選択できる、MIME流。
Convert.ToBase64String(ReadOnlySpan<byte>, Base64FormattingOptions) .NET Core 2.1(2018) スパン版:バッファへのビューをエンコードし、配列コピーも一片のアロケーションもない。
Convert.ToBase64CharArray(byte[], int, int, char[], int) .NET Framework 1.1(2003) 自分が持つ文字バッファに書き、何文字使ったかを返してくれる。
Convert.TryToBase64Chars(ReadOnlySpan<byte>, Span<char>, out int, ...) .NET Core 2.1(2018) 例外ではなくブール値:バッファに収まればエンコードし、収まらなければ false を報告。
System.Buffers.Text.Base64.EncodeToUtf8, EncodeToUtf8InPlace .NET Core 2.1(2018) 厳格なスパンファミリー:例外ではなく状態コード、そしてすでに自分が持つバッファのインプレース膨張。
System.Buffers.Text.Base64Url.EncodeToString と兄弟たち .NET 9(2024) URLセーフの文字表、パディングなしで出力。.NET Framework 4.6.2以降および.NET Standard 2.0では:Microsoft.Bcl.Memory NuGetパッケージ。
ToBase64Transform + CryptoStream .NET Framework 1.1(2003) ストリーミング:ファイルが流れるにつれエンコードし、ペイロード全体をメモリに持つことは決してない。

プロジェクトが2018年以降の.NETバージョンをターゲットにしているなら、最初の7行と、最下部のストリーミングのペアは箱に入っています。Base64Url には.NET 9以降が必要で、それより古いものでは Microsoft.Bcl.Memory パッケージが必要です。今後の注記も1つ:執筆時点ではプレビュー中の.NET 11のライブラリは2026年後半の一般リリースが予定されており、既存の型にさらなるBase64の便利APIとオーバーロードを追加します。メニューはこれからも大きくなります。この記事ではそれ以外のパッケージは必要ありません。

標準の呼び出し:Convert.ToBase64String

C#でのエンコード生活の9割は、たった1つの呼び出しです。バイトを渡せば、それらを運ぶ文字列を返してくれます:

using System;
using System.Text;

string text = "Man";
byte[] bytes = Encoding.UTF8.GetBytes(text);
string packed = Convert.ToBase64String(bytes);
Console.WriteLine(packed);
// TWFu

2ステップの形に注目してください。これがC#で最も多い「なぜ私のBase64が一致しないのか」という質問の答えだからです。string を直接取るオーバーロードはありません。それも設計によるものです:C#の文字列はUTF-16であり、あなたが「このテキストをエンコードして」と言ったとき、どのバイトを意味していたかを推測することをフレームワークは拒否します。まず Encoding.UTF8.GetBytes(またはデータが本当に持っている文字セット)でバイト表現を選び、それから初めてBase64のステップが起きます。クラシックなファミリーの残りは、ウエストをきゅっと絞った同じ呼び出しです:(byte[], int, int) オーバーロードはバッファの一片を、一片を切り出さずにエンコードし、スパンオーバーロードは ReadOnlySpan<byte> から同じことをします。データがより大きな読み取りバッファへのウィンドウであるとき、これが正しい道具です。クラシックなエンコーダーのもう1つの性質を率直に述べておきます:それは決して失敗せず、決して訊きません。常に標準文字表を出力し、常にパディングを含み、同じ入力には常に同じ文字列を返すので、Base64文字列は、それを生んだバイトの信頼できる指紋です。

76文字の質問:改行とBase64FormattingOptions

クラシックなエンコーダーにはダイヤルが1つあり、.NET 2.0からそこにあります:Base64FormattingOptions。それを InsertLineBreaks にすると、エンコーダーは出力の76文字ごとに改行を挿入します。MIME仕様がメール添付で使う行長です。None にするか、オプションのないオーバーロードを使うと、長い折り返しなしの1つの文字列が得られます:

using System;

byte[] bytes = new byte[90];
string plain = Convert.ToBase64String(bytes);
string wrapped = Convert.ToBase64String(bytes,
  Base64FormattingOptions.InsertLineBreaks);

Console.WriteLine(plain.Length);   // 120
Console.WriteLine(wrapped.Length); // 122、76文字目の後に改行が1つ追加された

そのダイヤルについて、実務に関わる2つの詳細があります。第一に、挿入される改行はWindowsのペア、キャリッジリターン+改行で、裸の改行ではありません。したがって折り返された出力は \r\n シーケンスを含み、後で \n だけを除いて文字列を「きれいにする」コードは、データの中に隠れた余計なキャリッジリターンが残った状態になります。第二に、折り返しはエンコード済み出力の76文字で行われます。だからこそMIME標準は、76文字または78文字の行制限を持つメール転送が、4文字のBase64グループを絶対に改行で割らないことを保証できたのです:76は4の倍数なので、すべての行がグループ境界で終わります。メール本文、PEM風のテキストブロック、レガシーなメールパイプラインが運ぶものを生成するときには、折り返し形が欲しい。その他のすべてでは折り返しなし形が欲しい:JSONペイロード、URLトークン、APIレスポンス、そして驚きが苦手な厳格なパーサーにデコードされるファイル。そしてJWTの中では、折り返し形は決して欲しくありません。その仕様は改行、空白、さらにはパディングまでも明示的に禁じているからです。

出力を所有する:charバッファとTry API

文字列が目的ではなく、バッファが目的なときがあります。固定サイズのchar配列に追加を書き込んでいたり、プロトコルフレームに書き込んでいたり、単にランタイムが自分のために出力を割り当てるのが嫌なだけです。そうした瞬間のために、エンコーダーには1.1の時代からcharバッファモードがあり、スパンの時代から Try モードがあります。charバッファのメソッドはあなたが提供する配列に書き、何文字使ったかを教えてくれるので、バッファのサイズ決めはあなたの仕事です。標準ライブラリはサイズ計算式すら手渡してくれます:

using System.Buffers.Text;
using System.Text;

byte[] bytes = Encoding.ASCII.GetBytes("Man");
char[] buffer = new char[Base64.GetMaxEncodedToUtf8Length(bytes.Length)];
int written = Convert.ToBase64CharArray(bytes, 0, bytes.Length, buffer, 0);

string packed = new string(buffer, 0, written);
Console.WriteLine(packed);
// TWFu

Try の兄弟はスパンから同じ仕事を行い、ブール値で答えます。入力をあなたの目的地スパンにエンコードし、outパラメータに文字数を報告し、目的地が小さすぎたなら何も書かず false を返します。最後のこの性質が、信頼できない入力のサイズでも安全に使えるようにします:失敗した呼び出しから半分埋まったバッファを渡されることは決してないからです:

using System;

byte[] bytes = { 1, 2, 3 };
char[] buffer = new char[4];

if (Convert.TryToBase64Chars(bytes, buffer, out int written,
  Base64FormattingOptions.None))
{
  Console.WriteLine(new string(buffer, 0, written));
  // AQID
}
else
{
  Console.WriteLine("Buffer too small, nothing was written.");
}

System.Buffers.Text.Base64 の厳格なスパンファミリーには、ブール値の代わりに OperationStatus 契約を持つ同じ形があります:EncodeToUtf8 はあなたが所有するバイトスパンを満たし、状態をもって、それが完了したのか、場所が尽きたのか、さらに入力が必要なのかを告げ、EncodeToUtf8InPlace は、バイナリデータがすでにあなたが成長させようとするバッファの中に座っているときに手を伸ばすべきものです:エンコードはデータを膨張させるので、メソッドは同じバッファの末尾の上にBase64テキストを書き、結果がどれほど長いかを報告します。これらすべてはサイズについて1つのルールを共有しています:入力バイト n の出力は、パディング込みで常に 4 * ceil(n / 3) 文字で、GetMaxEncodedToUtf8Length と Base64Url.GetEncodedLength のヘルパーがその計算を実装しています - 後者はパディングなしの長さ用で、常にパディング付きのサイズ以下です - だからヘルパーからサイズを決め、記憶した定数からは決して決めないでください。

URLセーフなエンコーダー:Base64Url

標準文字表には、URLが好まない2つの文字があります。クエリ文字列の + は、フォームパースのルールで普通にスペースとしてデコードされ、/ と = はどちらも、パスやパラメータに乗り込む前にパーセントエンコードを望みます。RFC 4648セクション5で定義されたBase64のURLセーフなバリアントは、+ と / を、どこでもエスケープを必要としない - と _ に差し替え、末尾の = パディングを任意にしました。.NET 9から、ランタイムにはそれに専用のクラスがあります:System.Buffers.Text.Base64Url。そしてそれは、初めての人々を驚かせる1つの動作を持っています:パディングを一切出力しないのです:

using System.Buffers.Text;

byte[] bytes = { 1, 2 };
string classic = Convert.ToBase64String(bytes);
string urlSafe = Base64Url.EncodeToString(bytes);

Console.WriteLine(classic); // AQI=
Console.WriteLine(urlSafe); // AQI

その違いが、まさにすべてです。JWTセグメント、アップロード識別子、クエリ文字列の中のトークン、URLパスの中の値:それらはすべてパディングなしのURLセーフな形を望み、Base64Url.EncodeToString はそれを直接与えてくれます。文字表もパディングも、それらのフォーマットが指定する通りに扱われます。このクラスには完全なファミリーがあります:文字列へのエンコード、charスパンへのエンコード、UTF-8バイトスパンへのエンコード、さらにバッファのサイズ決め用 GetEncodedLength と、入ってくる入力の検証用 IsValid。プロジェクトが古いランタイムで動いているなら、Microsoft.Bcl.Memory パッケージを追加してください。Microsoftが.NET Framework 4.6.2以降へのクラスをバックポートするために公開しているものです:

dotnet add package Microsoft.Bcl.Memory

パッケージが使えない場合、手づくり版は、クラシックなエンコーダーにリプレイス2つとトリム1つで、数多くのC#コードベースで出会うことになります:

using System;
using System.Text;

byte[] bytes = Encoding.UTF8.GetBytes("Hello World!");
string packed = Convert.ToBase64String(bytes)
  .Replace('+', '-')
  .Replace('/', '_')
  .TrimEnd('=');

Console.WriteLine(packed);
// SGVsbG8gV29ybGQh、URLセーフでパディングなし

あのチェーンの操作順は注意する価値があります:文字の交換は標準出力の上で行われ、パディングのトリムが最後に行われます。先にトリムしても何も変わりませんがコードは読みにくくなり、トリムの後に交換しても動きますが、微妙なバグが生まれるのはこういうときだからです。トークン、識別子、URLの中に住むものはこの形を使い、標準文字表は、+、/、= がまったく快適に暮らせるメール本文、JSONペイロード、ファイルのために取っておいてください。

エンコーダーに与えるもの:文字列、文字セット、エンコーディングの選択

テキストから始まるエンコードの仕事は、すべて同じ静かな決断から始まります:このテキストはどのバイトになりますか?Base64のステップは決定的で無辜ですが、その前の Encoding ステップこそが、出力が分かれる場所であり、その分岐は沈黙するかもしれません。UTF-8はモダンなウェブでのデフォルトの仮定で、ここでの正しいデフォルトでもあります:すべての言語を往復でき、あなたのペイロードをデコードするとき他のあらゆるプラットフォームが仮定するのはこれであり、Encoding.UTF8 が呼び出し1つでそれをくれます:

using System;
using System.Text;

string original = "h\u00e9llo \u4e16\u754c";
byte[] utf8 = Encoding.UTF8.GetBytes(original);
string packed = Convert.ToBase64String(utf8);
Console.WriteLine(packed);
// aMOpbGxvIOS4lueVjA==

ここで、同じ文字を別の文字セットでエンコードした姿を見て、「同じテキスト」が文字セットを伴わない限りよく定義されたものではないことを理解しましょう:

using System;
using System.Text;

string euro = "\u20ac";
string asUtf8 = Convert.ToBase64String(Encoding.UTF8.GetBytes(euro));
string asLatin1 = Convert.ToBase64String(
  Encoding.GetEncoding("ISO-8859-1").GetBytes(euro));

Console.WriteLine(asUtf8);   // 4oKs
Console.WriteLine(asLatin1); // Pw==

同じユーロ記号に2つの異なるBase64文字列。どちらも完全に有効で、片方だけが向こう側でユーロ記号にデコードされます。最も影響範囲が広い罠は Encoding.Default です:Windows上の.NET FrameworkではシステムのANSIコードページですが、.NET(Core)ではUTF-8です。そのため Encoding.Default でエンコードするプログラムは、2010年のマシンと2025年のマシンで異なるBase64を生み、両方の出力はそれぞれのホームプラットフォームでは「正しく」デコードされます。デコードされたペイロードがアクセント付きの文字化けで埋まって届いたら、元のエンコーディングはデコードが仮定したものと別の文字セットを使ったということで、直しはパイプのこちら側です:書いたチームよりも長生きするコードの中で、エンコーディングを明示的に固定し、両方向で。そして型システム自体についての最後の注:C#の文字列はUTF-16なので、もし生のUTF-16コードユニットをエンコーダーに渡したら(Encoding.Unicode.GetBytes を呼ぶことで)、ASCIIの各文字は2バイトのコストになり、出力はサイズを2倍にしますが何の得にもなりません。向こう側のデコーダーがそれをあなたの元の文字列のバイトとしてではなく、UTF-16テキストとして読むからです。Base64はあなたが与えたバイトを運び、それが何を意味するかは気にしません。

ファイル:ディスクから文字列へ

ファイルは最も一般的なエンコードペイロードで、最も寛容です。文字セットの問題がないからです:ディスク上のバイトがデータで、エンコーダーがそれが単語か波形かを綴っているかどうかを気にすることはありません。往復は読み取り、エンコード、書き込みで、唯一本物の決断は、結果をどこに置くかです:

using System.IO;

byte[] bytes = File.ReadAllBytes("photo.png");
string packed = Convert.ToBase64String(bytes);
File.WriteAllText("photo.b64", packed);

Console.WriteLine(packed.Length + " characters for "
  + bytes.Length + " bytes of image.");

サイズの計算が物語の全部で、トランスポートを選ぶ前にやる価値があります。ファイル1メガバイトはBase64文字1,333,336個になり、C#の文字列は1文字あたり2バイトを格納するので、そのエンコード済み結果は文字列としてメモリで約2.7メガバイトを占めます。10メガバイトのファイルは、マネージドメモリで26メガバイトに座る13メガバイトの文字列になります。写真や設定ブロブなら、どれも問題ではなく、ペイロードが動画なら、下記のストリーミング・エンコーダーを使うのにとても良い理由です。上記のパターンは、メモリに余裕で収まるものすべてに手を伸ばすべきもので、同時に、あらゆる「ファイルをJSONボディの中のBase64としてアップロード」機能が静かに使っているパターンでもあります:ファイルを読み、エンコードし、文字列をJSONに入れて、API層に仕事をさせます。

ウェブ上の画像:data URIの構築

エンコード済み画像の最も目に見える消費者はウェブで、ウェブが「ドキュメントの中に住む画像」のために持つフォーマットがdata URIです:data: スキームの後にMIMEタイプ、;base64 フラグ、カンマ、そしてエンコードされたバイトが続きます。C#で1つを構築するのは文字列連結で、エンコーダーがすべての本当の仕事をしています:

using System.IO;

byte[] png = File.ReadAllBytes("logo.png");
string packed = Convert.ToBase64String(png);
string dataUri = "data:image/png;base64," + packed;

Console.WriteLine(dataUri.Substring(0, 30));
// data:image/png;base64,iVBORw0K

あの出力の iVBORw0KGgo プレフィックスは便利なチェックポイントです:8バイトのPNGシグネチャのBase64形式なので、あなたがエンコードするPNGはすべてその形で始まります。そうでないPNG data URIはPNGではありません。このパターンに属する実務的な注が3つあります。第一に、data URIは画像の完全なコピーで、3分の1膨らんで、あなたのHTMLやCSSに埋め込まれます。ネットワークのリクエストを恒久的なページの重さと交換するので、4 KBのfaviconならお得ですが、4 MBのヒーロー画像なら強盗です。そしてエンコーダーは33パーセントについて交渉しません。第二に、画像が大きいなら、エンコードする前にリサイズするか再圧縮してください。オリジナルの全バイトがページに現れるからです。第三に、ユーザー向けHTMLにおけるユーザー提供のSVGには注意してください:SVGはスクリプトを運べうるため、それを埋め込むこと - インラインでも、<object>/<embed> 経由でも - は古典的なXSSの表面です。素の <img> data URIではモダンなブラウザはそれを実行しませんが、同じマークアップをそうした文脈で再利用すれば実行します。data URIの中のPNG、JPEG、GIF、WebPは無害です。無害でないのはSVGです。

手作業でのJWTの組み立て

JSON Web Tokenをゼロから作ることは通過儀礼で、C#では多くの言語よりも良い儀礼です。部品が短いからです。JWTはドットでつなぎ合わせた3つのbase64urlセグメントです:エンコードされたヘッダ、エンコードされたペイロード、署名。最初の2つはUTF-8 JSONドキュメントで、署名はドットでつなぎ合わせた最初の2セグメントに対して計算されます。以下が組み立ての全体で、代用の署名を付けています。暗号的なステップはあなたの署名鍵に属するもので、Base64の話には属さないからです:

using System;
using System.Buffers.Text;
using System.Text;

string headerJson = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
string payloadJson = "{\"sub\":\"42\",\"name\":\"Ada\"}";

string header = Base64Url.EncodeToString(Encoding.UTF8.GetBytes(headerJson));
string payload = Base64Url.EncodeToString(Encoding.UTF8.GetBytes(payloadJson));
string signature = "c2lnbmF0dXJl"; // 本物のHMACかECDSA値の代わり

string jwt = header + "." + payload + "." + signature;
Console.WriteLine(jwt);
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiIsIm5hbWUiOiJBZGEifQ.c2lnbmF0dXJl

Base64Url.EncodeToString の2つの性質が、あの例で静かな仕事をしています。URLセーフな文字表を出力するので、+ も / もトークンに現れず、パディングを省略するので = も決して現れません。これはJWS仕様が要求するちょうどそのもので、Convert.ToBase64String が助けなしにやらないことそのものです。.NET 9より前のランタイムにいるなら、同じ仕事は標準エンコーダーとURLセーフセクションの修正チェーンを通過します:エンコード、2つの文字を交換、パディングをトリム。セグメントの順序は署名にとって重要で、署名は素のASCIIバイトとしての header プラスドットプラス payload に対して計算されるので、まず2つのセグメントを組み立て、JSONのフォーマット直し版ではなく、その正確な連結に署名してください。そして鋭く保つべき境界:ユーザーが到達しうるものには、JWTをまったく手作業で組み立てないでください。System.IdentityModel.Tokens.Jwt パッケージが、構築、署名、検証、有効期限を代わりに扱ってくれ、そのbase64urlの扱いこそが、まさにこの文字表とパディングルールです。手作業の組み立ては、テスト、デモ、そしてライブラリがまさに何をしているかを理解しなければならない日のためにあります。

HTTPヘッダ:Basic認証

Base64はプレーンHTTPのBasic認証スキームに現れ、エンコード側はプロトコルの中で最も短いヘッダ構築のひとつです:ユーザ名とパスワードをコロンでくっつけ、結果をUTF-8でエンコードし、Base64にし、スキーム名を前置きします:

using System;
using System.Text;

string user = "ada";
string password = "s3cret";
string credentials = user + ":" + password;

string header = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(credentials));
Console.WriteLine(header);
// Basic YWRhOnMzY3JldA==

文字セットが厄介な部分です:RFC 7617は下位互換のためにBasicスキームのデフォルトの文字セットを未定義にし、勧告としてのUTF-8ヒントしか提供していませんが、それがまさにすべてのモダンなサーバが期待するものです。そのため、アクセント付きのユーザ名はプラットフォームのデフォルトではなく Encoding.UTF8 を通すべきで、そうでなければサーバは別のバイト文字列をデコードしてログインを拒否します。ヘッダの中で唯一のエンコーディングがBase64のステップです:結果をパーセントエンコードしない、URLエンコードしない、二重Base64にしない。それらの「親切な」追加ステップはどれも既知のバグで、二重エンコードが最もよくあります。認証情報が、それをすでにBase64化していたレイヤーから事前エンコードされて届くことがあり、2回目のエンコードは、見た目はまともだがサーバで黙って失敗するヘッダを生むからです。スキーム自体についての2つの注意は、セキュリティセクションで薄まってしまう前に、ここに置くためです:Basic認証はパスワードを1つのコマンドで読める形にして送信するので、TLSの上でのみ許容でき、それでもほとんどのAPI作業には間違った道具で、それゆえベアラートークンとJWTが引き継いだのです。これらすべてにおいてエンコーダーの仕事は小さくて誠実なものです:コロンでくっつけた認証情報を、ヘッダに安全な文字列に変えるだけ、それ以上でもなければ以下でもありません。

メール:MIMEとToBase64Transformが折り返さない理由

メールはBase64の歴史的な住処で、76文字の行ルールの由来が今なおそこです:MIME仕様はエンコードされた本文を76文字で折り返し、行の間にはCRLFを入れて、どのSMTPホップもそれを再折り返す理由を持たせないようにしています。C#はこの仕事のために2つのエンコーダーを与えてくれます。それぞれが違った約束をしています。それは選ぶ前に理解する価値があります。第一は、改行セクションで見たクラシックな Convert.ToBase64String 付き InsertLineBreaks で、まさにMIMEの形です:CRLFで76文字に折り返され、Content-Transfer-Encoding: base64 ヘッダの下に貼る準備ができています。第二はストリーミングの兄弟である ToBase64Transform で、ここでサプライズがあります:それは改行を挿入しません。それ用のモードも、オプションも、コンストラクタのフラグもなくて、その出力は1つの長い折り返しなしのストリームです:

using System.IO;
using System.Security.Cryptography;

using FileStream source = File.OpenRead("photo.png");
using MemoryStream destination = new MemoryStream();
using ToBase64Transform transform = new ToBase64Transform();
using CryptoStream encoder = new CryptoStream(source, transform, CryptoStreamMode.Read);

encoder.CopyTo(destination);
Console.WriteLine(destination.Length + " characters, no line breaks");

だから実務的なルールはこうです:小〜中程度のメールペイロードには、バイトを読み、折り返しクラシックなエンコーダーを使います。MIMEの形がそのまま得られるからです。大きな添付には、メモリを平坦に保つために ToBase64Transform でストリーム化し、トランスポートが本当に76文字行を必要とするなら、結果を自分で折り返します。出力をグループ境界で分割して(76文字ごと。改行セクションで説明した通り、常にグループ境界です)。transformが折り返しなしのままであることは正しいことで:それは入力を3バイトのグループで処理し、改行は、パイプの中でバイトを文字に変換しているレイヤーではなく、トランスポートを知っているレイヤーに属するフォーマット判断だからです。

ストリーミング:2回読まずに大きなファイルをエンコードする

ペイロードが動画でも、バックアップでも、文字列で持っているのが恥ずかしい何かであるとき、ストリーミング・エンコーダーが解決策の全体です。パターンはデコード側ストリーミングの鏡像です:ソースファイルの上に CryptoStream を置き、読み取りモードの ToBase64Transform を入れ、ターゲットへの CopyTo。ファイルが流れ込み、Base64が流れ出で、プロセスが持つメモリは、ストリームが内部で使うバッファだけです:

using System.IO;
using System.Security.Cryptography;

using FileStream source = File.OpenRead("video.mp4");
using FileStream target = File.Create("video.b64");
using ToBase64Transform transform = new ToBase64Transform();
using CryptoStream encoder = new CryptoStream(source, transform, CryptoStreamMode.Read);

encoder.CopyTo(target);
Console.WriteLine("Wrote " + target.Length + " characters.");

このパターンについて覚えておく価値がある事実が2つあります。第一に、出力のサイズは入力のサイズによって完全に決まります。3バイトごとに4文字なので、ターゲットの空間を予約し、content-lengthヘッダのために長さを事前に計算し、1バイトが流れる前にディスククォータを予算計上できます。第二に、transformは入力を3バイトのグループで期待し、CryptoStream はその整合性を代わりに扱ってくれ、ファイルが流れゆく間、transformがまさに望むものをちょうど与えます。もし TransformBlock でtransformを手動で運転するなら、3の倍数を与え、TransformFinalBlock に末尾を排水させます。余った1バイトまたは2バイトは、1つか2つパディング文字を持つ最後の不完全なグループになるのです。ほとんどのアプリケーションでは、CopyTo 形式があなたが書くすべてで、それはメモリ制限下でもよく振る舞う形式です。まさに大きなファイルが好きそうな場所に。

設定ファイル、環境変数、データベース

C#アプリケーションにおけるもう一つの一般的なエンコードの仕事が、ストレージの仕事です:シークレットやバイナリブロブを取り、テキストしか受け付けない場所に入れる。環境変数は見える例です。環境変数は定義上、文字列だからです:

using System;
using System.Text;

string secret = "p@ssw0rd+and/symbols";
string packed = Convert.ToBase64String(Encoding.UTF8.GetBytes(secret));
Environment.SetEnvironmentVariable("SECRET_B64", packed);

string back = Encoding.UTF8.GetString(
  Convert.FromBase64String(Environment.GetEnvironmentVariable("SECRET_B64")));
Console.WriteLine(back == secret);
// True

データベースでは同じ考え方は、テキスト列が保持しなければならない byte[] プロパティとして現れることが多く、Entity Framework Coreにはまさにこれのための組み込み機構があります。すべての読み書きであなたのエンコード関数とデコード関数を実行する値コンバータです:

using Microsoft.EntityFrameworkCore;

modelBuilder.Entity<Avatar>()
  .Property(a => a.ImageData)
  .HasConversion(
    v => Convert.ToBase64String(v),
    v => Convert.FromBase64String(v));

そのコンバータがデータベース統合の全体です:C#のコードは byte[] を見、列はBase64文字列を見、往復は呼び出し側から見えません。このセクションに属する注意が2つあります。第一に、列は33パーセントの税金を払っています:エンコード後の長さに合わせてサイズを決めたテキスト列は、同じ幅のバイナリより3分の1少ないデータしか持ちません。固定幅の列があるならBase64の長さに合わせてサイズを決め、varchar(max) や同等のものがあるなら、税金は請求の問題だけです。第二に、これが繰り返しやって来るものです:設定ファイルの中のBase64は形であって、盾ではありません。値を1行に保ち、テキストエディタの邪魔から保ち、ファイルを読める誰にでも1つのコマンドで読める位置にいます。シークレットには本当の保護が必要です。シークレットストア、キーボールト、最低限ファイル権限。そしてBase64は、設定の中に座っている間、シークレットが着ている伝送形式だけです。

コマンドラインから

どんなエンコーダーも15行のコンソール人生に値し、C#のそれは気持ち良いです。出力は、標準出力が作られたための素の文字列だからです。これがツールの全体です:ファイルパスまたは標準入力を取り、エンコードし、Base64をターミナルに書き出します。どんなシェルパイプラインもそこから引き取れるように:

using System;
using System.IO;
using System.Text;

string input = args.Length > 0
  ? File.ReadAllText(args[0])
  : Console.In.ReadToEnd();

byte[] bytes = Encoding.UTF8.GetBytes(input);
Console.WriteLine(Convert.ToBase64String(bytes));

一度ビルドすれば、.NETのエンコーダーの挙動を特に望む日に、シェルのbase64ユーティリティの隣に座っています:同じ文字表、同じパディング、そしてパイプが何を渡そうと、C#ランタイムのUTF-8処理。バイナリファイルには、File.ReadAllText の代わりに File.ReadAllBytes を使った同じ骨格が変更の全部で、出力はテキスト解釈ではなく、ファイルの正確なバイトを記述します。このツールは良いプローブでもあります:ファイルをパイプで通し、出力をデコード記事のデコーダーにパイプで戻し、2つのファイルをdiffしてください。パイプの両側がすべてのバイトについて一致しているという、満足感のあるエンドツーエンドのチェックです。

パディング、あるいは末尾のイコール

Base64文字列の最後の = 文字は、フォーマットの帳簿処理です。C#のエンコーダーはその点で意見が割れており、それが特定で一般的な相互運用バグの源になっています。クラシックな Convert.ToBase64String は常にパディングします。ペアとなるクラシックなデコーダーが常にそれを期待するからです。Base64Url.EncodeToString は決してパディングしません。それが狙うURLセーフの消費者、JWTやトークンAPIが、常にコンパクト形を期待するからです。あなたの出力が逆の期待を持つ世界に跨るとき、直しは演算です。デコード記事が逆の方向に示したのと同じ演算です:

using System;

string padded = Convert.ToBase64String(new byte[] { 1, 2 });
Console.WriteLine(padded);           // AQI=
Console.WriteLine(padded.TrimEnd('=')); // AQI、URLセーフの消費者が望むもの

string compact = "AQI";
string restored = compact + new string('=', (4 - compact.Length % 4) % 4);
Console.WriteLine(restored);         // AQI=、クラシックなデコーダーが望むもの

(4 - length % 4) % 4 の式がパディング宇宙のすべてです:0、1、2の文字を加えて長さを4の倍数に落とし、外側の剰余が、パディング済みの入力が余分なものを増やさないようにします。パディングについての警告が2つあります。心が正しいコードが間違えるのがここだからです。= をデータとして扱うことはやめてください:それは情報を一切持たないので、すでにパディングを含む文字列をペイロードであるかのようにエンコードしたり、クエリ文字列の中で = を %3D にURLエンコードしたりするのは、どちらも見た目は正しく、デコードは誤る出力を生む方法です。さらに、パディングが標準の = ではなく別の文字 - いくつかの古いシステムではドット - で書かれていたレガシーペイロードの小さなファミリーに注意してください:届いた値が、あなたがパディングを期待する場所でドットを使っているなら、デコード前に = に正規化するか、パディングなしでURLセーフのパスを通してください。

どれくらい速く動くか

モダンな.NETでのBase64エンコードは速く、面白い部分はCPUの話ではなく、メモリの話です。ランタイムの実装は、ハードウェアがサポートする場所でSIMDベクトル命令で最適化されており、何メガバイトもの入力が普通のデスクトップマシンで1桁から2桁下のミリ秒でエンコードされ、あなたが書くどんなアプリケーションでもエンコーダーは実質的に無料なほど速いのです。実際にコードを変えるパフォーマンス助言は、形に関わっています。出力はC#の文字列で、C#の文字列は1文字あたり2バイトを格納するので、エンコード済み結果のメモリコストは、入力バイト1バイトあたり約2.7バイトです(入力3バイトごとに4文字、1文字2バイト)。これはペイロードがメガバイト単位で知る価値がある数字です。ループで数千の小さなペイロードをエンコードするなら、呼び出しごとに新しいマネージド文字列を割り当てる文字列APIよりも、再利用するバッファに書き込むスパンとcharバッファのAPIを優先してください。大きなファイル1つをエンコードするなら、文字列を完全にスキップしてストリーミングのtransformを使います。13メガバイトの文字列を持つ1文字2バイトのコストは、CopyTo がワーキングセットをストリームのバッファに保っていたなら、純粋な無駄だからです。そしてMIMEで折り返された出力を生成するなら、折り返しパスはデータへの2回目の旅であることを覚えておき、デフォルトとしてではなく、トランスポートが必要するときだけに折り返してください。

セキュリティの話

Base64のエンコーダー側にはセキュリティの教訓が1つあり、それはデコーダー側の逆です:読み取れるデータをさらす選択をしているのはあなたで、フォーマットはそれを止めません。Base64はエンコーディングであり、暗号化ではありません。鍵もなく、アルゴリズムもなく、いかなる秘密性もなく、あなたの ToBase64String 呼び出しの出力は、どんなマシンでも、どんな言語でも、誰でも、入力から1つのコマンドの距離にあります。だから最初のルールは、あなたがエンコードすることを選ぶものについてのものです:Base64で「保護」された設定ファイルに、パスワード、トークン、シークレットを入れるのはやめてください。保護はちょうどデコード呼び出し1つ分の深さしかなく、設定を読む人にはそのコマンドがあるからです。値がシークレットでなければならないなら、本当の保護が必要で、Base64は、それがテキストフィールドの中に座っている間に着ている形だけです。

2つ目の教訓はチャネルについてで、この記事が作るもの特有です。Basic認証ヘッダはパスワードを、どんなプロキシでも、どんなログでも、どんなミドルボックスでも読める形で運びます。だからこのスキームはTLSの上でのみ許容でき、レガシー統合の外ではほぼ時代遅れです。HTMLの中のdata URIは画像を運び、画像がユーザー提供のSVGなら、SVGが運ぶものを運びます。だからdata URIの中のSVGというケースは、どんなユーザーコンテンツと同じ注意を必要とします。そしてURLの中のBase64値は、文字通り、URLの中にあります。つまりブラウザ履歴、サーバのアクセスログ、リファラヘッダ、プロキシキャッシュの中にあるということです。だから秘密でなければならないトークンは、パディングがあろうとなかろうと、クエリ文字列に属しません。3つのケースすべてで、エンコーダーは誠実な仕事をしています:バイトを運びやすい文字列に変える。セキュリティは、あなたが何を持ち、どこに持っていくかにあり、フォーマットはほとんどのものより良い伝令ですが、伝令であって金庫ではないのです。

C#エンコーダーが落ちる罠

これらはC#コードのエンコード側に繰り返し現れる罠で、どれもフレームワークの動きに具体的な原因があります:

  • あなたが選ばなかった文字セット。 Encoding.Default で文字列をエンコードすると、.NET Framework(WindowsのANSIコードページ)と.NET(UTF-8)で異なるBase64になります。出力はどちらも有効で、どちらもそれぞれのホームプラットフォームでは「正しく」デコードされますが、同じバイトではありません。エンコーディングを明示的に固定してください。
  • 二重エンコード。 入力はすでにBase64でした(エンコード済み値をエンコードした設定、入力を再エンコードするAPI)。エンコーダーは言われたことをそのまま行い、Base64のBase64を生みました。結果はまともに見え、1層ずつデコードされます。デコード2回で直るバグが本番で発見されるのが、まさにこうした形です。
  • 間違った場所の改行。 MIMEで折り返された形、そのCRLFペアを込めて、JSON文字列、JWTセグメント、URLパラメータの中に着地し、厳格な消費者は、期待するよう伝えられたことのない空白で詰まります。メールには折り返しを、その他のすべてには触れないで、誰かの折り返しを剥がすなら \n とともに \r も剥がしてください。
  • URLの中の標準文字表。 クエリ文字列の + はフォームパースのルールでスペースとしてデコードされるので、URLに置かれた標準Base64値は、プラス符号があった場所に文字を持って帰ってきます。URLセーフな文字表を使うか、値全体をパーセントエンコードしてください。両方を同時にやらないでください。
  • パディングの不一致。 あなたの出力はパディング付き、消費者はコンパクトを望む、またはその逆。どちらが間違っているわけではなく - 単に意見が割れているだけです。直しはパディングセクションの演算で、消費者の期待を知っている側に適用します。大抵それはトークンを書く側です。
  • 予算計上されなかったメモリ。 エンコード済み文字列はメモリで1文字2バイトなので、10 MBのファイルは1300万文字の文字列になり、マネージドメモリで約27 MBの重さになり、そうした文字列を1つずつ組み立てるループは、見えない原因の割り当て変動としてプロファイラに現れます。長さヘルパーでバッファをサイズ決めし、大きいものはストリーム化し、ホットループではバッファを再利用してください。
  • 折り返さないtransform。 ToBase64Transform は1つの長い行を出力します。「MIME準備完了」の添付をそれを通してストリーム化してメールするコードは、12万文字の行を生み、いくつかのトランスポートはグループの真ん中でそれを再折り返します。まさに76文字ルールが防ごうとしていた破壊です。
  • エンコードのエンコード。 「データはすでにテキストだ」という理由でBase64文字列をエンコーダーに渡すと、2層目が生まれます。エンコーダーは、入力がBase64のように見えることを知らず、気にもしません。文字列がたまたま持っているだけの文字数をエンコードし、向こう側のデコーダーは、あなたのデータを期待する場所にBase64文字列を受け取ります。

エンコーダーが育った軌跡:バージョンツアー

APIのエンコード側には独自のタイムラインがあり、2番目の.NETリリースから、現在プレビュー中のものまで続きます:

  • .NET Framework 1.1、2003年4月。 Convert.ToBase64String と ToBase64CharArray が到着し、クラシックなファミリー全体が1つのリリースで、一片のオーバーロードまですでに含まれています。2003年のAPIにしては、先見の明の小さな奇跡です。
  • .NET 2.0、2005年。 Base64FormattingOptions と InsertLineBreaks 値がファミリーに加わり、MIMEの行折返しをフレームワークにもたらして、メールコードの手づくり Substring ループの時代を終えました。
  • .NET Core 2.1、2018年。 スパンの時代。Convert がスパンベースのエンコードと TryToBase64Chars を手に入れ、新しい System.Buffers.Text.Base64 クラスが OperationStatus 契約とインプレース膨張を携えて到着。ゼロアロケーションの世界のために作られました。
  • .NET 5、2020年。 16進の兄弟(Convert.ToHexString たち)が出荷され、同じ設計パターンが16文字の文字表に適用され、変換クラスのパターンがハウスタイルになりました。
  • .NET 7、2022年。 X509Certificate2.ExportCertificatePem がフレームワークにPEMを代わりに作らせ、アーマーマーカー、64文字の折返し、Base64本文まで込みで、手動の証明書フォーマットコードのクラス丸ごとを静かに引退させました。
  • .NET 9、2024年11月。 System.Buffers.Text.Base64Url がコミュニティからの何年もの要求を経て箱に入り、Microsoft.Bcl.Memory パッケージが.NET Framework 4.6.2以降にバックポートし、JWTコードがずっと手づくりしてきたパディングなしの挙動も一緒にやってきました。
  • .NET 11、執筆時点ではプレビュー中。 2026年後半のリリースが予定されている次のバージョンは、既存の型にさらなるBase64の便利APIとオーバーロードを追加し、より人間らしい手触りの表面への行進を続けます。

フォーマット自体はより古い伝記を持っていて、それがC#のAPIがこの形に見える理由です。現在MIME Base64と呼んでいるものの最初の標準化された利用は、1987年のPrivacy-Enhanced Mailプロトコル(RFC 989)で、MIME仕様が1993年に76文字の行折返し形式を固定し、2006年のRFC 4648がこのフォーマットに現代的な文字表を意識した仕様を与えました。C#が2024年に初めて第一級のエンコーダーを得たURLセーフなバリアントも含めてです。30年のメールとウェブの慣習が、改行、パディング、2つの文字表が存在する理由で、C#のエンコーダーは、その3つが出会う場所です。

小さな驚異たち

  • 4文字の最小値。 考えられる最小の空でないBase64出力は4文字です。フォーマットは、1バイトしか与えなくても4文字のグループで考えるからです。何の1バイトでも2つの文字と2つの = 記号にエンコードされ、その形 - パディングの帽子をかぶった2つのデータ文字 - は、設定やトークンの中で認識し始めることになる指紋です。
  • ヌルは歓迎される。 エンコーダーはバイトが何を意味するかについて意見を持たないので、ゼロで満たされたバッファは喜んで A 文字の壁にエンコードされ、NULバイトをそのまま持つバイナリファイルは1つたりとも失わずに往復します。「文字列はバイナリを持てない」という不安は、型システム側の文字列側に属するもので、エンコーダーには属しません。エンコーダーは文字列を見ることはないからです。
  • 機能としての決定性。 同じバイト、同じオプション、いつも同じ文字列。タイムスタンプもなく、ランダムなソルトもなく、変動もなく、だからこそBase64文字列はファイルの内容の使い回し可能な指紋として機能します:同じBase64を持つ2つのファイルは同じファイルで、チェックは文字列比較です。
  • 1文字2バイト、無料。 C#の文字列はUTF-16なので、Base64出力の各文字はマネージドメモリで2バイトを占めます。エンコーダーはそれを発表せず、長さプロパティはそれを報告せず、1300万文字の文字列はただ26 MBの重さです。ペイロードが大きいときに頭に入れておくべき数字がこれです。
  • 伝統としてのCRLF。 MIMEの折返しは、コードがLinuxで動いていてもキャリッジリターン-改行ペアを挿入します。ルールはプラットフォームではなく、メール仕様から来ているからです。エンコーダーは変換者であると同時に歴史家で、1993年の行末を2026年のマシンに保存しています。
  • 初日からある一片オーバーロード。 ToBase64String(byte[], int, int) は2003年から、より大きな配列へのウィンドウをエンコードしてきました。スパンがその考え方を流行させる15年も前に。1.1時代のAPI設計者は本当のバッファを見て、オフセットと長さの形を追加し、データがより大きな読み取りの一部であるとき、それは今なお正しい選択です。
  • 64文字の証明書行。 PEMは64文字で折り返し、76文字ではありません。ExportCertificatePem はそれを知っており、それに応じて折り返します。これが「フレームワークにやらせる」が証明書の作業で正しい助言になる、静かな詳細のひとつです。2つの折返し幅、1つのフォーマットファミリー、そしてフレームワークはそれらを混同しません。
  • 2つの文字表、2つの名前。 64個の値は、APIの一部では「標準」(standard)と呼ばれ、別の部分では「URLセーフ」(URL-safe)と呼ばれ、ちょうど2つの文字 - 62番目と63番目のスロット - で異なります。片側に + と /、もう片側に - と _、そしてこの記事のすべての相互運用バグは、誰かが両側が同じだと仮定した瞬間に棲んでいます。

円環を閉じて

これがエンコーダー側です。そして、決断を下す場所です:文字表、パディング、改行、文字セット、バッファ。もう一方の方向、他の人々からBase64を受け取る方向 - 彼らのパディングの選択、改行、文字表、トークンとともに - は、痛みのほとんどが棲む場所で、ペイロードと交渉はできないからです。クラシックなワンライナから、スパンとURLセーフのファミリーまで、C#でのBase64デコードは、下のリンクのコンパニオン記事で詳しく扱われており、この2つでこの主題全体があなたのワーキングメモリに収まります。これほど古く、これほど小さいフォーマットの肝です。

最終更新: 2026-10-09

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