繁體中文

開發者工具 · Base64 編碼器和解碼器

在不使用 mojibake 的情況下將 Base64 解碼為 UTF-8:atob 加 TextDecoder

· 工作原理

base64 編碼 統一碼

從 Base64 到 UTF-8 的管道顯示 mojibake 失敗
原始 ToolAcre 向量圖

atob() 傳回偽裝成字元的字節,這就是為什麼帶有重音的文字在解碼後看起來很損壞。這篇文章展示了從 Base64 到位元組到 UTF-8 文字的正確管道,以及如何識別故障模式。

解碼為「Café」的 API 回應 — 具體的 mojibake 症狀以及兩個錯誤字元後面的兩個位元組

Base64 字串 Q2Fmw6k= 解碼為位元組 (67, 97, 102, 195, 169),即 UTF-8 文字館。貼到樸素解碼器中(只需 atob 和字串轉換),輸出通常是 Café,每個重音都被兩個錯誤的字元取代。發生這種 mojibake 是因為 atob 傳回位元組字串(代碼單元 0–255),而不是 UTF-8 文字。位元組 195 和 169 對 UTF-8 中的重音 é 進行編碼。

將它們視為單獨的拉丁語-1 字元會給出 mojibake 模式。正確的管道是 atob(位元組作為字串),然後是 TextDecoder(將位元組解釋為 UTF-8),然後傳回原始文字。 atob功能沒有被破壞;它是為二進位資料設計的。它的名字來自 ASCII-to binary,它產生的二進位字串是代碼單元 0–255 的序列,每個代碼單元代表一個位元組。

atob() 實際上返回的是一串代碼單元 0–255 ,代表字節,而不是解碼後的文字

如果您輸入 Q2Fmw6k=(標準 Base64),它會輸出字串,其中每個字元都是一個位元組:代碼單元 67,然後是 97,然後是 102,然後是 195,然後是 169。如果直接顯示該字串或解釋為 Latin-1 文字,您會看到亂碼輸出。缺少的步驟是將代碼單元轉換為位元組數組,然後將數組解碼為 UTF-8。

charCodeAt 循環恢復位元組值:對於 atob 輸出中的每個字符,請呼叫 charCodeAt 取得代碼單元(編號 0–255),儲存在 Uint8Array 中。一旦位元組數組存在,就使用字元集 utf-8 傳遞給 TextDecoder。 TextDecoder 讀取位元組序列並解釋為 UTF-8 文字,將 (195, 169) 等位元組序列組合成 é 等單字元。位元組 (67, 97, 102, 195, 169) 變成四個字元字串 Café。這個循環故意很無聊:用 charCodeAt 讀取每個傳回的程式碼單元並將其分配給匹配的 Uint8Array 位置。那裡不會發生任何字符集決定。當 TextDecoder 接收該陣列並套用 UTF-8 並進行致命錯誤處理時,唯一的解釋就會出現。

將該字串轉換為 Uint8Array — charCodeAt 循環以及為什麼它是位元組複製而不是轉換

這兩個步驟的過程(位元組恢復,然後 UTF-8 解釋)是 Base64 編碼器和解碼器內部執行的操作。 mojibake 模式是此錯誤的明顯跡象。如果 Café 顯示為 Café,您將看到 UTF-8 位元組的 Latin-1 解釋。 é 的 UTF-8 位元組為 0xC3 0xA9(十進位 195、169)。在 Latin-1 中,代碼單元 195 是 à,代碼單元 169 是 ©。

當讀取 UTF-8 位元組序列時,就好像每個位元組都是單獨的拉丁語-1 字元一樣,每個多位元組 UTF-8 序列都會產生錯誤的替換字元。如果 Café 顯示為 Caf 後跟替換字符,還是 Café?或 Caf 加 U+FFFD,您會看到不同的失敗:解碼器未將位元組序列識別為有效的 UTF-8。具體範例:Base64 SGVsbG8sIOS4lueVjCEg8J-Zgg== 解碼如下。

TextDecoder 和字元集決策 - 解碼為 UTF-8,以及為什麼字元集是您必須了解的單獨事實

