一句話
Base64 是用 64 個安全字元(A–Z、a–z、0–9、+、/)寫出任意位元組序列的一種方法。它的存在是為了讓二進位制資料——圖片、PDF、金鑰——能穿過只處理文字的系統:郵件正文、JSON 字串、URL、HTML 屬性。它是編碼,不是加密:沒有金鑰,解碼就是一個函式呼叫,每種語言、每個瀏覽器的開發者控制檯裡都有。“用 Base64 存”的密碼,就是多繞了一步的明文密碼。
它為什麼存在
電子郵件是 1970 年代為 7 位 ASCII 設計的。附件出現(MIME,1992 年)之後,一張 JPEG 的位元組——用到全部 256 個值,其中一些在郵件伺服器眼裡是“行結束”或“訊息結束”——必須偽裝成無害的文字。Base64 就是答案:取三個位元組(24 位),拆成四組 6 位,每組對映到 64 個字元之一。每 3 位元組變 4 字元,這就是著名的 33% 開銷:3 MB 的附件是 4 MB 的郵件,也是為什麼“25 MB”的郵件上限只裝得下 18 MB 的檔案。
凡是隻有文字這一條通道的地方都用同一個把戲:把小圖直接嵌進網頁(data:image/png;base64,…)、把二進位制令牌放進 HTTP 頭(Authorization: Basic …)、在 PEM 檔案裡裝憑證和金鑰、把位元組陣列塞進 JSON 欄位。
讓人栽跟頭的幾種變體
- 標準版 vs URL 安全版。 標準 Base64 用
+和/,兩個在 URL 裡都有含義。URL 安全字母表(RFC 4648 §5)換成-和_。一方產生、按另一方解碼的令牌,會在這兩個字元上失敗。JWT 用的是 URL 安全版。 - 填充。 輸出長度永遠是 4 的倍數,用
=補齊。有些生成方把填充去掉了(又是 JWT);有些解碼器堅持要它。一個看起來合法的字串解不開,試著補=到長度能被 4 整除。 - 換行。 MIME 每 76 個字元換行,PEM 每 64 個。解碼器一般忽略空白,但不是全部。
- 底下的文字編碼。 Base64 編碼的是位元組。要把一個字串 Base64,得先把它變成位元組,而選 UTF-8、UTF-16 還是 Latin-1 會改變結果。JavaScript 裡那個經典 bug——
btoa("é")拋錯——正是這個:btoa只接受 Latin-1。先編成 UTF-8 位元組。
Base64 工具處理兩種字母表、填充、換行和 UTF-8,解碼結果可以按文字顯示也可以下載成檔案,所以一個 data: URL 或一個郵件附件能在瀏覽器裡還原成原件。
編碼、雜湊、加密
這三個詞在隨意的寫作裡被混用,意思完全不同。
| 可逆? | 要金鑰? | 用途 | |
|---|---|---|---|
| 編碼(Base64、hex、URL 編碼) | 可逆,任何人都行 | 不要 | 讓資料適配通道 |
| 雜湊(SHA-256、MD5) | 不可逆 | 不要 | 給資料取指紋;驗證它沒被改過 |
| 加密(AES、RSA) | 可逆,需要金鑰 | 要 | 讓沒有金鑰的人看不到資料 |
檔案的雜湊讓你核對下載的東西和釋出者放上去的是否一致——雜湊計算在瀏覽器裡算 MD5、SHA-1 和 SHA-256/384/512,並與貼上的校驗值比對。它不隱藏任何東西:檔案還是那個檔案。三者中只有加密提供保密性,而它的強度取決於金鑰——這就是真正隨機的密碼派上用場的地方。
如果你正打算把秘密 Base64 一下
別。具體說:
- 配置檔案裡的 Base64 密碼(老式 Java 和 .NET 應用的常見做法),對任何開啟檔案的人來說就是明文密碼。
- 前端 JavaScript 裡“混淆”過的 API 金鑰,在網路面板裡一覽無餘,Base64 與否沒區別。
- 明文 HTTP 上的 Basic 認證,使用者名稱和密碼是 Base64 編碼傳送的,也就是等於明文傳送。只有在 HTTPS 裡它才可接受,因為加密由傳輸層完成。
資料必須儲存或傳送並且保密,就用真正的密碼演演算法加密(平臺加密庫裡的 AES-256-GCM);然後,如果它必須穿過文字通道,再把密文 Base64。加密之後編碼沒問題。用編碼代替加密才是錯誤。