PowerShell での Base64 デコード:完全ガイド
ログの行、設定ファイル、あるいはエラーメッセージのどこかで、それに遭遇します:英字と数字が長く続き、時々見かけるプラス記号やスラッシュ、そして末尾に怪しげに停まっている 1 つか 2 つの等号。ノイズのように見えます。でもそうではありません。それは Base64 であり、あなたが欲しいものももう分かっています:その中に隠されているものです。
Base64 は翻訳です。圧縮でもなければ、鍵でもありません。どんなバイト列でも印字可能なテキストに書き換え、入力バイト 3 つにつき文字 4 つを出力します(そのためエンコードされたデータは元より約 33% 大きくなります)。使うのは 64 文字のアルファベットに、末尾パディングとしての等号です。このサイトのホームページでは、アルファベット、ビットの計算、各変形を一通り扱っているので、この記事は PowerShell が差をつける場所で時間を過ごします:あなたが呼ぶ 1 つの .NET メソッド、それが強制するルール、そして実際の作業で PowerShell でのデコードが面白くなる 10 か所ほどの角です。
メソッドと、その契約
PowerShell には、独自の Base64 cmdlet は同梱されていません。その仕事を担うのは .NET クラスのメソッドで、2003 年の .NET Framework 1.1 からフレームワークの一部であり、PowerShell 自体が登場する 3 年前のことです:
$bytes = [System.Convert]::FromBase64String("SGVsbG8sIFdvcmxkIQ==")
[System.Text.Encoding]::UTF8.GetString($bytes)
# Hello, World!
これが API の全部です:文字列が 1 つ入って、バイト配列が 1 つ出てくる。それは .NET であるだけで成り立つので、あらゆる OS のあらゆる PowerShell、Windows PowerShell 5.1 でも、Windows、Linux、macOS の PowerShell 7 でも動きます。契約は暗記できるほど短いので、表にしておきます:
| 入力 | 戻ってくるもの |
|---|---|
$null |
空の配列、エラーなし。PowerShell は呼び出しの前に $null をそっと空の文字列に変えます |
| 空の文字列 | 空の配列、エラーなし |
| 有効なペイロード | byte[]。データがテキストであっても、文字列になることは絶対にありません |
| 無効なペイロード | FormatException が、MethodInvocationException に包まれて返ってきます |
エラー処理を書く前に、警告を 1 つ。その FormatException は、3 つの異なる罪を 1 つのメッセージでまとめて処理します。アルファベット外の文字、2 つを超えるパディング文字、パディングの間に隠れた非空白文字、これらはすべてまったく同じ文を生成します。それが起きたとき、メッセージはあなたがどの罪を犯したかは教えてくれません。だから、自分の入力を振り返って読むしかありません:
try {
[System.Convert]::FromBase64String("SGV!G8s=")
}
catch {
$real = $_.Exception.InnerException
$real.GetType().Name
# FormatException
$real.Message
}
そして「4 の倍数」ルールには、初めてぶつかった人が驚くエッジがあります。パディングなしの 4 文字は完全なまでに有効です。最後の文字の余ったビットが捨てられる、というだけの話です。3 文字は 4 の倍数ではないので、拒否されます:
[System.Convert]::FromBase64String("SGVs").Count
# 3:パディングなしの 4 文字なら問題なし
[System.Convert]::FromBase64String("SGV")
# FormatException: 3 文字は 4 の倍数ではない
デコーダーが受け入れ、受け入れないもの
デコーダーはアルファベットについては厳格で、特定の 1 点については寛大です。有効な文字は、64 個の Base64 数字(A から Z まで、a から z まで、0 から 9 まで、それにプラス記号とスラッシュ)と、末尾パディングとしての等号です。無視される空白文字は、どこにどれだけ頻繁に現れてもちょうど 4 つ:タブ、改行(line feed)、キャリッジリターン、スペースです。公式の .NET ドキュメントがこれらを Unicode 名で列挙しているのは、これが文書化された保証であり、たまたまうまくいった偶然ではないことを示しています。
実地でこれが超能力になります。Base64 を世に知らしめたメールエンコーディングの MIME は、エンコードされた行を 76 文字で折り返すので、メール、チケット、ログファイルを経てきたペイロードは、たいてい多くの行に断ち割られた状態で届きます。デコーダーはそれを気にしません。そのまま貼り付けてください:
$wrapped = "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4gQmFzZTY0IHRleHQg`r`n" +
"YXJyaXZlcyB3cmFwcGVkIGF0IHNldmVudHktc2l4IGNvbHVtbnMgaW4gbWFpbCwgc28gdGhlIGRl`r`n" +
"Y29kZXIgbXVzdCBub3QgY2FyZS4="
[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($wrapped))
# The quick brown fox jumps over the lazy dog. Base64 テキストは
# メールでは 76 桁で折り返されて届くので、デコーダーはそれを気にしてはならない。
アルファベット文字ではない他のすべては、そこで確実に止まります。現場で最もよく見かける犯人は、ノンブレイキングスペース(ウェブページから貼り付けたテキストの常連)と、バイト順マーク(BOM)(ファイルを読み込む際にエンコーディングを間違えるとついてくる、目に見えない印)です。このメソッドにとってどちらも空白文字ではないため、どちらでも例外を投げます:
try {
[System.Convert]::FromBase64String("SGVs`u{00A0}G8=")
}
catch {
$_.Exception.InnerException.GetType().Name
# FormatException
}
この厳格さは意図的なもので、気まぐれではありません。2006 年に Base64 を標準化した RFC 4648 は、プロトコルが明示的に寛容さを許す場合を除き、実装はアルファベット外の文字を拒否しなければならないと定めています。なぜなら、異質な文字をこっそり飲み込んでしまうデコーダーは、アルファベットしか検査しないものをすり抜けてデータを密輸するための隠しチャネルに仕立て上げられてしまうからです。.NET のデコーダーは厳格なルールに従っており、たいていあなたが望むのもそれです。
バイト配列は文字列ではない
このメソッドは、意図的にバイト配列で止まります。そのバイトが何を意味するのかは、2 番目の判断であり、下せるのはあなただけです。それを誤ると、PowerShell の Base64 作業で最も有名な間違いを犯します。既定の前提である UTF-8 は、インターネット上のほぼすべてに対して正しく、往復は 2 回の呼び出しです:
$bytes = [System.Convert]::FromBase64String("SGVsbG8sIFdvcmxkIQ==")
[System.Text.Encoding]::UTF8.GetString($bytes)
# Hello, World!
実際に手が伸びるエンコーディングと、それを誤ったときにそれぞれ何が起きるか:
| エンコーディング | 使う場面 | 間違えたとき |
|---|---|---|
UTF8 |
Web API、JSON、JWT、現代的なすべて。安全な既定値 | Latin-1 や UTF-16 のテキストは文字化けで返ってくる |
Unicode(UTF-16LE) |
ペイロードが Windows のツール、レジストリ値、あるいは送信前にエンコード済みの .NET 文字列から来たとき | 2 バイトであるべきところを 1 バイト読んでしまうため、すべての文字の周りに間ができる |
ASCII |
従来の HTTP Basic 認証情報など、7 ビット保証のプロトコル | 値 127 を超えるものはすべて、疑問符になる |
Latin1 |
UTF-8 より前の、レガシーなヨーロッパのテキスト | マルチバイトの UTF-8 シーケンスが、いくつもの誤った文字に割られる |
Default |
ほとんどない。これはそのマシンのシステムコードページだから | スクリプトが Windows のリージョン設定ごとに異なる動作をする |
古典的な失敗は、UTF-8 のテキストを UTF-16 としてデコードすることです。バイトは本物で、メソッドは満足そうに動きますが、結果はゴミのままです:
#「SGk=」は「Hi」の UTF-8 バイト
[System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String("SGk="))
# 読めない文字が 1 つ:UTF-8 の 2 バイトが、2 バイトの UTF-16 ユニット 1 つとして読まれる
実務的なルール:デコードされたテキストが、すべての文字の周りに見えない間があるように見える、あるいは別のアルファベットのよう見えるなら、あなたは 1 つエンコーディングをずらしています。データがどこで作られたかを聞きましょう。迷ったら UTF-8 を信頼してください。ただし、最初の数文字は目で確認を。そして、ログに文字化けが出てからでなく、デコードする前にエンコーディングを決めましょう。
base64url: URL で通いやすいアルファベット
あなたが触れる API トークン、JWT、URL に埋め込まれた識別子のすべてで、Base64 のいとこに遭遇します。標準 Base64 のプラスとスラッシュは URL ではパーセントエンコーディングを通過してはじめて合法になり、等号のパディングはフィールドの区切り文字のように見えてしまいます。そこで RFC 4648 は、URL とファイル名に安全なアルファベットを定義しました:同じ 64 文字で、ただしプラスがハイフンに、スラッシュがアンダースコアになるだけです。パディングはたいていすべて落とされます。データの長さから推定できるため、不要だからです。RFC は慎重に、この変形は base64url と呼ぶべきで、単に「base64」ではないと述べており、このセクションの後半もそれに従います。
.NET にはこれ専用のクラスが同梱されています:System.Buffers.Text.Base64Url です。.NET 9 で追加され、高速なエンコードとデコードのメソッドは、すべて ReadOnlySpan<T> パラメータのまわりに構築されています。現行の PowerShell(7.4 以降で、このクラスを同梱する .NET 版本の上で実行されていれば)は、メソッドバインダーが今や実行する配列/文字列から span への暗黙の変換のおかげで、今日すでにこれらの span 引数オーバーロードを直接呼び出すことができ、[System.Buffers.Text.Base64Url]::DecodeFromChars("--__AQI") は儀式なしで動きます。しかし、それは常に本当だったわけではありません。Windows PowerShell 5.1 と古い PowerShell 7.x リリースは、span パラメータにバインドできず、そもそも .NET 9 以前にはこのクラスが存在しませんでした。だから、5.1、古い 7.x、.NET 9 以前のホストで動かさねばならないスクリプトは、依然としてポータブルな版を必要とします:2 つの文字を入れ替えて、テキストを標準デコーダーに渡す前にパディングを復元するのです。足すパディングは、長さが 4 の倍数になるだけのものです:
$token = "--__AQI" # base64url、パディングなし
$standard = $token.Replace("-", "+").Replace("_", "/")
$pad = 4 - ($standard.Length % 4)
if ($pad -eq 4) { $pad = 0 }
$standard = $standard.PadRight($standard.Length + $pad, "=")
$bytes = [System.Convert]::FromBase64String($standard)
$bytes -join ","
# 251,239,255,1,2
あの小さなブロックには、罠が 2 つ住んでいます。1 つ目は、パディングの計算:長さがすでに 4 の倍数のペイロードにはパディングが不要で、-eq 4 のガードが、その式を誠実なまま保っています。2 つ目は、方向:デコードだけを行う場合、あなたはパディングを加えて文字を入れ替えます。標準 Base64 入力からパディングを削除することは決してありません。標準デコーダーは、それがそこにあることを期待しているからです。もし出所が JWT や API トークンなら、それはパディングなしの base64url であり、上記のレシピこそがまさにあなたが望む形です。
鍵なしで JWT を開く
JSON Web Token は、ドットで結ばれた base64url の 3 セグメントです:ヘッダー、ペイロード、署名。最初の 2 つはそのままの JSON で、Base64 は暗号化ではないので、トークンを持っていれば誰でも両方を読み取れます。それは機能であり、欠陥ではありません:トークンは検査されるように設計されており、署名こそがそれを偽造不能にしているものです。PowerShell なら、こっそり中を見るのは 3 行のコマンドになります:
$jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
$parts = $jwt.Split(".")
function Decode-UrlSegment([string]$segment) {
$standard = $segment.Replace("-", "+").Replace("_", "/")
$pad = 4 - ($standard.Length % 4)
if ($pad -eq 4) { $pad = 0 }
$standard = $standard.PadRight($standard.Length + $pad, "=")
return [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($standard))
}
Decode-UrlSegment $parts[0] | ConvertFrom-Json | ConvertTo-Json -Compress
Decode-UrlSegment $parts[1] | ConvertFrom-Json
# name プロパティ:
(Decode-UrlSegment $parts[1] | ConvertFrom-Json).name
# John Doe
心に留めておくべきことを 3 つ挙げます。3 番目のセグメント、つまり署名は base64url ですが、デコードされるのはテキストではなくバイナリの署名バイトなので、そこで美しい JSON を期待しないでください。ヘッダーはたいてい、どのアルゴリズムでトークンが署名されたか(HS256, RS256, ...)を伝えるだけです。そして none と書かれたヘッダーは、便利なものではなく赤いフラグです。さらに、ペイロードを読むことは、それを信頼することではありません:base64 はクレームを見ることを可能にし、クレームを本物にするのは署名だけです。あなたの仕事がトークンを受け入れることなら、発行者の鍵で署名を検証してください。あなたの仕事がある 1 つをデバッグすることなら、上記のコードがあれば十分です。
ファイル、PEM、そしてバイトへの長い道
最も一般的なファイルの形は、より大きなものの Base64 を保持するテキストファイルです:バックアップのブロブ、ダウンロードされたバイナリ、シリアライズされたオブジェクト。往復は 4 行で、出力を読む現代的な方法は、テキストの推測ではなく本物のバイト配列です:
$encoded = Get-Content -Path ./payload.b64 -Raw
$encoded = $encoded.Trim()
$bytes = [System.Convert]::FromBase64String($encoded)
[System.IO.File]::WriteAllBytes("./payload.bin", $bytes)
$bytes.Length
# テキストが抱えていたバイト数
元のバイナリを読み戻すときが、PowerShell 6 以降がその価値を正当にする場面です。-AsByteStream パラメータは生のバイトを読み、-Raw を付けると、本物の byte[] を 1 回の手続きで手渡してくれます:
$bytes = Get-Content -Path ./photo.png -AsByteStream -Raw
$encoded = [System.Convert]::ToBase64String($bytes)
Set-Content -Path ./photo.b64 -Value $encoded -NoNewline
$bytes.Length
# 33% のテキスト税を払う前の、元のサイズ
-Raw を省くと、個々のバイトオブジェクトのストリーム(キャプチャすると Object[])が戻ってきます。検査には十分ですが、配列を期待する .NET メソッドに渡すのには不適切です。そして Windows PowerShell 5.1 にはそもそも -AsByteStream がなく、5.1 での確実な読み取りは、どこにでもある [System.IO.File]::ReadAllBytes() です。
PEM は、すべての証明書と秘密鍵から知っている、鎧をまとったいとこです:標準 Base64 のボディで、通常は 64 文字で折り返され、-----BEGIN ... と -----END ... の行の間に置かれています。鎧はテキストで、ボディがペイロードです。鎧を剥いで、行を結合して、デコードします:
$pem = Get-Content -Path ./certificate.pem -Raw
$body = ($pem -split "`n") | Where-Object { $_ -notmatch "^-----" } | ForEach-Object { $_.Trim() }
$der = [System.Convert]::FromBase64String(($body -join ""))
$der.Length
# 証明書のバイナリ DER のサイズ
標準デコーダーはそもそも空白を無視するため、-join "" は必要条件というより二重の安全装置です。ただし、何を剥ぐのかをスクリプトが明示的に保つことで、どのマシンでもどの改行慣習でも同じ動作をします。反対方向、つまり DER バイトを PEM に包むのは、Base64 エンコーダーに 2 行のテキストを加えるだけで、姉妹サイトのエンコード記事では 64 桁の折り返しをすべて扱っています。
証明書と Windows の道具箱
日常の作業で、証明書は Base64 の中でも最も重い市民です。PowerShell はその一族を丸ごと保持できます。PFX ファイルは、証明書と秘密鍵のバイナリバンドルで、設定ファイルやデプロイスクリプトの中で Base64 テキストとして放置されているのを、最もよく見かける形式です。.NET タイプを使えば、それを生きた証明書にデコードし直すのは 1 行で、PowerShell 7 ではクロスプラットフォームで動作します:
$bytes = [System.Convert]::FromBase64String($pfxText)
$password = ConvertTo-SecureString "secret" -AsPlainText -Force
$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($bytes, $password)
$cert.Subject
# CN=example.org
$cert.NotAfter
# それが真ではなくなる日
PowerShell 7 には Get-PfxCertificate も同梱されており、-Password パラメータで PFX ファイルをディスクから直接読み取るので、ディスク上のファイルについては手動のデコードを完全にスキップできます。鍵のない素の証明書はさらに簡単です:DER バイトを、パスワードなしで同じ X509Certificate2 タイプに直接渡せます。
言語の外側では、知る価値のあるネイティブなツールが 2 つあります。Windows では、certutil -decode infile.b64 outfile がファイル入力/ファイル出力の意味論で Base64 ファイルをデコードします(上書きするには -f を追加)。それが、素のコマンドプロンプトでの素早い修正の定番になっています。その兄弟の certutil -encode には、覚えておく価値のあるフラグがあります:-unicodetext は、Base64 でエンコードする前に入力テキストを UTF-16 に変換し、エンコーディング判断全体を 1 つのスイッチの中に隠しています。Linux と macOS の古典的なユーティリティは base64 -d で、ファイルまたは標準入力をデコードし、既定では改行をスキップします。ペイロードが Windows メール由来のスペース、タブ、CRLF も運んでいるなら、GNU coreutils では -i を追加してください。
Base64 の封筒に入ったコマンド
PowerShell はバージョン 1.0 以来、Base64 を話すための組み込みの理由を持っていました:ホスト自体の -EncodedCommand パラメータです。pwsh に Base64 文字列を手渡すと、バイト列を UTF-16LE としてデコードし、その結果がコマンドとして実行されます。公式の目的は、ドキュメントからそのまま引用すると、複雑な引用符や波括弧を必要とするコマンドを、外側のシェルのクォーティングルールと戦うことなく提出することです:
$command = "Write-Host encoded-hello"
$encoded = [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($command))
# VwByAGkAdABlAC0ASABvAHMAdAAgAGUAbgBjAG8AZABlAGQALQBoAGUAbABsAG8A
pwsh -NoProfile -EncodedCommand $encoded
# encoded-hello
その 2 行目をよく読んでください。誰もがそこでつまずくからです:ペイロードは UTF-16LE でなければならず、それは [System.Text.Encoding]::Unicode です。もしコマンドを UTF-8 でエンコードすると、PowerShell は喜んでそれを UTF-16LE としてデコードし、文字化けでできたコマンドを実行します。そこで生じるエラーメッセージは、その間違いの完璧な肖像画です:
$wrong = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($command))
pwsh -NoProfile -EncodedCommand $wrong
# エラー:壁のような文字化け、"The term ... is not recognized..."
同じメカニズムが、セキュリティチームが PowerShell における Base64 を気にする理由です。-EncodedCommand に渡される長い不透明なトークンは、自動ツールには一般的な形で、まさにそれゆえにエンドポイント保護製品は、実行前にこれらのペイロードをデコードします:Base64 について、デコーダーからコマンドを隠すものは何もなく、それはプロセスリストを読む人間からだけコマンドを隠すからです。自分の自動化のためにエンコードされたコマンドを生成するなら、元のコマンドをトークンのそばに置いておいてください。午前 3 時にトークン自体が自分を説明してくれるわけではないからです。
入力が巨大なときのデコード
日常のサイズなら、1 メソッド方式が速い方法です。5 メガバイトのバイナリは、およそ 690 万文字の文字列になり、その文字列のデコードはモダンなマシンで 1 桁のミリ秒で完了します。.NET ドキュメント自身の注記は、FromBase64String はすべてのデータを含む単一の文字列を処理するよう設計されている、というもので、それは真実であり、非常に大きな限界まで問題ありません。なぜならこのメソッドは、意味のある余分なコピーなしに文字列のその場で動作するからです。
ペイロードが 1 つの文字列で保持するには大きすぎる、あるいはストリームとして届く(ダウンロード、ソケット、巨大なログ)場合、ドキュメントに記載されているツールは、CryptoStream に包まれた System.Security.Cryptography.FromBase64Transform です:Base64 テキストを給餌し、デコードされたバイトを読み出すと、どの瞬間にも生きているのは小さなバッファだけです。注意してください:これのための C# ヘルパーである TransformStream は拡張メソッドであり、PowerShell は拡張メソッドが見えないため、CryptoStream を直接インスタンス化します:
$inputStream = [System.IO.File]::OpenRead("./payload.b64")
$transform = [System.Security.Cryptography.FromBase64Transform]::new()
$stream = [System.Security.Cryptography.CryptoStream]::new(
$inputStream, $transform, [System.Security.Cryptography.CryptoStreamMode]::Read)
$destination = [System.IO.File]::Create("./payload.bin")
$buffer = New-Object byte[] 65536
while (($read = $stream.Read($buffer, 0, $buffer.Length)) -gt 0) {
$destination.Write($buffer, 0, $read)
}
$destination.Dispose()
$stream.Dispose()
$inputStream.Dispose()
90% の作業では、シンプルな経路が依然として正しいものです:テキストファイル全体を Get-Content -Raw で読み、切り詰め、デコードし、バイトを書き込む。ファイルがメモリに快適に保持できるサイズを超えている、あるいはデータが少しずつ届いているときに、ストリーム版の手を伸ばしてください。そして、行をループして各行を別々にデコードしようとはしないでください:Base64 の 4 文字グループはあなたの改行を尊重しないため、グループを途中で分断する行は、それだけではデコードできません。テキスト全体を読み、それから 1 回デコードするのです。
午後の半日を失う罠
- 文字セットの推測。UTF-8 を UTF-16 として読み、Latin-1 を UTF-8 として読むと、自信満々の文字化けが生まれます。データの出所からエンコーディングを決定し、既定は UTF-8 にして、残りを信頼する前にデコードされた最初の数文字を見てください。
- ウェブ由来の不可視文字。ページやリッチテキストのメールから貼り付けたノンブレイキングスペースやバイト順マーク(BOM)は、デコーダーにとっての異質な文字で、汎用の
FormatExceptionを投げます。デコードする前に、入力を.Trim()と非印刷文字チェックに通してください。 - パディングの混同。標準 Base64 は末尾に
=または==付きで届き、トークン由来の base64url はパディングなしで届きます。片方を、もう片方のために作られたレシピに渡すのは、API 作業で最も一般的な静かな破損で、base64url セクションの長さチェックこそがガードです。 - 1 つのメッセージ、3 つの罪。
FormatExceptionのメッセージは、不正な文字、過剰なパディング、汚れたパディングをすべて一度にカバーするため、メッセージだけをログする catch ブロックはあなたを回り道に追い込みます。入力の長さと、最初に問題が起きた領域もログにしてください。 - 文字列が戻って来るのを期待する。結果は常にバイト配列です。それを直接文字列フォーマットし始めた瞬間、得るのはテキストではなく数字のリストです。エンコーディングを明示して、最後に 1 回だけ変換してください。
- 5.1 のファイル既定。Windows PowerShell 5.1 は BOM なしのファイルをシステムの ANSI コードページで読み、PowerShell 7 は UTF-8 を前提にします。スクリプトが 5.1 で Base64 テキストファイルを読む場合、ファイルがペイロードの周りに非 ASCII を含む UTF-8 なら、破損はデコーダーがそれを見る前に起きます。
- Base64 を鍵として扱う。それは翻訳です。Base64 のパスワード、トークン、シークレットは、仮装した平文であり、この記事を含む世界中のすべてのデコーダーは、それを 1 行で開きます。
スクリプトを誠実なまま保つ習慣
- デコード前に外部入力を切り詰める。1 つの
.Trim()が、どんなエラーハンドラーよりも多くの本番インシデントを除去します。 - 出所が信頼できないときは、デコード前に検証する:許された 4 つの空白文字を剥いだあと、文字列はアルファベット文字と、最大 2 つの末尾の等号にのみマッチしなければなりません。さっさと正規表現チェックをすれば、謎の例外は、すっきりとした入力の拒否メッセージに変わります。
- バイトを、最終ステップまでバイトのまま保つ。1 回デコードし、
byte[]をそれを必要とするファイル API かエンコーダーに渡し、それから始めて、意図したエンコーディングでテキストに変換する。 - ペイロードではなく、長さをログに出す。入力のサイズとデコードされた出力のサイズは、ログに機密になりうるデータを貼り付けることなく、デコード失敗のほぼすべてを教えてくれます。
- ワイヤーを渡るものについては、それがどのアルファベット(標準か base64url か)と、どのパディング規則に従っているかを、それをデコードするコードの同じ行に記録しておく。未来のあなたが、そのメモの消費者です。
PowerShell がデコーダーを継いだ経緯
PowerShell における Base64 の、最も短い本当の歴史は、PowerShell はそれを一度も書いたことがない、というものです。あなたが使うメソッド、Convert.FromBase64String は 2003 年に .NET Framework 1.1 とともに登場し、2006 年 11 月のバージョン 1.0 以降のすべての PowerShell は、単に自分が動かしている .NET を公開しているだけです。このプロジェクトは開発中は Monad と呼ばれ、2003 年 10 月の Professional Developers Conference で初めて一般公開されました。リリースの頃には、PowerShell が包む .NET のエンコーダー/デコーダーのペアはすでに 3 年の歳月を経て、毎日使われていました。
形式自体は、シェルが登場した同じ年に標準化されました。2006 年 10 月に公開された RFC 4648 が、アルファベット、パディング規則、厳格デコードの期待、base64url 変形を固定した文書で、今日 FromBase64String が実装している動作を、今もまさにそのまま記述しています。PowerShell が 2016 年 8 月に PowerShell Core としてオープンソース化・クロスプラットフォーム化するとき、デコーダーは何の変更もなしに Linux と macOS に同行しました。変えるものが何もないからです。
唯一の本物の付加物は、PowerShell Gallery からのコミュニティ管理の Microsoft.PowerShell.TextUtility モジュールで、その ConvertFrom-Base64 cmdlet は同じ .NET メソッドを包み、-AsByteArray スイッチに加えて、UTF-8 としてデコードするテキスト既定値を追加しています。cmdlet の形を好むなら Install-Module -Name Microsoft.PowerShell.TextUtility でインストールしてください。ただし注意点が 1 つ:このモジュールは現在はアーカイブされており、アクティブに維持されていません。これがもう 1 つの理由で、組み込みメソッドが新規スクリプトへの推奨を続けているのです。
覚えておきたい事実
- デコーダーは、入力のどこにあってもタブ、改行(line feed)、キャリッジリターン、スペースを無視します。折り返された 100 行は、長い 1 行とまったく同じようにデコードされます。
$nullと空の文字列はどちらも、文句なしに空の配列にデコードされます。これはFromBase64Stringを、エッジのところで異様に寛大にしています。- 1 つの
FormatExceptionメッセージが、3 つの異なる失敗モードをカバーします。それが発火したとき、答えがあるのはメッセージではなく入力です。 "SABpAA=="は、Hiという文字列を PowerShell 自身の内部エンコーディング、UTF-16LE で表したものです。同じ 2 文字の UTF-8 エンコーディングの 2 倍の長さで、その比率は、あなたが読むあらゆる Base64 にある Windows ネイティブなテキストの指紋です。-EncodedCommandは最初の PowerShell リリースから存在し、そのペイロードは UTF-8 ではなく UTF-16LE と定められています。間違ったエンコーディングでエンコードすると、シェルは喜んであなたの文字化けを実行します。- .NET の新しい span ベースの Base64 ヘルパー(
Base64Urlクラスを含む)は、古い PowerShell リリースから到達できませんでした。span は byref 風型であり、メソッドバインダーがバインドできなかったためです。それが変わりました:現行の PowerShell(7.4 以降、そのクラスが同梱されるほど新しい .NET 版本)は、配列または文字列引数をReadOnlySpan<T>パラメータに対して文句なく解決するので、今日の直接呼び出しは動きます。2 文字のスワップは、残された唯一の経路としてではなく、Windows PowerShell 5.1 や古いホストでも動く版として、その価値を保っています。 -RawなしのGet-Content -AsByteStreamは、バイト配列ではなくバイトオブジェクトのストリームを返します。-Rawを追加すると、型は .NET メソッドが期待するものと完全に一致します。
大回り
この記事のすべては、Base64 文字列を取り、あなたのデータを元に戻すことについてです。鏡像の操作、つまりデータを Base64 に変える方は、1 行のコマンドのように見えます。ただし、PowerShell の文字列はバイトではない、UTF-16 はサイズを 2 倍にする、行折り返しには 2 つの慣習的な幅がある、base64url の出力は独自の 2 文字の手術を必要とする、という事実に出会うまでは。その方向は、姉妹サイトの関連記事「PowerShell での Base64 エンコード」の中で、独自の罠と独自の歴史を携えて、独自の完全な扱いを受けます。このページは下記にそれをリンクしています。
最終更新: 2026-10-10