[指南] 9 min read

[指南] 什麼是 Base64?它是如何運作的?

從第一原理出發的實用解析。瞭解 Base64 背後的 6-bit 分組技巧、為何輸出會大 33%、= 填充字元從何而來,以及何時該使用它。

April 2026 | fundamentals

// 一句話定義

Base64 是一種把二進位資料以文字形式呈現的方式,只使用 64 個可列印的 ASCII 字元:A–Z、a–z、0–9,以及兩個符號(通常是 + 與 /,另外以 = 作為填充標記)。這就是它的全部概念。每三個位元組的二進位資料會被重新分組成四個 Base64 字元,除此之外什麼都沒變。

它是一種編碼(encoding),而不是加密(encryption)——任何人都可以在幾毫秒內把它還原。它的目的不是隱私,而是安全地傳輸。Base64 讓你能把二進位內容(圖片、憑證、PDF 檔、密碼學金鑰)塞進那些只接受文字的場所:HTML 屬性、JSON 字串、URL、電子郵件內容、YAML 設定檔、資料庫 TEXT 欄位、環境變數。

// 為什麼我們需要它

純文字通道會刪除或扭曲它們不認得的位元組。只理解 7-bit ASCII 的電子郵件閘道會破壞每個非英文字元的最高位元。JSON 剖析器會拒絕內嵌的 null 位元組。URL 中若含有字面上的空白、引號或「與」符號(&),除非先經過百分比編碼,否則都是無效的。1990 年代的 SMTP 標準假設郵件內容永遠都是純英文文字——但人們後來想寄出照片與試算表。

Base64 解決這個問題的方法,是把輸出限制在 64 個能安全通過所有已知文字通道的字元:26 個大寫字母、26 個小寫字母、10 個數字,以及兩個 (a) 可列印、(b) 不被常見跳脫機制使用、(c) 能在 ASCII 中往返而不失真的符號。再加上一個填充字元 =,同樣安全。

// 6-BIT 分組技巧

Base64 的運作核心可以用一句話講完:把輸入視為位元流,切成每 6 個位元一組,再把每一組拿去查一個 64 字元的字母表。

為什麼是 6 個位元?因為 2^6 = 64,所以每一個 6-bit 區塊正好對應字母表裡的一個字元。為什麼不是 7 或 8 個位元?7 個位元會有 128 個值(太多——並非每個都可列印),而 8 個位元又只是原始二進位。6 個位元是所有可能數值都能對應到可讀字元的最佳甜蜜點。

三個輸入位元組 = 24 個位元 = 正好四組 6-bit = 四個 Base64 字元。這個 3 進 4 出的比例是固定的;這也是 Base64 輸出為何大約是輸入的 4/3 倍,也就是約 33% 大的原因。

// Worked example: encoding the 3 bytes 'Man' (77 97 110)
// ASCII:  M        a        n
// Binary: 01001101 01100001 01101110
// Re-group into 6-bit chunks:
//         010011 010110 000101 101110
// Decimal: 19     22     5      46
// Base64:  T      W      F      u
// Result:  'TWFu'  (stored as 4 ASCII bytes: 84 87 70 117)

// BASE64 字母表

RFC 4648 §4 定義了標準字母表。位置 0–25 是 A–Z,26–51 是 a–z,52–61 是 0–9,位置 62 是 +,位置 63 是 /。

URL 安全版本(RFC 4648 §5,又稱為 base64url)把 + 換成 -、把 / 換成 _,讓輸出可以安全地放進 URL 與檔名而不需要再次跳脫。只要使用對應的字母表,兩種版本都能相同地解碼。

// Standard Base64 alphabet (index → character)
// 0–25:  A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
// 26–51: a b c d e f g h i j k l m n o p q r s t u v w x y z
// 52–61: 0 1 2 3 4 5 6 7 8 9
// 62:    +       (or  -  in URL-safe)
// 63:    /       (or  _  in URL-safe)
// pad:   =

