[指南] 什麼是 Base64?它是如何運作的?
從第一原理出發的實用解析。瞭解 Base64 背後的 6-bit 分組技巧、為何輸出會大 33%、= 填充字元從何而來,以及何時該使用它。
// 一句話定義
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 —— 比較表