私が関わってきた .NET プロジェクトは、最終的に必ず Base64 のデコードが必要になります — Kubernetes シークレットから接続文字列を取り出したり、Webhook からバイナリペイロードを読み取ったり、デバッグセッション中に JWT トークンを検査したりといった場面です。C# で Base64 をデコードする主なメソッドは Convert.FromBase64String() で、byte[] を返します。それを Encoding.UTF8.GetString() に渡すと読み取り可能なテキストが得られます。コードを書かずに手軽に確認するには、 ToolDeck の Base64 デコーダ がブラウザ上で即座に処理できます。このガイドでは Convert.FromBase64String()、.NET 5+ 向けの Span ベース TryFromBase64String()、高性能な System.Buffers.Text.Base64 API、JWT ペイロードの抽出、ファイルと API レスポンスのデコード、CryptoStream によるストリーミング、そして C# 開発者がよく陥る 4 つのミスを解説します。
- ✓Convert.FromBase64String(s) + Encoding.UTF8.GetString(bytes) が標準的な 2 ステップパイプライン — すべての .NET バージョンで動作する。
- ✓Convert.TryFromBase64String() は不正入力で例外をスローせず、Span<byte> に書き込む — .NET 5+ のホットパスに最適。
- ✓System.Buffers.Text.Base64.DecodeFromUtf8() はパフォーマンスクリティカルなサービスで UTF-8 バイトバッファをゼロアロケーションでデコードできる。
- ✓JWT トークンは Base64url(+ と / の代わりに - と _)を使用 — Convert.FromBase64String() を呼ぶ前に入力を正規化する必要がある。
- ✓CryptoStream と FromBase64Transform を組み合わせると、すべてをメモリに読み込まずに大きなファイルをストリーミングデコードできる。
Base64 デコードとは?
Base64 エンコーディングはバイナリデータをテキストのみの転送路(JSON フィールド、HTTP ヘッダー、メール本文、XML 属性)で安全に送受信できるよう、64 文字の ASCII アルファベットに変換します。入力 3 バイトが 4 文字の Base64 に変換されるため、Base64 の出力は元のデータより常に約 33% 大きくなります。デコードはこの変換を逆に行います。末尾の = パディングは最後のグループから何バイト削るかをデコーダに伝えます。= が 1 つなら最後のブロックは 2 バイト、== なら 1 バイトです。Base64 は暗号化ではありません — 誰でも逆変換できます。目的は生のバイナリを損なうチャネルを通じた安全な転送であり、機密性のためではありません。
cmVkaXM6Ly9jYWNoZS1wcm9kLmludGVybmFsOjYzNzkvc2Vzc2lvbi1zdG9yZQ==
redis://cache-prod.internal:6379/session-store
Convert.FromBase64String() — 標準デコードメソッド
Convert.FromBase64String() メソッドは Framework 1.1 の頃から .NET に存在します。NuGet パッケージも System 以外の追加インポートも不要 — 呼び出すだけで byte[] が返ってきます。Base64 を C# の文字列にデコードする 2 ステップパイプラインは常に同じです。Convert.FromBase64String() でバイトを取得し、Encoding.UTF8.GetString() でそのバイトをテキストとして解釈します。注意点は、このメソッドが生のバイトを返すことです。バイトをテキストに戻すには適切な Encoding を選ぶ必要があり、その選択は多くの人が思う以上に重要です。エンコーディングが合わないと、例外なしにサイレントに文字化けが発生します。
最小限の動作例
using System; using System.Text; // Kubernetes シークレットに Base64 で保存された接続文字列 string encoded = "cmVkaXM6Ly9jYWNoZS1wcm9kLmludGVybmFsOjYzNzkvc2Vzc2lvbi1zdG9yZQ=="; byte[] decodedBytes = Convert.FromBase64String(encoded); string connectionString = Encoding.UTF8.GetString(decodedBytes); Console.WriteLine(connectionString); // redis://cache-prod.internal:6379/session-store
特別な理由がない限り Encoding.UTF8 を使用してください。.NET ランタイムは内部的に文字列を UTF-16 で表現しますが、システム境界を越えるデータ(API レスポンス、設定ファイル、シークレット)のほとんどは UTF-8 でエンコードされています。マルチバイト文字を含むデータに Encoding.ASCII を使用すると、例外なしにサイレントで ? に置き換えられ、出力が破損します。
ラウンドトリップ検証
using System; using System.Text; string original = "postgres://db-admin:Kx8!mQ@db-prod.us-east-1.internal:5432/orders"; // エンコード string encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(original)); Console.WriteLine(encoded); // cG9zdGdyZXM6Ly9kYi1hZG1pbjpLeDghbVFAZGItcHJvZC51cy1lYXN0LTEuaW50ZXJuYWw6NTQzMi9vcmRlcnM= // デコード byte[] decoded = Convert.FromBase64String(encoded); string recovered = Encoding.UTF8.GetString(decoded); Console.WriteLine(recovered == original); // True
適切な Encoding の選び方
GetString() に渡す Encoding は、データが元々エンコードされた際に使用されたものと一致させる必要があります。間違ったものを選ぶと例外なしにゴミ文字が出力され、デコーダは喜んで意味のない文字列を生成します。実用的な判断基準を示します。
Encoding.UTF8— 安全なデフォルト。ASCII とすべての Unicode を処理。特別な理由がない限りこれを使用。Encoding.ASCII— 純粋な 7 ビット ASCII データのみ。マルチバイト文字は?になる。Encoding.Unicode— .NET では UTF-16LE。Windows 内部の文字列や PowerShell のエクスポートで使用されることがある。Encoding.Latin1— レガシーな西欧エンコーディング。古い SOAP サービスやメインフレーム連携で見られる。
using System;
using System.Text;
// 同じバイト、異なるエンコーディング — 異なる結果
byte[] decoded = Convert.FromBase64String("w7bDvMOk");
Console.WriteLine(Encoding.UTF8.GetString(decoded)); // öüä (正しい)
Console.WriteLine(Encoding.ASCII.GetString(decoded)); // ?????? (消失)
Console.WriteLine(Encoding.Latin1.GetString(decoded)); // öüä (文字化け)エラーハンドリング付きの再利用可能なデコードヘルパー
Convert.FromBase64String() は不正な入力でスローし、空白除去の処理が常に発生するため、ほとんどのプロジェクトで小さなヘルパーを用意しています。
using System;
using System.Text;
static class Base64Helper
{
public static string? DecodeToString(string encoded)
{
if (string.IsNullOrWhiteSpace(encoded))
return null;
// .NET のデコーダが拒否する空白を除去
string cleaned = encoded
.Replace("
", "")
.Replace("
", "")
.Replace(" ", "")
.Trim();
try
{
byte[] bytes = Convert.FromBase64String(cleaned);
return Encoding.UTF8.GetString(bytes);
}
catch (FormatException)
{
return null; // またはドメイン固有の例外をスロー
}
}
}
// 使用例
string? decoded = Base64Helper.DecodeToString(" cmVkaXM6Ly9jYWNoZQ== \n");
Console.WriteLine(decoded); // redis://cacheConvert.FromBase64String() は Base64 アルファベット外の文字(スペース、改行、- や _ などの URL セーフ文字を含む)が入力に含まれると FormatException をスローします。上記のヘルパーは空白を自動的に処理します。非標準型への Base64 デコード
デコーダは常に byte[] を返します。そのバイトをどう扱うかは元のデータによります。16 バイトの生データとして保存された GUID のこともあれば、シリアライズされた protobuf メッセージやバイナリ形式のタイムスタンプのこともあります。よく使う変換方法を紹介します。
Base64 から GUID へ
using System; // 一部の API は 36 文字の16進数文字列の代わりに 22 文字の Base64 として GUID を送信する string compactGuid = "C0HqetxMckKlZw4CssPUeQ=="; byte[] guidBytes = Convert.FromBase64String(compactGuid); Guid recovered = new Guid(guidBytes); Console.WriteLine(recovered); // 7aea41c0-4cdc-4272-a567-0e02b2c3d479
Base64 から System.Text.Json でデシリアライズした JSON へ
using System;
using System.Text;
using System.Text.Json;
// メッセージキューからの Base64 エンコードされた JSON ペイロード
string encoded = "eyJzZXJ2aWNlIjoicGF5bWVudC1nYXRld2F5IiwicmVnaW9uIjoiZXUtd2VzdC0xIiwicmVwbGljYXMiOjR9";
byte[] jsonBytes = Convert.FromBase64String(encoded);
string json = Encoding.UTF8.GetString(jsonBytes);
Console.WriteLine(json);
// {"service":"payment-gateway","region":"eu-west-1","replicas":4}
// レコードにデシリアライズ
var deployEvent = JsonSerializer.Deserialize<DeployEvent>(jsonBytes);
Console.WriteLine($"{deployEvent!.Service} in {deployEvent.Region}");
// payment-gateway in eu-west-1
record DeployEvent(string Service, string Region, int Replicas);Base64 から16進数文字列へ
using System; // Base64 として保存された SHA-256 ハッシュ string hashBase64 = "n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg="; byte[] hashBytes = Convert.FromBase64String(hashBase64); string hex = Convert.ToHexString(hashBytes); Console.WriteLine(hex); // 9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08
BinaryFormatter でデシリアライズしないでください。 既知のリモートコード実行脆弱性があり、.NET 8+ では廃止されています。 エンコードされたコンテンツが外部ソースから来る場合は、.NET のバイナリシリアライゼーションではなく JSON や protobuf として解析してください。Base64 デコードメソッドリファレンス
.NET は 2 つの名前空間にわたって複数のデコードメソッドを提供しています。Convert クラスは汎用デコードを担い、System.Buffers.Text.Base64 は生の UTF-8 バイトバッファを扱う高スループットシナリオを対象としています。
Convert.TryFromBase64String() — 例外なしのデコード
Try パターンは、ユーザー入力で失敗する可能性のある操作に対する .NET の標準的な手法です。.NET 5 以降で使用できる Convert.TryFromBase64String() は FormatException をスローする代わりに bool を返します。デコードされたバイトを呼び出し元が提供した Span<byte> に書き込むため、小さなペイロードにはスタック割り当てメモリを使用してヒープを完全に回避できます。
using System;
using System.Text;
string userInput = "eyJob3N0IjoiMTAuMC4xLjUwIiwicG9ydCI6ODQ0M30=";
// 小さなペイロードにはスタック割り当て(1KB 未満が安全な目安)
Span<byte> buffer = stackalloc byte[256];
if (Convert.TryFromBase64String(userInput, buffer, out int bytesWritten))
{
string result = Encoding.UTF8.GetString(buffer[..bytesWritten]);
Console.WriteLine(result);
// {"host":"10.0.1.50","port":8443}
}
else
{
Console.WriteLine("Invalid Base64 input — skipping");
}このアプローチは、不正な入力が例外的ではなく想定されるリクエストミドルウェアやバリデーションパイプラインで威力を発揮します。不正なリクエストのたびに FormatException をスローしてキャッチするとスケール時に測定可能なオーバーヘッドが生じます — TryFromBase64String() はそれを完全に回避します。
デコードせずに Base64 入力を検証する
API コントローラーでよく使われるパターン:入力を下流に渡す前に Base64 として有効かどうかを確認します。使い捨てバッファを割り当てることで TryFromBase64String() をバリデーターとして使用できます。
using System;
static bool IsValidBase64(string input)
{
// デコード後の最大サイズを計算
Span<byte> buffer = stackalloc byte[((input.Length + 3) / 4) * 3];
return Convert.TryFromBase64String(input, buffer, out _);
}
// API コントローラーでの使用例
Console.WriteLine(IsValidBase64("eyJob3N0IjoiMTAuMC4xLjUwIn0=")); // True
Console.WriteLine(IsValidBase64("not!!valid!!base64")); // False
Console.WriteLine(IsValidBase64("")); // True (空は有効)TryFromBase64String() は入力が有効でも false を返します。安全のために必要なサイズを (inputLength / 4) * 3 として計算してください。ファイルと API レスポンスからの Base64 デコード
ディスクから Base64 エンコードされたファイルを読み込む
証明書、暗号化された blob、データエクスポートファイルは Base64 テキストとして提供されることがあります。典型的なパターン:ファイルを文字列として読み込み、FormatException を引き起こす空白や改行を除去し、バイトにデコードして、バイナリ出力を書き込みます。エラーハンドリングに注意してください — ファイル I/O エラーと Base64 フォーマットエラーは別々にキャッチするべきです。
using System;
using System.IO;
string inputPath = "tls-cert.pem.b64";
string outputPath = "tls-cert.pem";
try
{
string encoded = File.ReadAllText(inputPath).Trim();
// 改行を除去 — .NET のデコーダは改行を拒否する
encoded = encoded.Replace("
", "").Replace("
", "");
byte[] decoded = Convert.FromBase64String(encoded);
File.WriteAllBytes(outputPath, decoded);
Console.WriteLine($"Decoded {decoded.Length} bytes -> {outputPath}");
}
catch (IOException ex)
{
Console.Error.WriteLine($"File error: {ex.Message}");
}
catch (FormatException ex)
{
Console.Error.WriteLine($"Invalid Base64: {ex.Message}");
}HTTP API レスポンスから Base64 フィールドをデコードする
クラウド API(Azure Key Vault、AWS Secrets Manager、GitHub Contents API)は JSON の中に Base64 文字列として埋め込まれたバイナリデータを返すことがよくあります。ワークフローは常に同じです。HTTP リクエストを送り、JSON レスポンスを解析し、Base64 フィールドを取り出してデコードします。以下の例では .NET 6+ に組み込まれている HttpClient と System.Text.Json を使用します。
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer sk-prod-9f8e7d6c");
try
{
var response = await client.GetAsync("https://api.example.com/secrets/db-password");
response.EnsureSuccessStatusCode();
string body = await response.Content.ReadAsStringAsync();
// API が返すもの: {"name":"db-password","value":"cG9zdGdyZXM6eGs5bVAycVI=","version":3}
using var doc = JsonDocument.Parse(body);
string encodedValue = doc.RootElement.GetProperty("value").GetString()!;
byte[] decoded = Convert.FromBase64String(encodedValue);
string secret = Encoding.UTF8.GetString(decoded);
Console.WriteLine($"Secret: {secret}");
// Secret: postgres:xk9mP2qR
}
catch (HttpRequestException ex)
{
Console.Error.WriteLine($"HTTP error: {ex.Message}");
}
catch (FormatException ex)
{
Console.Error.WriteLine($"Base64 decode failed: {ex.Message}");
}FormatException の catch ブロックは分けてください。まとめてしまうと、API が不正なデータを返したのかリクエスト自体が失敗したのか判別しにくくなります。本番環境では FormatException をキャッチした際に生の Base64 値(または少なくともその長さと最初の 20 文字)をログに記録してください — デバッグが劇的に楽になります。コマンドラインからの Base64 デコード
コンパイル済みプロジェクトは常に必要ではありません。dotnet-script ツールと PowerShell はどちらも 1 行で Base64 デコードを処理できます。デバッグ中の素早い確認には、コンソールアプリを作成するより速いです。
# PowerShell(Windows 組み込み、Linux/macOS でも利用可)
[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("eyJob3N0IjoiMTAuMC4xLjUwIn0="))
# {"host":"10.0.1.50"}
# Linux / macOS ネイティブ base64 コマンド
echo "eyJob3N0IjoiMTAuMC4xLjUwIn0=" | base64 --decode
# {"host":"10.0.1.50"}
# macOS は --decode の代わりに -D を使用
echo "eyJob3N0IjoiMTAuMC4xLjUwIn0=" | base64 -D
# dotnet-script(インストール: dotnet tool install -g dotnet-script)
echo 'Console.WriteLine(System.Text.Encoding.UTF8.GetString(Convert.FromBase64String("eyJob3N0IjoiMTAuMC4xLjUwIn0=")));' | dotnet-script eval
# デコードして jq で JSON を整形表示
echo "eyJob3N0IjoiMTAuMC4xLjUwIiwicG9ydCI6ODQ0M30=" | base64 --decode | jq .エンコードされた文字列をブラウザに直接貼り付けるには、 ToolDeck の Base64 デコーダ が標準と URL セーフの両方の形式をセットアップなしで処理できます。
高性能な代替手段:System.Buffers.Text.Base64
.NET Core 2.1 以降で利用可能な System.Buffers.Text.Base64 クラスは .NET 文字列ではなく生の UTF-8 バイトスパンで動作します。これにより文字列からバイトへの変換オーバーヘッドが完全に排除されます — 中間文字列のアロケーションも UTF-16 エンコードステップもありません。受信データがリクエストボディの ReadOnlySpan<byte> として既に存在する ASP.NET Core ミドルウェアでよく使います。Convert.FromBase64String() に渡すためだけに string を作成するのは理由もなくアロケーションを 2 倍にします。.NET 8 での BenchmarkDotNet テストでは、Span ベースのパスは 1 KB 未満のペイロードでおよそ 2〜3 倍速く、GC プレッシャーが一定に保たれるため大きな入力ではその差がさらに広がります。
using System;
using System.Buffers.Text;
using System.Text;
// HTTP リクエストボディからの生 UTF-8 バイトをシミュレート
byte[] utf8Input = Encoding.UTF8.GetBytes("eyJob3N0IjoiMTAuMC4xLjUwIiwicG9ydCI6ODQ0M30=");
byte[] output = new byte[Base64.GetMaxDecodedFromUtf8Length(utf8Input.Length)];
OperationStatus status = Base64.DecodeFromUtf8(
utf8Input, output, out int bytesConsumed, out int bytesWritten);
if (status == OperationStatus.Done)
{
string result = Encoding.UTF8.GetString(output.AsSpan(0, bytesWritten));
Console.WriteLine(result);
// {"host":"10.0.1.50","port":8443}
Console.WriteLine($"Consumed: {bytesConsumed}, Written: {bytesWritten}");
// Consumed: 44, Written: 31
}
else
{
Console.WriteLine($"Decode failed: {status}");
// 可能な値: InvalidData, DestinationTooSmall, NeedMoreData
}戻り型は OperationStatus — 4 つの値を持つ列挙型です。 Done、InvalidData、DestinationTooSmall、NeedMoreData。最後の値はストリーミングデータの部分デコードに有用です。完全なゼロアロケーションのためには、new byte[] の代わりに ArrayPool<byte>.Shared.Rent() と組み合わせてください。
DecodeFromUtf8InPlace — 入力バッファを上書きする
using System;
using System.Buffers.Text;
using System.Text;
// 入力バッファがデコードされたバイトで上書きされる
byte[] data = Encoding.UTF8.GetBytes("c2VydmVyLWNvbmZpZw==");
OperationStatus status = Base64.DecodeFromUtf8InPlace(data, out int bytesWritten);
if (status == OperationStatus.Done)
{
Console.WriteLine(Encoding.UTF8.GetString(data.AsSpan(0, bytesWritten)));
// server-config
}DecodeFromUtf8InPlace は入力バッファを変更します。元の Base64 文字列が後で必要な場合は使用しないでください — 配列の最初の bytesWritten バイトにはデコードされたデータが入り、残りはガベージになります。注意点として、System.Buffers.Text.Base64 は URL セーフな Base64 を直接処理しません。入力が - と _ の文字(JWT トークンなど)を使用している場合は、これらのメソッドを呼び出す前に + と / に置き換える必要があります。Span ベースの API は厳密に RFC 4648 標準アルファベットのみ対応しています。現代の API で Base64url がどれほど一般的であるかを考えると、DecodeFromUtf8Url 相当のメソッドが存在しないのは意外なギャップです。
シンタックスハイライト付きターミナル出力
Spectre.Console ライブラリは JSON ハイライトを含むリッチなターミナル出力を提供します — Base64 をデコードして結果を表示する CLI ツールの構築に有用です。dotnet add package Spectre.Console でインストールします。
using System;
using System.Text;
using Spectre.Console;
using Spectre.Console.Json;
string encoded = "eyJob3N0IjoiMTAuMC4xLjUwIiwicG9ydCI6ODQ0MywibWF4Q29ubiI6MTAwfQ==";
byte[] decoded = Convert.FromBase64String(encoded);
string json = Encoding.UTF8.GetString(decoded);
// ターミナルでシンタックスハイライト付きで整形表示
AnsiConsole.Write(new JsonText(json));
// カラー付き JSON を出力:
// {
// "host": "10.0.1.50",
// "port": 8443,
// "maxConn": 100
// }リモートサービスから設定を取得してデコードする CLI ツールに特に便利です。Base64 設定をデコードし、JSON にデシリアライズして、ハイライト付きで出力 — わずか数行で実現できます。Spectre.Console にはテーブルレンダリング、プログレスバー、ツリービューもあり、より複雑なデコードデータ構造をターミナルで表示する必要がある場合に対応できます。
CryptoStream による大きな Base64 ファイルのストリーミング
500 MB の Base64 ファイルを File.ReadAllText() で読み込んで Convert.FromBase64String() を呼び出すと、ヒープに約 700 MB が割り当てられます。文字列自体(UTF-16 なのでファイルサイズの 2 倍)とデコードされたバイト配列の分です。CryptoStream + FromBase64Transform の組み合わせはチャンク単位でデコードし、メモリ使用量を一定に保ちます。
using System.IO;
using System.Security.Cryptography;
string inputPath = "database-export.sql.b64";
string outputPath = "database-export.sql";
using var inputStream = new FileStream(inputPath, FileMode.Open, FileAccess.Read);
using var transform = new FromBase64Transform(FromBase64TransformMode.IgnoreWhiteSpaces);
using var base64Stream = new CryptoStream(inputStream, transform, CryptoStreamMode.Read);
using var outputStream = new FileStream(outputPath, FileMode.Create, FileAccess.Write);
base64Stream.CopyTo(outputStream);
Console.WriteLine($"Decoded {new FileInfo(outputPath).Length} bytes -> {outputPath}");FromBase64TransformMode.IgnoreWhiteSpaces フラグは、改行で折り返された Base64(PEM ファイル、メールエクスポート)を手動クリーンアップなしで処理します。このフラグがないと入力の改行が FormatException を引き起こします。これはデコード中に空白文字をサイレントにスキップする Java の getMimeDecoder() に最も近い .NET の等価物です。
CryptoStream という名前は誤解を招きます — Base64 に暗号的な要素は何もありません。Microsoft が FromBase64Transform を System.Security.Cryptography 名前空間に置いたのは、AES などの暗号変換と同じインターフェース ICryptoTransform を実装しているからです。ストリーム自体はチャンク単位でデータを任意の変換に通す汎用ストリーミング変換パイプラインで、たまたま誤った名前空間にあるだけです。
ASP.NET Core シナリオ向けの非同期ストリーミング
using System.IO;
using System.Security.Cryptography;
async Task DecodeStreamAsync(Stream inputStream, string outputPath)
{
using var transform = new FromBase64Transform(FromBase64TransformMode.IgnoreWhiteSpaces);
using var base64Stream = new CryptoStream(inputStream, transform, CryptoStreamMode.Read);
await using var outputStream = new FileStream(
outputPath, FileMode.Create, FileAccess.Write, FileShare.None,
bufferSize: 81920, useAsync: true);
await base64Stream.CopyToAsync(outputStream);
}
// エンドポイントでの使用例:
// await DecodeStreamAsync(Request.Body, "uploaded-file.bin");CopyTo から CopyToAsync に切り替えてください。リクエストスレッドでの同期 I/O は Kestrel でデフォルトでブロックされ、InvalidOperationException がスローされます。C# での Base64 JWT トークンペイロードのデコード方法
JWT はドットで区切られた 3 つの Base64url エンコードされたセグメントを持ちます。中間のセグメントがペイロードです。JWT ライブラリを使わずにデコードできます — . で分割し、Base64url 文字を正規化し、パディングを修正して、Convert.FromBase64String() を呼び出します。JWT が + と / の代わりに - と _ を使用し、= パディングを省略しているため、ほぼ誰もが最初につまずきます。
using System;
using System.Text;
static string DecodeJwtPayload(string token)
{
string[] parts = token.Split('.');
if (parts.Length != 3)
throw new ArgumentException($"Invalid JWT: expected 3 segments, got {parts.Length}");
// ペイロード(2 番目のセグメント)を取得
string payload = parts[1];
// URL セーフ文字を標準 Base64 に置換
payload = payload.Replace('-', '+').Replace('_', '/');
// 4 の倍数にパディング
switch (payload.Length % 4)
{
case 2: payload += "=="; break;
case 3: payload += "="; break;
}
byte[] bytes = Convert.FromBase64String(payload);
return Encoding.UTF8.GetString(bytes);
}
// 実際の形状のトークンでテスト
string token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"
+ ".eyJzdWIiOiJ1c3ItNjcyIiwiaXNzIjoiYXV0aC5leGFtcGxlLmNvbSIsImV4cCI6MTc0MTk1NjgwMCwicm9sZXMiOlsiYWRtaW4iLCJiaWxsaW5nIl19"
+ ".SIGNATURE_PLACEHOLDER";
Console.WriteLine(DecodeJwtPayload(token));
// {"sub":"usr-672","iss":"auth.example.com","exp":1741956800,"roles":["admin","billing"]}補足:これはペイロードを読み取るだけです。署名の検証は行いません。 本番環境での認証検証には Microsoft.IdentityModel.JsonWebTokens などの適切なライブラリを使用してください。ただし、デバッグ、ロギング、テストアサーションには、この手動アプローチで十分です。
デコードされた JWT ペイロードを型付きオブジェクトに解析する
JSON 文字列を取得したら、System.Text.Json でデシリアライズして文字列操作なしに個別のクレームにアクセスします。
using System;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
record JwtPayload(
[property: JsonPropertyName("sub")] string Subject,
[property: JsonPropertyName("iss")] string Issuer,
[property: JsonPropertyName("exp")] long Expiration,
[property: JsonPropertyName("roles")] string[] Roles
);
// 上記の DecodeJwtPayload() を実行後
string json = DecodeJwtPayload(token);
var claims = JsonSerializer.Deserialize<JwtPayload>(json)!;
Console.WriteLine($"User: {claims.Subject}");
Console.WriteLine($"Issuer: {claims.Issuer}");
Console.WriteLine($"Expires: {DateTimeOffset.FromUnixTimeSeconds(claims.Expiration):u}");
Console.WriteLine($"Roles: {string.Join(", ", claims.Roles)}");
// User: usr-672
// Issuer: auth.example.com
// Expires: 2025-03-14 12:00:00Z
// Roles: admin, billingよくあるミス
これらはすべて本番の C# サービスで経験したものです。最初の 2 つは私がコードレビューで見る Base64 関連バグの大半を占めています。各ミスは単独で見ると明らかですが、大きな機能の途中で Base64 デコードがパイプラインの一ステップに過ぎない場合には見落としやすいものです。
問題: 設定ファイル、環境変数、ユーザー入力から読み取った Base64 文字列には末尾に改行が付いていることが多い。Convert.FromBase64String() は \r\n を含む Base64 アルファベット外の文字をすべて拒否する。
修正: .Trim() または .Replace() を呼び出してデコード前に空白を除去する。
// 環境変数に末尾の改行がある
string encoded = Environment.GetEnvironmentVariable("DB_PASS_B64")!;
byte[] decoded = Convert.FromBase64String(encoded);
// FormatException: The input is not a valid Base-64 stringstring encoded = Environment.GetEnvironmentVariable("DB_PASS_B64")!;
string cleaned = encoded.Replace("\n", "").Replace("\r", "").Trim();
byte[] decoded = Convert.FromBase64String(cleaned);
Console.WriteLine(Encoding.UTF8.GetString(decoded));問題: JWT トークンや一部の API ペイロードは - と _(URL セーフアルファベット)を使用する。標準デコーダは + と / のみ受け付けるため、最初の - 文字で FormatException をスローする。
修正: デコード前に - を + に、_ を / に置き換える。パディングも修正する。
// JWT ペイロード — URL セーフ Base64 を使用 string payload = "eyJzdWIiOiJ1c3ItNjcyIn0"; byte[] decoded = Convert.FromBase64String(payload); // FormatException
string payload = "eyJzdWIiOiJ1c3ItNjcyIn0";
payload = payload.Replace('-', '+').Replace('_', '/');
switch (payload.Length % 4)
{
case 2: payload += "=="; break;
case 3: payload += "="; break;
}
byte[] decoded = Convert.FromBase64String(payload);
Console.WriteLine(Encoding.UTF8.GetString(decoded));
// {"sub":"usr-672"}問題: デコードされた画像や protobuf バイトに Encoding.UTF8.GetString() を呼び出すとゴミが生成される。さらに悪いことに、その文字列をバイトに戻すと不正な UTF-8 シーケンスが置き換えられてデータが静かに破損する。
修正: バイナリデータはパイプライン全体を通じて byte[] として保持する。コンテンツがテキストとわかっている場合のみ GetString() を呼び出す。
byte[] decoded = Convert.FromBase64String(pngBase64);
string imageStr = Encoding.UTF8.GetString(decoded); // バイナリが破損
File.WriteAllText("image.png", imageStr); // 壊れたファイルbyte[] decoded = Convert.FromBase64String(pngBase64);
// バイトを直接書き込む — 文字列変換不要
File.WriteAllBytes("image.png", decoded);問題: 古い .NET Framework API の中にはシステムの現在の ANSI コードページをデフォルトとするものがあり、マシンによって異なる。コードページ 1252 の Windows サーバーと UTF-8 の Linux コンテナは同じバイトから異なる文字列を生成する。
修正: 常に Encoding.UTF8 を明示的に指定する。プラットフォームのデフォルトに頼らない。
byte[] decoded = Convert.FromBase64String(encoded); // Encoding.Default はプラットフォームによって異なる string result = Encoding.Default.GetString(decoded);
byte[] decoded = Convert.FromBase64String(encoded); string result = Encoding.UTF8.GetString(decoded); // Windows、Linux、macOS で一貫した結果
メソッド比較
.NET にはほとんどの開発者が気づいているより多くの Base64 デコードメソッドが存在します。以下の表はすべての組み込みオプションと最もよく使われる 2 つのサードパーティの代替手段をカバーしています。「アロケーション」の列は高スループットサービスで最も重要です — 毎回呼び出すたびに新しい byte[] を割り当てるメソッドは、タイトなループで GC に圧力をかけます。
日常的な用途には Convert.FromBase64String()。ユーザー入力を検証するホットパスには TryFromBase64String()。生のリクエストバイトを操作する ASP.NET Core ミドルウェアには Base64.DecodeFromUtf8()。大きなファイルには CryptoStream + FromBase64Transform。BouncyCastle は他の暗号操作のためにすでに依存関係ツリーに含まれている場合のみ意味があります。
何もコンパイルせずに素早く確認するには、 オンライン Base64 デコーダ が使い捨てのコンソールアプリを書くより速いです。
よくある質問
C# で Base64 文字列をテキストにデコードするには?
Convert.FromBase64String() でバイト配列を取得し、Encoding.UTF8.GetString() に渡します。エンコード時に使用したエンコーディングと一致させる必要があります。UTF-8 はほぼすべての現代的なシステムで安全なデフォルトです。入力に空白や改行が含まれる可能性がある場合は、デコード前に .Trim() を呼び出すか、それらを除去してください。
using System; using System.Text; string encoded = "cG9zdGdyZXM6eGs5bVAycVI="; byte[] bytes = Convert.FromBase64String(encoded); string result = Encoding.UTF8.GetString(bytes); Console.WriteLine(result); // postgres:xk9mP2qR
Convert.FromBase64String() と Convert.TryFromBase64String() の違いは?
FromBase64String() は不正な入力に対して FormatException をスローします。TryFromBase64String() は bool を返し、呼び出し元が提供した Span<byte> に結果を書き込みます。例外のオーバーヘッドを避けたいホットパスに適しています。TryFromBase64String() は .NET 5 以降が必要です。
using System;
string input = "maybe-not-valid-base64!!";
Span<byte> buffer = stackalloc byte[256];
if (Convert.TryFromBase64String(input, buffer, out int written))
Console.WriteLine($"Decoded {written} bytes");
else
Console.WriteLine("Invalid Base64 input");C# で Base64url JWT ペイロードをデコードするには?
トークンをドットで分割し、2 番目のセグメントを取り出して、- を + に、_ を / に置き換え、= で 4 の倍数になるようにパディングを追加してから Convert.FromBase64String() を呼び出します。JWT トークンは URL セーフな Base64 アルファベットを使用しており、.NET の標準デコーダはそのまま処理できません。
string token = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c3ItNjcyIn0.SIG";
string payload = token.Split('.')[1];
payload = payload.Replace('-', '+').Replace('_', '/');
switch (payload.Length % 4)
{
case 2: payload += "=="; break;
case 3: payload += "="; break;
}
byte[] bytes = Convert.FromBase64String(payload);
Console.WriteLine(Encoding.UTF8.GetString(bytes));
// {"sub":"usr-672"}C# の Base64 デコードで適切な Encoding を選ぶには?
デフォルトは Encoding.UTF8 を使用してください。ASCII とマルチバイト Unicode 文字を処理できます。Encoding.ASCII はデータが純粋な 7 ビット ASCII であると確実にわかっている場合のみ使用します。Encoding.Unicode(.NET では UTF-16LE)は元データが UTF-16 でエンコードされていた場合のみ使用します。Windows 内部の文字列や PowerShell のエクスポートで稀に発生します。
Convert.FromBase64String() が FormatException をスローするのはなぜ?
主な原因は 3 つです。入力に空白や改行が含まれている(デコード前に除去する)、入力が - や _ などの URL セーフ文字を使用している(それぞれ + と / に置き換える)、パディングが欠如または不正である(パディング後の合計長が 4 の倍数でなければならない)。Java と異なり、.NET には空白を許容する組み込みの MIME デコーダがなく、入力を自分でクリーンにするか CryptoStream と FromBase64Transform の IgnoreWhiteSpaces モードを使用する必要があります。
// 空白を修正
string cleaned = rawInput.Replace("\n", "").Replace("\r", "").Trim();
byte[] decoded = Convert.FromBase64String(cleaned);C# で大きな Base64 データをストリーミングデコードするには?
System.Security.Cryptography の FromBase64Transform と CryptoStream を使用します。読み取りながらチャンク単位でデコードするため、ファイルサイズに関わらずメモリ使用量が一定に保たれます。入力に改行が含まれる場合は FromBase64TransformMode.IgnoreWhiteSpaces を渡します。.NET 6+ では Base64.DecodeFromUtf8() を使った IAsyncEnumerable パターンによる手動チャンク処理も可能ですが、ファイル間のデコードには CryptoStream の方が簡単です。
using System.IO;
using System.Security.Cryptography;
using var input = File.OpenRead("payload.b64");
using var transform = new FromBase64Transform();
using var cryptoStream = new CryptoStream(input, transform, CryptoStreamMode.Read);
using var output = File.Create("payload.bin");
cryptoStream.CopyTo(output);関連ツール
- Base64 Encoder — ブラウザでテキストまたはバイナリデータを Base64 にエンコード。C# のユニットテストに貼り付けるテストフィクスチャの生成に便利です。
- JWT Decoder — JWT の 3 つのセグメントすべてをフィールドごとのペイロード検査付きで一度にデコード。トークンを読み取りたいだけのときは C# ヘルパーを書くより速いです。
- URL Decoder — URL エンコードされた文字列をパーセントデコード。API レスポンスが Base64url データとパーセントエンコードされたクエリパラメータを混在させている場合に便利です。
- JSON Formatter — Base64 の JWT ペイロードや API 設定をデコードした後、JSON をここに貼り付けて整形表示し構造を検証できます。