開發者工具 · Base64 編碼器和解碼器
RFC 4648 解釋:定義 Base64、base32 和 base16 的標準
· 背景
base64 編碼
RFC 4648 是每個 Base64 實作背後的簡短易讀的文檔。這篇文章將詳細介紹它所指定的內容、它故意保留的內容以及為什麼實作仍然不同。
兩個函式庫,同一個字串有兩個答案──一個真正的互通性難題,只有標準才能解決
兩個 JavaScript 函式庫可以為相同字串傳回不同的 Base64,每個函式庫都宣告正確性。 RFC 4648 是一份可讀的十二頁文件,應該可以解決此類分歧,但實作仍然有所不同,因為 RFC 故意將某些決定留給應用程式。本文將介紹 RFC 4648 指定的內容、它有意委託給呼叫者的內容,以及為什麼閱讀該標準一次就能解決大多數真正的互通性難題。 Base64 編碼器和解碼器工具包括 RFC 4648 測試向量,因此您可以根據權威範例驗證實作。
RFC 4648 取代並合併了幾個早期檔案:來自 MIME 的 Base64 (RFC 2045)、來自隱私增強郵件的 Base64 (RFC 1421)、來自 S/MIME (RFC 2630) 的 Base32 以及來自各種來源的 Base16。合併是必要的,因為 MIME 和 PEM 都有自己的字母表和規則,並 MIME 換行與 PEM 的 64 列區塊相衝突。 RFC 4648 在一個地方定義了五個編碼系列:base64、base64url、base32、base32hex 和 base16,每個系列都有自己的字母表、填充規則和範例測試向量。 Base64 字母依序為 A-Z、a-z、0-9、加號和斜線。
實作和測試向量建立了什麼——標準和 URL 安全字母表、填充和空白處理
每個字元代表 6 位元;三個輸入位元組(24 位元)對應到四個輸出字元。字母表不是任意的:它避免了 EBCDIC 和 ASCII 之間不同的字符,避免了控製字符、引號和需要在 C 字串文字中轉義的反斜杠。 base64url 變體將加號替換為破折號,將斜線替換為底線,以避免 URL 和檔案名稱中的保留字元。兩個變體同樣有效; RFC 4648 部分 2 指定 base64,部分 5 指定 base64url,應用程式必須宣告它使用哪一個。
使用等於字元填滿可使輸出達到四個字元的倍數。如果輸入是 1 位元組(8 位元),則輸出是兩個字元加兩個等號。如果輸入為 2 位元組(16 位元),則輸出為三個字元加一個等號。如果輸入是 3 位元組的倍數,則不需要填入。有些應用程式省略填充或允許在解碼時遺失填充; RFC 4648 部分 3.2 將規範編碼定義為始終填充,但 3.3 部分指出解碼器可能接受缺少填充以實現相容性。
此工具實現的字母表 — 標準 Base64 和 Base64url;其他基地仍在其範圍之外
填充的區別是實現不一致的原因:嚴格的解碼器拒絕缺少等於,而寬鬆的解碼器則接受它。 RFC 4648 明確指出:在 URL 中使用時,填充字元 equals 通常是百分比編碼的,因此如果直接在 URL 參數中使用 base64url 輸出,則不需要填充,應該省略填充。這句話是 URL 安全模式和填充省略經常配對的原因之一,儘管它們是獨立的選擇。 5 (base64url) 部分不禁止填充;它只是指出了常見的做法。
選擇 base64url 的呼叫者必須決定接收系統是否需要填入。不同的解碼器對輸入中的非字母字元進行不同的處理。 RFC 4648 部分 3.1 規定:如果編碼包含基本字母表以外的字符,則實作必須拒絕該編碼。但是,3.3 節指出,MIME Base64 (RFC 2045) 允許 76 字元換行換行,並 MIME 解碼器必須跳過空格。 RFC 區分嚴格解碼(拒絕所有非字母)和 MIME 相容解碼(跳過空格,拒絕其他字元)。
填充、非字母字元和規範編碼 - 解釋大多數解碼器分歧的部分
應用程式必須選擇要遵循的規則;該標準定義了兩者。 Base32 使用 A-Z 和 2-7 (總共 32 個字元),將五個輸入位元組(40 位元)編碼為八個輸出字元。 Base32hex 以 0-9 和 a-v 取代字母字符,這在首選小寫字母的上下文中很有用。
Base16 是十六進位:0-9 和 a-f。 Base32 和 base32hex 在 6 和 7 部分有自己的填充規則,並 RFC 為每個字母提供單獨的測試向量。大多數開發者只需要base64和base64url;為了完整性以及 TOTP 機密 (RFC 4226) 和 DNS 編碼等應用程序,RFC 中包含了 base32、base32hex 和 base16。
此實作中可見的應用程式選擇 — 換行、嚴格的文字解碼和錯誤處理
RFC 4648 中的測試向量是檢查實現的基本事實。對字串 f、fo、foo、foob、fooba 和 foobar 進行編碼會產生特定的 base64 輸出:Zg==、Zm8=、Zm9v、Zm9vYg==、Zm9vYmE= 和 Zm9vYmFy。為這些字串產生不同輸出的實作是不正確的。 RFC 提供了 base32、base32hex 和 base16 的等效測試向量。 Base64 編碼器和解碼器工具包含這些向量,因此您可以根據標準驗證其輸出。換行是 MIME 問題,而不是 base64 問題。
RFC 2045 指定 76 字元行; RFC 4648 部分 3.1 在 MIME 上下文中註意到了這一點,但並未將其作為 base64 本身的要求。某些應用程式以 64 字元換行(原始 PEM 標準);其他的則根本不包裹。嚴格的 RFC 4648 base64 解碼器僅對字母和填充進行操作。 MIME 相容的解碼器必須跳過換行符(CR、LF、CRLF)。在 MIME 之外使用 base64 的應用程式不應添加換行符,除非接收系統需要它們; RFC 沒有將換行定義為 base64 的一部分。
工作範例:RFC 自己的測試向量 — 編碼「foobar」前綴並在瀏覽器中檢查它們
空白處理是實現差異的另一個點。 RFC 4648 表示嚴格的解碼器必須拒絕非字母字元。 MIME 包裝的 base64 (RFC 2045 base64) 允許使用空格進行格式化。這兩個標準在輸出位元組應該是什麼方面達成一致,但在輸入有效方面有所不同。大多數 JavaScript 實作都會選擇 MIME 相容性並跳過空格;嚴格的規則很少在瀏覽器中使用。 Base64 編碼器和解碼器接受包含空格 (MIME) 和嚴格輸入,從而明確區分。規範解碼與寬容解碼是最後的主要差異。
規範解碼遵循 RFC 4648 部分 3.2:拒絕格式錯誤的填充、拒絕缺少的填充、拒絕非字母字元。 Web 標準中使用的寬容解碼(HTML 規範將其稱為 forgiving-base64)添加了規則:忽略空格、接受缺少的填充、允許破折號和下劃線作為加斜線等效項,即使在標準 base64 模式下也是如此。 JavaScript 的 atob() 是寬容的;嚴格的 RFC 4648 解碼器更嚴格。兩者都沒有錯;它們服務於不同的環境。從使用者或網路讀取資料的應用程式應該知道對方期望哪種規則。
這不包括的內容 — MIME 和 PEM 檔案本身以及特定於語言的 API
RFC 為應用程式留下了九個選擇:五個字母中的哪一個、是否需要或允許填充、是否需要或允許空格、是否將破折號下劃線視為加斜杠等效項、如何報告錯誤、如何處理輸入結束、是否接受缺少的填充、要分配多少輸出字節以及如何發出大小限制信號。這些選擇解釋了為什麼兩個 RFC 4648 實作在相同輸入上可能存在分歧。閱讀一次 RFC;根據測試向量檢查您的實作;說明您的應用程式使用哪些選項;測試與實際對等點的互通性,而不是假設。
了解 RFC 4648 解決了大多數 Base64 爭議,因為分歧通常不是關於 RFC 本身,而是關於雙方選擇的選項。 RFC 夠短,一小時內可從頭到尾讀完。該標準定義了字母表,提供了測試向量,並警告實現必須決定的地方。 Base64 編碼器和解碼器工具可讓您試驗測試向量並查看實際的標準字母表。大多數日常使用 Base64 不需要深厚的 RFC 知識;但是,當調試編碼不匹配或與不熟悉的 API 集成時,閱讀一次標準就可以消除猜測。
重點:閱讀一次標準 — Base64 編碼器和解碼器如何為您提供快速檢查標準字母測試向量的方法
RFC 4648 將數十年的臨時基本編碼實踐整合為一個可讀規範。它沒有定義何時使用base64(MIME、PEM、JWT、資料URI等,每個都有自己的規格);它定義了 base64 是什麼。透過定義五個編碼系列並註明哪些選項是規範的,RFC 可以檢查實作是否正確。權威測試向量是起點:如果您的實作對 foobar 進行編碼並產生 Zm9vYmFy 以外的任何內容,則 RFC 表示該實作是錯誤的。
使用該權限作為驗證檢查點:對每個 RFC 測試向量進行編碼,比較確切的字符,然後對結果進行解碼以確認原始位元組返回不變。這種基於瀏覽器的檢查將字母或填充錯誤與整合中其他地方的問題分開,同時將標準本身作為參考,而不是依賴庫標籤。