// = 填充字元從何而來

演算法期望輸入大小是 3 位元組的倍數(這樣才能剛好分成 4 個 Base64 字元)。當輸入長度不是 3 的倍數時,會剩下一個或兩個位元組,不足以填滿最後 4 個字元的輸出組。用 = 填充就是在指出少了多少個位元組。

• 輸入長度 mod 3 == 0 → 不需填充(例如 'Man' → 'TWFu')。
• 輸入長度 mod 3 == 1 → 兩個填充字元(例如 'M' → 'TQ==')。
• 輸入長度 mod 3 == 2 → 一個填充字元(例如 'Ma' → 'TWE=')。

// Why 'M' becomes 'TQ==':
// Byte: M = 01001101                   (8 bits)
// Padded to 12 bits with zeros: 010011 010000
// Decimal: 19, 16  →  'T', 'Q'
// Output length must be multiple of 4 → add '==' padding
// Result: 'TQ=='
//
// When decoding, the decoder strips '==' and recovers the first 8 bits,
// discarding the trailing zero bits.

// 33% 的額外開銷是精確值嗎?

接近,但不完全是。精確公式是:長度為 n 的位元組輸入,輸出為 ceil(n / 3) × 4 個字元。以 100 KB 的輸入來說,結果是 ceil(102400 / 3) × 4 = 136,534 個字元——比 100 KB 多出 33.3%。實務上,一旦加上 HTTP 層級的 gzip 或 Brotli 壓縮,傳輸線上的成本會比較接近 10–15%,因為 Base64 文字壓縮效果尚可(雖然比不上原始二進位)。

對小型資料而言,填充的影響很顯著:編碼 1 個位元組要花掉 4 個字元(四倍大),編碼 2 個位元組也是 4 個字元。所以很小的 Base64 字串看起來會比它們的輸入長得驚人。

// BASE64 在真實世界的使用場景

  • > HTML/CSS 中的 Data URI —— 以行內方式嵌入圖示與標誌:<img src="data:image/png;base64,…">
  • > JSON 與 GraphQL 內容 —— 需要在文字協定中夾帶二進位(檔案上傳、縮圖)的 API
  • > JWT 權杖 —— JWT 中以點號分隔的三個部分都是 base64url 字串
  • > HTTP Basic Auth —— Authorization 標頭中的 username:password 採用 Base64 編碼(在沒有 TLS 的情況下仍然不安全!)
  • > 電子郵件(MIME) —— 附件以 Base64 編碼以便通過傳統 7-bit SMTP 伺服器
  • > PEM 格式的憑證與金鑰 —— -----BEGIN CERTIFICATE----- 區塊包裹的是 Base64 編碼過的 DER blob
  • > SSH 金鑰 —— id_rsa.pub 與 authorized_keys 的每一行都是 Base64 編碼的公開金鑰
  • > 資料庫 TEXT 欄位 —— 當你不能使用 BLOB 型別時,Base64 讓你以文字形式儲存二進位
  • > 環境變數 —— Kubernetes Secrets 的值會被 Base64 編碼(但並未加密)
  • > QR code 與魔法連結 —— 可以安全嵌入 URL 的短 Base64 權杖

// BASE64 不是加密

這是最常見的誤解。Base64 不會隱藏你資料的內容——它只是用一個公開、有完整文件的對應表把二進位改寫成文字。把密碼以 Base64 編碼成 cGFzc3dvcmQ=,其脆弱程度和明文 password 完全一樣;任何人都能在一次函式呼叫內解回來。把 Base64 當成十六進位對待:它是一種表示法,不是保護。

如果你需要隱私,請先對明文套用真正的密碼學原語(AES-GCM、ChaCha20-Poly1305、age、PGP),之後若需要透過文字通道傳輸密文,再把它 Base64 編碼。Base64 這一步是傳輸的最後一哩路,不是安全層。