atob 產生具有位元組的二進位字串 (72, 101, 108, 108, 111, 44, 32, 228, 184, 180, 149, 140, 33, 32, 240, 159, 152, 152, 159, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152, 152。前六個位元組是ASCII:成為Hello,。位元組228、184、180是表示CJK字元的三位元組UTF-8序列。位元組 149、140 是下一個序列的一部份。完整序列包括最終字符的四字節表情符号序列(240、159、152、130)。

當透過 TextDecoder 正確處理時,所有位元組組合起來產生原始的混合腳本文字。 UTF-8 位元組序列具有可預測的長度:以 0xxxxxxx 開頭的位元組是單字節 ASCII;以 110xxxxx 開頭的位元組預計以 10xxxxxx 開頭的後續位元組(總共兩個位元組);以 1110xxxxxxx 的xxxxxx開頭的位元組需要三個後續位元組(總共四個位元組)。對於混合的 CJK 和表情符號樣本,位元組視圖尤其具有診斷性,因為 ASCII 直覺不再有幫助。每個可見符號都有幾個位元組,刪除一個位元組會將剩餘序列移至無效的 UTF-8 中。嚴格的解碼將這種轉變轉變為命名的故障,而不是看似合理的損壞。

工作範例:解碼包含 CJK 和表情符號的 Base64 字串 — 位元組、程式碼點以及與原始字串相比的最終字串

以 1111110x 開頭的序列在 UTF-8 中無效(為將來保留,未使用)。以 10xxxxxx 開頭的位元組絕不能作為前導位元組出現;這是延續。如果位元組流違反規則,則 UTF-8 無效。具有字元集 utf-8 的 TextDecoder 會依照這些規則解釋數組,並成功獲得有效序列。無效則報錯。

Base64 編碼器和解碼器使用嚴格模式標誌為 true 的 TextDecoder。這意味著無效的 UTF-8 會引發錯誤,而不是默默地插入替換字元 (U+FFFD)。如果 Base64 字串解碼為無效 UTF-8 的字節,嚴格模式將拋出異常,而不是繼續處理亂碼文字。這是設計選擇:二進位有效負載(圖片、金鑰、壓縮資料)不是文字,不應解碼為文字。

辨識模式: à、’ 和 � — 如何區分 Base64 問題和字元集問題

如果嘗試將 JPEG 解碼為 Base64,位元組流將不代表有效的 UTF-8,嚴格解碼將拒絕它。工具為此類有效負載提供十六進位視圖:您可以看到原始字節,而無需假裝它們是文字。識別 UTF-8 解碼錯誤涉及查看上下文中的位元組。它們是多位元組序列預期的奇數嗎?

潛在序列的第一個位元組是否無效(以 10xxxxxx 開頭)?是否缺少連續位元組?位元組輸出按鈕提供了調查中最乾淨的分叉。如果出現十六進位但文字解碼失敗,則 Base64 解析成功,並有效負載是二進位的、損壞的或使用不同的字元集編碼的。在出現正確的位元組後,變更 Base64 標點符號無法修復字元集不符。

這不包括 UTF-16 有效負載、二進位輸出(例如圖片)和無效位元組處理

模式是一致的。單字節 0xFF 在 UTF-8 中永遠無效;它不能是 ASCII 位元組(只有 0-127 是 ASCII),也不能是前導位元組(前導位元組是 0xC0-0xFD,0xFF 保留)。單獨代理(UTF-16 概念)不能出現在 UTF-8 中;如果看到位元組序列 0xED 0xA0 0x80(以 UTF-8 樣式編碼代理項 U+D800),則它不是有效的 UTF-8。

一種歷史解決方法是 btoa(unescape(encodeURIComponent(text)))。 encodeURIComponent將café轉換為%C3%A9(百分比編碼UTF-8位元組),unescape重新包裝為代碼單元,btoa對代碼單元進行編碼。這適用於大多數文字,但在單獨的代理項周圍很脆弱並難以閱讀。現代管道(TextEncoder 到字節,然後是 base64)更加清晰和標準。 TextEncoder 內建於所有現代瀏覽器和 Node.js 中,做出了正確的選擇。 UTF-16、傳統單字節編碼和任意檔案內容需要為這些位元組選擇解碼器或二進位感知檢視器。 ToolAcre 故意不在其中進行猜測。猜測可能會將無效序列變成誤導性文字,而十六進制轉儲會保留每個位元組以供以後進行明智的解釋。

重點:Base64 為您提供字節,UTF-8 為您提供文字 — Base64 編碼器和解碼器如何執行這兩個步驟,以便解碼後的文字與輸入完全匹配

當您有 Base64 字串並想要 UTF-8 文字時,完整的步驟是:將 Base64 解碼為位元組(使用 atob 或 base64 解碼庫),從位元組建立 Uint8Array,使用字元集 utf-8 將陣列讀取為字串Decoder,將結果作為字串Decoder。

如果輸入是二進位資料而不是文字,則跳過 TextDecoder 並直接檢查位元組。 Base64 編碼器和解碼器提供十六進位位元組視圖,保留嚴格 UTF-8 解碼會拒絕的值。這個分叉是診斷性的:成功的位元組加上失敗的文字意味著 Base64 解析有效,而有效負載是二進位的、損壞的或使用該工具無法猜測的字元集編碼的。