Visual Basic での Base64 デコード:完全ガイド
場面を想像してみてください:1つの文字列があなたの Visual Basic プロジェクトに舞い込みました。シャッフルされた文字と数字の束のようで、ところどころにプラス記号、スラッシュ、イコール記号が混ざっています。送ってくれた相手は、それがかつてはごく普通の文章、JPEG、設定データの塊だったと断言します。その文字列こそ Base64 であり、このページはそれを元の姿に戻すための実戦ガイドです。最初に朗報を:Visual Basic には、.NET Framework のいちばん初期の頃から第一級の Base64 デコーダーが備わっており、それはランタイムの中に同梱されています。使うのに1つとしてパッケージをインストールする必要はありません。
まず簡単な復習から。このサイトのホームページではフォーマットを深く詳しく説明しています:Base64 は3バイトを、64記号の文字表から選んだ4文字として書き、末尾の = 1つか2つが本当のデータの終わりを示します。だからエンコード後のテキストは、元のデータより少しふっくらしています:入力の3バイトごとに4文字で、だいたい3分の1だけ増えます。デコードとは、その取引をただ逆方向に回すことです。問題の形を頭に描いたら、封筒をいくつか開けてみましょう。
デコーダーファミリー:1つのランタイム、4つの時代
デコードに必要なものはすべて .NET ランタイムの中にあります。ここ20年ほどで4つの波に分けて成長してきましたが、古い波は今もこれまでとまったく同じように動き続けています。だから実のところでは、そのすべての世代に出会うことになります:
| API | 利用可能になった時期 | 用途 |
|---|---|---|
System.Convert.FromBase64String |
.NET Framework 1.1(2003) | 定番。文字列1つがはいり、新しい Byte() 配列がでる。不正な入力には例外を投げる。 |
System.Convert.FromBase64CharArray |
.NET Framework 1.1(2003) | 同じデコードだが、すでに自分の手にある文字配列の一片から読む。 |
System.Convert.TryFromBase64String, TryFromBase64Chars |
.NET Core 2.1(2018) | 例外ではなくブール値で答え、あなたが提供するバッファに書き込む。信頼できない入力へのフレンドリーなガード。 |
System.Buffers.Text.Base64 |
.NET Core 2.1(2018) | 低レベルなスパンベースのデコード:例外ではなく状態コード、インプレースでの解凍、IsValid による事前チェック。 |
System.Buffers.Text.Base64Url |
.NET 9(2024) | URLセーフの文字表(+ と / の代わりに - と _)、パディングは任意。古いランタイムでは Microsoft.Bcl.Memory NuGetパッケージに載っています。 |
FromBase64Transform + CryptoStream |
.NET Framework 1.1(2003) | ストリーミングデコード:ファイルからファイルへ、ネットワークからディスクへ、チャンク単位で、ペイロード全体をメモリに保持せずに。 |
先に進む前に、Visual Basic としての補足を1つ。VB プロジェクトでは、名前だけの Convert は System.Convert に解決します。標準のプロジェクトテンプレートが System 名前空間をあらかじめ取り込んでくれるうえ、Visual Basic のランタイムにはその名前を隠すものは何もありません。それでもこの記事では、ほとんどのところで完全な System.Convert の形で書きます。何の代価もかからず、コードを読む人にとって意図がまったく揺らぎないからです。
バージョンについて:.NET 10 が現在の長期サポートリリースです(2025年11月リリース、2028年11月までサポート)。.NET 8 と .NET 9 もどちらも2026年11月までサポートが続きます。一方、.NET 11 はプレビュー中で、新しい Base64 の便利メソッドが一揃い追加されています。デコードAPIはこれらすべてで安定しています。唯一のバージョンの壁は Base64Url です:.NET 9以降に内蔵されており、.NET Framework 4.6.2 以降では Microsoft.Bcl.Memory パッケージで取り込めます。この記事のどこにも、それ以外のパッケージは必要ありません。
ゼロからセットアップするなら、.NET SDK は最初から Visual Basic を箱に詰めてくるので、儀式はこの3行だけ:
dotnet new console -lang VB -o EnvelopeOpener
cd EnvelopeOpener
dotnet run
これで先頭に Imports System のついた小さな Program.vb ができ、デコードの準備は整います。
一行で済む主力:FromBase64String
Visual Basic でのデコード生活の9割は、1つの呼び出しです。文字列を渡せば、中に詰め込まれていたそのままのバイトを返してくれます:
Imports System
Imports System.Text
Module EnvelopeOpener
Sub Main()
Dim packed As String = "TWFu"
Dim bytes() As Byte = System.Convert.FromBase64String(packed)
Dim text As String = Encoding.UTF8.GetString(bytes)
Console.WriteLine(text)
' Man
End Sub
End Module
記憶に刻む価値があることが3つあります。第一に、戻り値はバイトであり、テキストではありません:デコーダーは最初から最後までバイト指向で、それはまさに望ましいことです。ペイロードは文章かもしれないし、JPEG、証明書、ハッシュかもしれない。どれも特別扱いされるべきではないからです。そのバイトを読みやすい文字列にするのは、Encoding オブジェクトを通した別個の、意図的なステップであり、文字セットの決断が生きるのもまさにそのステップです(後述します)。第二に、FromBase64String はデコード後の長さにぴったり合わせた新しい配列を割り当てるので、余分な容量を引きずることはありません。第三に、"TWFu" は単語「Man」にデコードされます。3バイト、サプライズゼロ。あなたが書くどんなデコードコードにも最適なスモークテストです。
何を受け入れ、何を拒むか
ここが .NET のデコーダーに個性があるところです。それもかなり個性的なもので:ちょうど1つのことには寛大で、それ以外のすべてには容赦がありません。寛大な対象は空白です。デコーダーがスキップするのは、どんな場所に現れてもちょうど4つの文字、すなわちスペース(U+0020)、タブ(U+0009)、改行(U+000A)、キャリッジリターン(U+000D)です。この方針は、Base64 のペイロードが短い行に折り返されて届くメールへの意図的な配慮で、MIME で折り返された添付ファイルは前処理ゼロでデコードできます。面白い事実:この寛容さは内蔵ファミリー全体に共通しており、スパンベースの System.Buffers.Text.Base64 や URLセーフの Base64Url クラスも含まれます。どのAPIに手を伸ばしても、同じおおらかさが手に入るということです。64記号の文字表の外のもの、長さルールの破れ、間違った位置のパディングには例外が待ち受けています。いくつかの入力で、同じデコーダーの様子を見てみましょう:
| 入力 | 結果 |
|---|---|
"TWFu" |
Man にデコードされる(3バイト)。 |
"TWF" + CRLF + "u" |
Man にデコードされる。真ん中の改行はデコーダーには見えない。 |
"TWFu" + 半角スペース |
FormatException。スキップされるのは上記の4つの空白文字だけで、半角スペースはそれに含まれない。 |
"TWE" |
FormatException。空白を無視しても、長さは4の倍数でなければならない。 |
"TWFu=" |
FormatException。データが終わった後のパディングは認められない。 |
"====" |
FormatException。パディング文字が2つを超えるのは無効。 |
"" または空白のみ |
空のバイト配列。静かで、有効な成功。 |
"TW=u" |
FormatException。真ん中のパディングは無効。 |
つまり正直な契約は小さくて覚えやすい:Nothing 参照は ArgumentNullException を投げ、空または空白だけの入力は空の配列にデコードされ、有効な入力はバイトにデコードされ、それ以外の無効な入力はどれも1つの非常に具体的な例外 FormatException を投げます。
バイトからテキストへ:文字セットを選ぶ
デコードされたバイトは実際にテキストだと決めた瞬間から、文字セットを名乗らなければなりません。なぜなら、読み方を告げるまでバイトはテキストではないからです。Visual Basic の文字列は内部で UTF-16 ですが、デコーダーから出てくるバイトは別の誰かが作ったもので、たいてい別の方式の下で作られています。だから向こうの選択に合わせる必要があります。実用的なメニューはこれ:
Encoding.UTF8:web や API を経由したあらゆるものの安全なデフォルト。迷ったらここから始める。Encoding.Unicode:UTF-16 リトルエンディアン、.NET のネイティブな風味。やり取りの両側が明示的に UTF-16 を選んだ .NET プログラム同士のときに理にかなう。Encoding.ASCII:7ビットのみ。ASCII 外のバイトはクエスチョンマークに置き換わるので、これは音が失われる選択で、アクセントを静かに破壊します。Encoding.Default:.NET Framework では機械の ANSI コードページでしたが、.NET(Core)ではロケールに関係なく常に UTF-8 です。やり取りするデータにはいまだに使わない方がいい - 明示的にエンコーディングを名乗り、たいていはEncoding.UTF8にする。
Imports System
Imports System.Text
Module CharsetDemo
Sub Main()
' 単語「Café」を UTF-8 バイトとして格納した
Dim bytes() As Byte = System.Convert.FromBase64String("Q2Fmw6k=")
Dim correct As String = Encoding.UTF8.GetString(bytes)
Console.WriteLine(correct)
' Café
End Sub
End Module
同じ5バイトを Encoding.Unicode で読めば、短くごみみたいな文字列が返ってきます - 妙な CJK 範囲の文字が2つと置換記号です。デコーダーがバイトを2つずつペアで読むからです。「Café」の UTF-8 バイトを Encoding.ASCII で読めば、アクセントは ??、クエスチョンマーク2つになります。UTF-8 ではアクセントが2バイトだからです。これらの選択はどれも例外を投げません。ただ静かに間違ったテキストを生むだけです。だからこそ文字セットは、引き継ぐデフォルトではなく、意図的に下す決断なのです。
URLセーフの文字表:Base64Url
標準の Base64 は + と / を使いますが、この2文字は URL の中でそれぞれ別の意味を持っているため、標準の文字表はクエリストリングに載った瞬間にリンクを壊しかねません。RFC 4648 の5節で標準化された修正は、URLセーフかつファイル名セーフなバリアントです:64文字の方式は同じで、- が + の代わりに、_ が / の代わりに座り、長さから自明なので末尾のパディングは普通落とされます。この文字表に出会うのは、JWT、APIトークン、URL の中に Base64 が乗るあらゆる場所です。.NET 9 はそれに専用のクラス System.Buffers.Text.Base64Url を追加し、使うのは実に気持ちいい:
Imports System.Buffers.Text
Imports System.Text
Module UrlSafeOpener
Sub Main()
' URLセーフの入力、末尾にパディングなし
Dim packed As String = "SGVsbG8gd29ybGQ"
Dim bytes() As Byte = Base64Url.DecodeFromChars(packed)
Dim text As String = Encoding.UTF8.GetString(bytes)
Console.WriteLine(text)
' Hello world
End Sub
End Module
知っておく価値のある挙動が2つあります。Base64Url のエンコーダーは設計上、パディングなしの出力を生み出しますが、そのデコーダーはパディング有りの入力も無しの入力も受け入れます。だから他のエコシステムのデータにも親切です。また Base64Url.IsValid なら、デコードする前に候補文字列を事前チェックできます。データが外の世界からやってくる場合に便利です。このクラスのない古いランタイムで立ち往生しているなら、変換は文字の入れ替え2つとパディングの補填で済み、エンコーダーがやっていたこととちょうど逆のことです:
Imports System
Module CompatOpener
Function FromUrlSafe(ByVal packed As String) As Byte()
Dim standard As String = packed.Replace("-"c, "+"c).Replace("_"c, "/"c)
Select Case standard.Length Mod 4
Case 2
standard &= "=="
Case 3
standard &= "="
End Select
Return System.Convert.FromBase64String(standard)
End Function
End Module
.NET Framework 4.6.2 以降では、代わりに Microsoft.Bcl.Memory NuGetパッケージをインストールして本物の Base64Url クラスを使えます。どちらの道でもルールはシンプルです:文脈(URL、JWT、APIトークン)から文字表を見抜き、それに合うデコーダーを選ぶ。
ファイルを開く
Base64 の最も古い用途の一つは、バイナリをテキストファイルに便乗させることです:エンコードされたバイトを含む .b64 や .txt ファイル。Visual Basic ではこの往復は、ファイル呼び出し2つとデコード1つです。テキストを読み、デコードして、バイトを書き出す:
Imports System.IO
Module FileOpener
Sub Main()
Dim packed As String = File.ReadAllText("payload.b64")
Dim bytes() As Byte = System.Convert.FromBase64String(packed)
File.WriteAllBytes("payload.bin", bytes)
End Sub
End Module
空白への寛容さが、この作業を満足できるほど堅牢にします:ファイルは、ペイロードが1つの長い行で書かれたのか、76文字で折り返されたのか、64文字で折り返されたのかを気にしません。デコーダーはどちらにせよ改行をスキップするからです。ポケットにしのんでおきたいサイズの話が1つ:テキストファイルは、隠しているバイナリよりだいたい3分の1大きいので、10メガバイトのファイルは、だいたい13.3メガバイトの文字として到着します。劇的なことではありませんが、「小さな」テキストファイルが妙に大きく感じたときの、覚えておきたい数字です。
画像とデータURI
データURI方式(RFC 2397)は、URL 自身が自分の内容を持ち運ぶことを可能にします:data: の後にメディアタイプ、リテラルの目印 ;base64、コンマ、そしてエンコードされたバイト。HTML と CSS の中、小さな画像やフォントを別のファイルを指す代わりにマークアップに直接埋め込むのに使われているのを見たことがあるはずです。Visual Basic では、それをほどぐのは文字列の分割とデコードだけです。次の例は、データURI から PNG を取り出し、それを WPF の画像にします:
Imports System.IO
Imports System.Windows.Media.Imaging
Module DataUriOpener
Function ImageFromDataUri(ByVal dataUri As String) As BitmapImage
Dim comma As Integer = dataUri.IndexOf(","c)
Dim header As String = dataUri.Substring(0, comma)
If Not header.EndsWith(";base64") Then
Throw New FormatException("Not a base64 data URI")
End If
Dim packed As String = dataUri.Substring(comma + 1)
Dim bytes() As Byte = System.Convert.FromBase64String(packed)
Dim image As New BitmapImage()
image.BeginInit()
image.CacheOption = BitmapCacheOption.OnLoad
image.StreamSource = New MemoryStream(bytes)
image.EndInit()
Return image
End Function
End Module
ヘッダーの防御的チェックに注意してください:;base64 目印のないデータURI は、代わりにURLエスケープされたデータを含んでおり、それを Base64 としてデコードすると失敗するか、ごみができます。RFC 自体が、データURIは短い値にしか役に立たないと警告しており、HTML には属性長の制限もあるので、これは写真館まるごとを1つの属性に詰めるのではなく、アイコン、アバター、サムネイルの正しい道具として扱いましょう。
HTTP、API、Basic認証
Base64 はウェブ API のどこにでもいます。最もよく見える姿の2つは、HTTP の Basic 認証ヘッダーと、バイナリや事前にエンコードされたデータを持つ JSON フィールドです。Basic 認証が最もシンプルなケースです:クライアントは Authorization: Basic の後に、username:password の Base64 を送ります。Visual Basic でそれを読むのは、プレフィックスのチェックと1つの呼び出しです:
Imports System
Imports System.Text
Module BasicAuthOpener
Function ReadCredentials(ByVal header As String) As String
If Not header.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase) Then
Throw New FormatException("Not a Basic auth header")
End If
Dim packed As String = header.Substring(6)
Dim bytes() As Byte = System.Convert.FromBase64String(packed)
Return Encoding.UTF8.GetString(bytes)
End Function
End Module
この関数は username:password を1つの文字列として返し、それをコロンで分割します。もう1つの日常のケースは、あるフィールドが事前にエンコードされた塊(画像や証明書など)を持つ JSON レスポンスです。HttpClient と System.Text.Json で(System.Text.Json は .NET Core 3.0 から箱に入っており、HttpClient ははるか前から)、パターンは明快です:
Imports System.Net.Http
Imports System.Text.Json
Module ApiOpener
Async Function ReadImageAsync() As Task(Of Byte())
Using client As New HttpClient()
Dim json As String = Await client.GetStringAsync("https://httpbin.org/get?attachment=TWFuIGlzIGhlcmU%3D&name=man.txt")
Dim doc As JsonDocument = JsonDocument.Parse(json)
Dim packed As String = doc.RootElement.GetProperty("args").GetProperty("attachment").GetString()
Return System.Convert.FromBase64String(packed)
End Using
End Function
End Module
事務的な注意が2つ。できたからといって、デコードした認証情報をログや UI に出力しないこと。そして、プレーン HTTP 上の Basic 認証に頼らないこと。そうすると、パスワードをより興味深い文字表で綴っただけで終わりますから。
JWT: 3つの部品を読む
コンパクト形式の JSON Web Token は、ドットで区切られた Base64Url の3つの部品:ヘッダー、ペイロード、署名です。最初の2つは目で読める素の JSON で(1回のデコード呼び出しでも読めます)。3つ目は暗号署名で、デコードするのではなく、正しいキーでチェックするものです。一般的なデモのサンプルトークンはこんな感じです:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.W-5gI_FdnMBdpgG4twX96rptvh13gmXQ75kKLRXVaGs。Visual Basic でそのペイロードを読むには、ドットで分割し、URLセーフの文字表を標準の文字表に戻し、デコードします:
Imports System
Imports System.Text
Module JwtOpener
Function ReadPayload(ByVal token As String) As String
Dim parts() As String = token.Split("."c)
If parts.Length <> 3 Then
Throw New FormatException("Not a compact JWT")
End If
' URLセーフの文字表を標準の文字表に戻す
Dim packed As String = parts(1).Replace("-"c, "+"c).Replace("_"c, "/"c)
Select Case packed.Length Mod 4
Case 2
packed &= "=="
Case 3
packed &= "="
End Select
Dim bytes() As Byte = System.Convert.FromBase64String(packed)
Return Encoding.UTF8.GetString(bytes)
End Function
End Module
サンプルトークンに実行すると、JSON {"sub":"1234567890","name":"John Doe"} が得られます。同じやり方でヘッダーを読むと {"alg":"HS256","typ":"JWT"} が得られます。ここでの警告で大事なものを1つ:JWT を読むことと検証することは違う。誰でもトークンを造れますから、中のクレームを一つでも信頼する前に、発行元のキーで署名を検証してください。その仕事には、System.IdentityModel.Tokens.Jwt NuGetパッケージ(Microsoft Entra チームの IdentityModel スイート)があり、Base64Url の細かい処理、署名チェック、クレームの解析をすべて引き受けてくれます。自分で作りたくなかったい、まさにそのレイヤーです。
メール添付、76文字で折り返し
Base64 が名声を築いたのがメールです。SMTP は7ビット ASCII 向けに設計されているので、バイナリ添付は飛ぶ前にテキストにならなければなりません。MIME 標準(RFC 2045)が選んだのは、76文字の行制限を持つ Base64 で、より古い64文字行の PEM とは近い親類です。生メールを受けたことがあるなら、その結果を見たことがあるはずです:密な Base64 の塊が、短い行にきちんとなぞり分けて折り返され、Content-Transfer-Encoding: base64 ヘッダーの下に置かれている。デコーダーにとって美しいのは、何もほどぐ必要がないということです。.NET のデコーダーは、改行とスペースはどこに現れてもスキップするので、折り返された塊はそのままデコードされます:
Imports System
Imports System.Text
Module MimeOpener
Sub Main()
' 59バイトの文を、CRLFで76文字ごとにMIME折り返した
Dim wrapped As String = "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZywgYW5kIHRoZW4gc29t" & vbCr & vbLf & "ZS4="
Dim bytes() As Byte = System.Convert.FromBase64String(wrapped)
Console.WriteLine(Encoding.UTF8.GetString(bytes))
' The quick brown fox jumps over the lazy dog, and then some.
End Sub
End Module
System.Net.Mail クラス群で仕事をするなら、デコードはさらに目立ちません:MailMessage に追加する Attachment は ContentEncoding が TransferEncoding.Base64 になっていて、メールライブラリが折り返し、送信、ほどぐ、この一連の儀式を全部やってくれます。手動のデコードが必要になるのは、ストリーム、テストフィクスチャ、レガシーのメールボックスファイルから生 MIME を読むときだけです。
データベース、設定、環境変数
テキスト専用のストレージは、Base64 を求めてくるのが常です:テキスト型に設定されたデータベースのカラム、XML の設定値、環境変数。どれも素の文字を望むので、バイナリは保存前にエンコードされ、戻ってきたときにデコードされます。デコード側はいつも同じ一行で、面白いのはサイズの計算です。SQL Server の通常の NVARCHAR カラムは8,000文字が上限なので、33パーセントの課税で上限を越える前に、バイナリはだいたい6,000バイトまで。それ以上なら MAX 変種に、もっと正直に言えば本物のバイナリカラムに手を伸ばします。Windows では、1つのユーザー定義の環境変数は32,767文字が上限(XP 世代のシステムでは、環境ブロック全体も同じサイズで頭打ちでした)。だから「ライセンスの塊まるごとを環境変数に」には、固い天井があります。頭の片隅にしまっておく価値のあるデコードパターンを1つ:保存した指紋をファイルと照合し、不一致がタイミング情報を漏らさないよう一定時間比較を使う:
Imports System.Security.Cryptography
Module FingerprintCheck
Function FingerprintsMatch(ByVal expectedPacked As String, ByVal fileBytes() As Byte) As Boolean
Dim expected() As Byte = System.Convert.FromBase64String(expectedPacked)
Dim actual() As Byte = SHA256.HashData(fileBytes)
Return CryptographicOperations.FixedTimeEquals(expected, actual)
End Function
End Module
同じ形が、保存されたどんなハッシュにも働きます:保存値をデコードし、新しいハッシュを計算し、一定時間で比較する。設定ファイルも同じパターンです。値が XML の app.config 項目から来たとしても、JSON 設定ファイルから来たとしても、レジストリの文字列から来たとしても。
ビッグデータ:読み込まずにストリームをデコード
これまでの例はすべて、ペイロード全体をメモリに読んでいました。添付や設定値ならそれで十分ですが、誰かがテキストファイルにエンコードした2ギガバイトのファイルには誤りです。その規模には、.NET Framework 1.1(2003年)から存在してきたストリーミングの組み合わせがあります:CryptoStream で包まれた FromBase64Transform 暗号トランスフォーム。エンコードされたテキストをチャンクごとに読み、トランスフォームがその場でデコードし、バイトを書き出すので、ファイルがどれほど大きくてもメモリは平らなまま:
Imports System.IO
Imports System.Security.Cryptography
Module StreamOpener
Sub DecodeFile(ByVal packedPath As String, ByVal outputPath As String)
Using packedStream As New FileStream(packedPath, FileMode.Open, FileAccess.Read)
Using decodedStream As New CryptoStream(packedStream, New FromBase64Transform(), CryptoStreamMode.Read)
Using outputStream As New FileStream(outputPath, FileMode.Create)
Dim buffer(65535) As Byte
While True
Dim read As Integer = decodedStream.Read(buffer, 0, buffer.Length)
If read = 0 Then Exit While
outputStream.Write(buffer, 0, read)
End While
End Using
End Using
End Using
End Sub
End Module
エンコードされたファイルは素の ASCII テキストなので、バイトストリームとして読むのはまったく安全で、折り返しの処理も何もしなくてもトランスフォームがこなしてくれます。出力ファイルは入力のだいたい4分の3の大きさになります。行きで払った33パーセントの課税を、帰りで回収するのと同じことです。
Visual Basic だけに刺さる落とし穴
このセクションの罠の多くは .NET の他の言語と共通していますが、いくつかはっきり VB 帽子をかぶっているので、まとめています:
- Byte と Byte()。Visual Basic では1バイトは
Byte、バイトの配列はByte()で、空の丸括弧がすべての仕事をします。Byte()のつもりでDim b As Byteと書くのは、初日の定番ミスで、ちょうどOption Strict Onがコンパイル時に拾う類いのものです。まだオンにしていないプロジェクトではオンにしてください:dotnet new console -lang VBテンプレートは決断をあなたに任せますが、Visual Studio のプロジェクトテンプレートはセットしています。 - スパンの壁。モダンなスパンベースAPIは VB から呼び出せますが、呼び出し側でのみです:
Byte()やChar()配列をそのまま、スパンを取るメソッドに渡せれば、コンパイラが変換してくれます。できないのは、自分のコードでスパンを名乗ることです。SpanやReadOnlySpan型の変数、フィールド、パラメータを宣言すると、コンパイラは「Types with embedded references are not supported in this version of your compiler」と答えます。だから VB 慣例はこれ:スパンAPIは素の配列で呼び、スパンを変数に格納しようとしない。 - BitConverter は Base64 ではない。
BitConverter.ToString(bytes)はバイトをダッシュ区切りの16進数として描画するので、「バイトを文字列に変換」と聞いた人にとって、うっかり選んでしまいかねない誤答です。API がTWFuを期待するところで、喜んで4D-61-6Eを差し出します。迷ったらSystem.Convertに手を伸ばしてください。 - MidB は幽霊。クラシックな Visual Basic には、
MidB、LeftB、RightBというバイトレベルの文字列関数がありました。2バイト文字セットを想定したものです。.NET の文字列は今はすべて Unicode で、ランタイムの文書は率直です。もはやサポートされていないと。レガシーのスニペットがそれを使っているなら、バイト配列とこの記事のAPIで書き直してください。 - Encoding.Default は機械についていく。ロケールによる分岐の物語は .NET Framework のものです。モダンな .NET では
Defaultは常に UTF-8 なので、同じバイトはどこでも同じようにデコードされます。共有するデータでは、エンコーディングを明示的に名乗り、たいていはEncoding.UTF8に - どちらの世界でも助言は変わりません。 - 往復は同一ではない。文字列をデコードして、その結果をエンコードし直しても、新しい文字列はオリジナルと一致する保証がありません。空白が消え、パディングが正規化されるからです。改行のあるペイロードは、きれいな1行に戻ってきます。データとしては問題ないが、デコードされたバイトではなくエンコードされたテキストを比較するロジックには危険です。
- 空白だけの入力は静かな成功。スペースと改行だけの文字列は、エラーなしで空のバイト配列にデコードされます。つまり「ユーザーは改行しか貼り付けていない」が、「ユーザーは空のペイロードを貼り付けた」とまったく同じ顔をして見えます。その違いが大事ななら、デコードする前に入力の長さをチェックしてください。
デコードのベストプラクティス
上記すべてを絞り込んだ、デコードコードを地味に保つ(最良の意味で)習慣:
- Base64 を保護ではなく搬送として扱う。これは暗号化ではなくエンコーディングで、誰でも1回の関数呼び出しでオリジナルを読めます。RFC 4648 すら、文字表の扱いがずぼらだと隠れチャネルが開くおそれがあると注記しています。中身を隠す必要があるなら、先に暗号化し、それからエンコードする。
- 信頼できない入力には、
FormatExceptionを飛ぶのを待つ代わりに、TryメソッドやIsValid事前チェックを優先する。ブール値は例外を握りつぶすよりも、フレンドリーなエラーメッセージに変えやすい。 - 文字セットは意図的に選び、別の方式に決まった理由が文書化されていない限り UTF-8 がデフォルト。
- 文字表は出所に合わせて選ぶ:MIME、メール、設定には標準 Base64。JWT と URL の中にあるものは Base64Url。
- 大きなものはすべて
FromBase64TransformとCryptoStreamでストリームし、文字列に読み込まない。 - 指紋やハッシュの比較は
=ではなくCryptographicOperations.FixedTimeEqualsで行い、値のどこまで一致したかをタイミングから漏らさないようにする。 Option Strict Onを維持し、Byte/Byte()系のすべりを本番の謎ではなくコンパイルエラーにする。
Visual Basic がデコーダーを手に入れた経緯
物語の始まりは、Visual Basic に Base64 がまったくなかった時代です。Visual Basic 6 と VBA(今も Excel や Office の中で動いているマクロ言語)の世界では、Base64 が必要だった開発者は、機械にすでにある COM コンポーネントから借りていました。有名な芸当は XML DOM の要素を使ったものです:MSXML パーサはノードが DataType を bin.base64 と宣言できるので、Base64 文字列をノードの text プロパティに割り当て、nodeTypedValue を読み返せば生のバイトが手に入ります。実際の Base64 の計算は DOM がやってくれます(ADO の Stream オブジェクト自身の Charset プロパティは「utf-8」や「iso-8859-1」のような本物の文字セット名しか理解せず「base64」は理解しないので、この芸当は MSXML に手を伸ばすわけです)。エンコードは同じ考えを逆回しで、バイトを nodeTypedValue に書き、エンコードされた文字列を text から読み返す:
' 背景説明のための、クラシックな VB6 / VBA デコード手法
Dim node As Object
Set node = CreateObject("MSXML2.DOMDocument").createElement("b64")
node.DataType = "bin.base64"
node.text = packed ' Base64文字列
Dim bytes() As Byte
bytes = node.nodeTypedValue
それは動いた。賢かった。そして「base64 VBA」が30年経った今も検索エンジンを照らしている理由です。そして2002年、すべてが変わりました:Visual Basic 7.0(この言語の最初の .NET バージョン、当時まだ Visual Basic .NET と呼ばれていた)が新しい Common Language Runtime に合流し、.NET Framework は System.Convert と、FromBase64String やその仲間たちを箱から出して同梱してくれました。2003年の .NET Framework 1.1 から、登録するコンポーネントなしで、すべての VB プログラムが第一級のデコーダーを持ったのです。モダンな波は2018年に .NET Core 2.1 とともに到来し、例外を投げることはない Try メソッドと、速いスパンベースの System.Buffers.Text.Base64 クラスを追加しました。そして2024年の .NET 9 で、ついに URLセーフの文字表が Base64Url として標準化されました。2026年時点では、.NET 10 - 2025年11月リリース - が長期サポートリリースであり、プレビュー中の .NET 11 ライブラリは新しい世代の Base64 便利メソッドを追加しつつあります。2003年のオリジナルの一行がそのまま動き続ける一方で、デコーダーはこれからもどんどん良くなっていくのです。
VBの世界からトリビア
- 公式の Visual Basic コンパイラは、それ自身が Visual Basic で書かれています。オープンソースの Roslyn プロジェクトの一部として、この言語は自分自身をコンパイルしているのです。
- 最初の Visual Basic が登場したのは1991年、web の時代が始まる前でした。Base64 の大活躍は1993年に MIME とともにやって来て、VB 自身も2年後の1995年に Visual Basic 4 とともに32ビット対応となり、32ビットプログラムを初めて可能にしました。1997年の VB 5 は堂々の32ビット専用で、箱には16ビットの残りも何もありませんでした。
- クラシック VB のバイトレベル関数
MidB、LeftB、RightBは、.NET では公式に「もはやサポートされていません」。なぜなら VB の文字列はフレームワークの最初の日から Unicode だからです。1つのエンコーディングの選択で引退したAPIの一族です。 - ランタイムの Base64 機構は、古めかしいテーブル参照などではありません:モダンな実装は機械がサポートしている場合にハードウェアベクトル化コードパス(AVX-512、AVX2、SSE 各バリアント)を実行するので、奥で起きているのは「遅いテキストコーデック」などではないのです。
- VB6 時代の MSXML
bin.base64芸当は、今日も本番の Excel マクロの中で動いています。つまり1990年代後半の回避策と、2003年のConvert呼び出しが、同じ組織のコードベースの中でうまく共存しているということです。
出発する前に
この記事では、Visual Basic における Base64 のデコード側を、一行からストリーミングまで、JWT から VB 帽子の落とし穴まで扱いました。硬貨のもう半分、バイトやテキストを Base64 に変える作業そのものは、パディング、改行、サイズの課税についてのそれぞれの決断を持っていて、姉妹サイトの伴走エンコード記事で細部まで扱われています。そのリンクはちょうどこの行の下にあります。そしてホームページのツールは、小さなペイロードを手で確かめる最速の道であり続けます。
最終更新: 2026-10-10