Base64 不是加密:它是幹什麼用的,真要保密該用什麼

Base64 把位元組變成文字,好讓它們在只認文字的系統裡活下來。任何人一行程式碼就能還原。它該出現在哪(郵件、JSON、data URL)、它多花的那 33%,以及編碼、雜湊、加密三者的區別。

一句話

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。加密之後編碼沒問題。用編碼代替加密才是錯誤。

本文用到的工具