Kubernetes Secrets 是典型的陷阱:Kubernetes 會把密鑰值以 Base64 儲存,看起來似乎有保護作用。其實並沒有——任何對該命名空間有讀取權限的人都能把它還原。真正的密鑰保護需要仰賴 SealedSecrets、Vault、SOPS 或雲端原生 KMS 整合等工具。

// 每種語言的快速編解碼

// JavaScript (browser):
//   btoa('Hello')            → 'SGVsbG8='
//   atob('SGVsbG8=')         → 'Hello'
//   (btoa/atob only handle Latin-1; use TextEncoder for Unicode)
//
// Node.js:
//   Buffer.from('Hello').toString('base64')     → 'SGVsbG8='
//   Buffer.from('SGVsbG8=', 'base64').toString() → 'Hello'
//
// Python:
//   import base64
//   base64.b64encode(b'Hello')       → b'SGVsbG8='
//   base64.b64decode('SGVsbG8=')     → b'Hello'
//
// Go:
//   base64.StdEncoding.EncodeToString([]byte("Hello")) → "SGVsbG8="
//
// Ruby:
//   Base64.strict_encode64('Hello')   → 'SGVsbG8='
//
// Shell:
//   echo -n 'Hello' | base64          → 'SGVsbG8='
//   echo 'SGVsbG8=' | base64 -d        → 'Hello'

// 應避免的常見錯誤

  • > 用標準字母表編碼 URL 安全字串 —— 接收端的解碼器會拒絕 +/。輸出如果要進入 URL、cookie 或檔名,請使用 base64url。
  • > 重複編碼 —— 把已經是 Base64 的文字再丟進編碼器。編碼前永遠先確認資料是否已是 Base64。
  • > 忘記 UTF-8 —— 在瀏覽器中 btoa('héllo') 會丟例外,因為 é 超出 Latin-1。請先用 TextEncoder 把字串轉成位元組。
  • > 填充剝除不一致 —— base64url 常會省略 = 填充。如果你的解碼器很嚴格,請重新加回填充:input + '==='.slice((input.length + 3) % 4)
  • > 以為 Base64 是安全的 —— 它不是。加密是另一回事。永遠要分開看待。
  • > 對超大二進位使用 Base64 —— 把一個 500 MB 的檔案編成單一字串會把記憶體吃光。改用串流處理。
  • > 折行與不折行的差異 —— MIME/PEM 會在第 64 或第 76 欄以 \n 折行。大多數其他情境則預期一整行。視情況剝除或插入換行符。

// 30 秒速查表——BASE64

  • > 64 個可列印 ASCII 字元(A–Z、a–z、0–9、+、/)加上 = 填充
  • > 3 個位元組進 → 4 個字元出(固定比例)
  • > 輸出約比輸入大 33%
  • > 可逆:這是編碼而非加密
  • > URL 與檔名請使用 base64url(把 + → -、/ → _)
  • > 短輸入要用 1 或 2 個 = 字元填充,讓輸出長度為 4 的倍數
  • > Unicode 文字:先把字串編成 UTF-8 位元組,再對這些位元組做 Base64
  • > 每種主流語言的標準函式庫都可解碼
  • > 在 HTML、JSON、URL、電子郵件與 PEM 檔案中都安全
  • > 不能當成安全層使用——敏感資料一律要搭配真正的加密

// 下一步

既然你已瞭解機制,來試試我們的 Base64 編碼器Base64 解碼器,逐字元檢視輸出。URL 安全的內容請切換到 base64url。要處理圖片可參考 圖片 → Base64

相關深度文章:
Base64 URL 安全版與標準版 —— 何時使用哪一種字母表
Base64 與 UTF-8、Unicode —— 避開 btoa() 的陷阱
JavaScript 與 Node.js 中的 Base64 —— atob、btoa、Buffer
Base64 vs Base64URL vs Base32 —— 比較表