Base64 編碼完整指南 2026:原理、實作、安全注意事項

寫給:每天遇到 iVBORw0KGgo... 開頭字串卻不知所云的工程師、PM、資安人員,以及想搞懂為什麼 email 附件變這麼大的所有開發者。

Base64 是 1990 年代為了在純文字協定(SMTP、HTTP header、URL)裡安全傳輸二進位資料而生的編碼方案。雖然它不是加密、也不是壓縮,但幾乎所有現代 Web 系統都離不開它:JWT、Data URL、Email 附件、Webhook 簽名、TLS 證書全部都用 Base64。本文 13 分鐘帶你看懂 Base64 的設計動機、實作細節、6 種變體與最常見的踩坑,並用 SuperTools 線上工具一鍵編解碼。

1. Base64 是什麼

Base64 是一種二進位轉文字的編碼方案。它把任意 byte 序列轉換成只用 64 個可印字元(A-Z、a-z、0-9、+、/)+ 填充符 = 表示的字串。

為什麼需要這層轉換?因為許多文字協定對某些 byte 有特殊意義:

  • SMTP 把 0x0A(\n)視為訊息分隔符
  • HTTP header 不能含 \r\n
  • URL 不能含 ?、&、空白

如果你想透過這些協定傳送圖片、PDF、簽章等二進位資料,就必須先「文字化」。Base64 是 RFC 4648 標準,幾乎所有平台都內建。

核心數學:

  • 3 個 byte(24 bits)→ 4 個 Base64 字元(6 bits × 4)
  • 體積膨脹比例:4/3 ≈ 1.333 倍(再加上 = padding 後接近 33% 膨脹)

舉例:

原始 bytes: 0x4D 0x61 0x6E       (3 bytes = "Man")
二進位:     01001101 01100001 01101110
重新分組:   010011 010110 000101 101110
查表:       19     22     5      46
Base64:     T      W      F      u
最終輸出:   "TWFu"

如果 byte 數不是 3 的倍數,最後用 = 補齊到 4 的倍數:

原始 byte 數 Base64 字元數 Padding
3 4 0
2 4 1 個 =
1 4 2 個 =

2. 5 個核心觀念

2.1 Base64 不是加密

最常見的迷思。Base64 沒有 key、可以被任何人 reverse,純粹是編碼而非加密。如果你看到「我把密碼 base64 過了所以安全」,那是嚴重的資安認知錯誤。

import base64
# 看似亂碼,其實任何人都能解
print(base64.b64decode("cGFzc3dvcmQxMjM=").decode())
# password123

要安全儲存密碼請用 bcrypt / argon2,要傳輸機密請用 HTTPS + 加密。

2.2 URL Safe Base64(RFC 4648 §5)

標準 Base64 用 + 和 /,這兩個字元在 URL 裡有特殊意義(+ 代表空白、/ 代表路徑分隔)。所以 RFC 4648 §5 定義 URL-safe 變體:

用途 字元集 Padding
標準 Base64 A-Za-z0-9+/ 用 =
URL Safe Base64 A-Za-z0-9-_ 可省略 =

JWT 就是用 URL Safe Base64(且省略 padding)。

import base64
data = b"hello?world"
print(base64.b64encode(data).decode())       # aGVsbG8/d29ybGQ=
print(base64.urlsafe_b64encode(data).decode()) # aGVsbG8_d29ybGQ=

2.3 padding = 的真相

= 不在 64 字元集裡,純粹是 padding 標記。許多協定(JWT、URL 參數)會省略 padding,因為從字串長度就能反推原始 byte 數:

def add_padding(s):
    return s + "=" * (-len(s) % 4)

token = "eyJhbGciOiJIUzI1NiJ9"  # 沒 padding
padded = add_padding(token)
# 補成 "eyJhbGciOiJIUzI1NiJ9=="

幾乎所有 Base64 解碼器都接受帶或不帶 padding(嚴格實作除外),但自己 encode 時請保留 padding 以提升相容性。

2.4 Data URL

data: URL(RFC 2397)讓你把圖片、字型等資源直接嵌進 HTML/CSS,省一個 HTTP request:

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA..." />

格式:

data:[<mediatype>][;base64],<data>

優點:減少 request 數量。 缺點:體積膨脹 33%,且不會被瀏覽器快取(每個頁面要重新下載)。

實務建議:

  • 小於 4KB 的圖示用 Data URL
  • 大於此值用一般 <img> 走快取

2.5 MIME Base64(每 76 字元換行)

Email(MIME)規定 Base64 每 76 字元要插入一個換行符(RFC 2045):

import base64
data = b"x" * 200
print(base64.encodebytes(data).decode())
# 每 76 字元一個 \n,最後也有一個 \n

print(base64.b64encode(data).decode())
# 純粹一行,不換行

encodebytes 適用於 Email;b64encode 適用於 API / JSON。混用會導致解碼錯誤。

3. 實戰範例

範例 1:把圖片變成 Data URL

import base64

with open("logo.png", "rb") as f:
    encoded = base64.b64encode(f.read()).decode()

data_url = f"data:image/png;base64,{encoded}"
print(f'<img src="{data_url}" />')

把輸出貼進 HTML 即可直接顯示,不需要 host 圖片伺服器。

範例 2:解碼 JWT payload

import base64, json

token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0IiwibmFtZSI6IkFsaWNlIn0.signature"
header_b64, payload_b64, sig = token.split(".")

def b64url_decode(s):
    s += "=" * (-len(s) % 4)
    return base64.urlsafe_b64decode(s)

header = json.loads(b64url_decode(header_b64))
payload = json.loads(b64url_decode(payload_b64))
print("Header:", header)   # {'alg': 'HS256'}
print("Payload:", payload) # {'sub': '1234', 'name': 'Alice'}

範例 3:HTTP Basic Auth header

import base64
user, password = "admin", "s3cret"
credentials = base64.b64encode(f"{user}:{password}".encode()).decode()
print(f"Authorization: Basic {credentials}")
# Authorization: Basic YWRtaW46czNjcmV0

範例 4:bash 命令列

# 編碼
echo -n "hello" | base64
# aGVsbG8=

# 解碼
echo "aGVsbG8=" | base64 -d
# hello

# 編碼檔案
base64 < image.png > image.b64

# 解碼回二進位
base64 -d < image.b64 > image_restored.png

-n 很重要,否則 echo 會多送一個換行 byte 導致編碼結果不同。

範例 5:JavaScript 在瀏覽器與 Node

// 瀏覽器
const b64 = btoa("hello");          // 編碼 ASCII
const text = atob("aGVsbG8=");      // 解碼

// 含 Unicode 必須先 encode
const b64utf8 = btoa(unescape(encodeURIComponent("中文")));

// Node.js
const buf = Buffer.from("hello", "utf-8");
console.log(buf.toString("base64"));               // aGVsbG8=
console.log(Buffer.from("aGVsbG8=", "base64").toString("utf-8")); // hello

btoa / atob 名稱來自 "binary to ASCII",但只支援 Latin-1,遇到 UTF-8 必須先 escape。

4. 進階技巧

4.1 串流處理大檔案

把 1GB 影片整個讀進記憶體後 encode 會 OOM。用 chunk 處理:

import base64

CHUNK = 3 * 1024 * 1024  # 必須是 3 的倍數,否則跨 chunk 會出 padding 怪事
with open("big.mp4", "rb") as fin, open("big.b64", "w") as fout:
    while chunk := fin.read(CHUNK):
        fout.write(base64.b64encode(chunk).decode())

注意 chunk size 必須是 3 的倍數。

4.2 Base32 / Base58 / Base85

Base64 不是唯一選擇:

編碼 字元數 特色
Base32 32 大小寫不敏感(適合語音念出來)
Base58 58 去掉 0OIl 等易混淆字元(比特幣地址用)
Base85 85 比 Base64 多 7% 效率(PDF、git 用)

選擇看場景:人類要念的用 Base32,二進位緊湊用 Base85,網路傳輸用 Base64。

4.3 Constant-time 比較防 timing attack

驗證 Base64 簽章時不能用 ==,否則早期 byte 不同會早 return,可被時間側通道攻擊:

import hmac, base64

received_sig = base64.b64decode("...")
computed_sig = hmac.new(key, payload, "sha256").digest()
if not hmac.compare_digest(received_sig, computed_sig):
    raise PermissionError("Invalid signature")

hmac.compare_digest 是 constant time。

4.4 串接 gzip 進一步壓縮

純文字資料 base64 後會膨脹 33%,但前面先 gzip 可大幅抵消:

import gzip, base64
data = b"x" * 10000  # 高度重複資料
encoded = base64.b64encode(gzip.compress(data))
print(len(data), len(encoded))  # 10000 → 約 75

API 傳大文本時可考慮這個 pattern:先 gzip → 再 base64 → 放進 JSON。

4.5 Web Crypto API(瀏覽器原生)

不想引外部 lib:

async function sha256B64(text) {
  const buf = new TextEncoder().encode(text);
  const hash = await crypto.subtle.digest("SHA-256", buf);
  const bytes = new Uint8Array(hash);
  return btoa(String.fromCharCode(...bytes));
}
sha256B64("hello").then(console.log);
// LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=

5. 常見錯誤與解法

錯誤 1:URL 中傳遞 Base64 結果亂碼

症狀:把 base64 字串放 query string,server 收到變成空白或截斷。 根因:+ 在 URL 中代表空白。 解法:改用 URL safe base64(-_ 取代 +/),或對整個字串 encodeURIComponent。

錯誤 2:JWT 解碼失敗 Invalid base64

症狀:明明從 server 收到正常 JWT,本地 decode 卻說格式錯。 根因:JWT 使用無 padding 的 URL safe base64,標準 decoder 不接受。 解法:手動補 =,並用 urlsafe_b64decode。

def b64url_decode(s):
    s += "=" * (-len(s) % 4)
    return base64.urlsafe_b64decode(s)

錯誤 3:中文編碼變亂碼

症狀:JS 用 btoa("中文") 噴錯 InvalidCharacterError。 根因:btoa 只支援 Latin-1。 解法:先用 TextEncoder 轉 UTF-8 byte array 再 base64。

function b64EncodeUnicode(str) {
  const utf8 = new TextEncoder().encode(str);
  return btoa(String.fromCharCode(...utf8));
}

錯誤 4:檔案傳輸後 hash 不一致

症狀:base64 編碼上傳、server 解碼後 sha256 對不上。 根因:CRLF / LF 行尾差異,或 chunk size 不是 3 的倍數導致額外 padding。 解法:用 binary mode 讀寫("rb" / "wb"),chunk size 設 3 的倍數。

錯誤 5:Email 附件解碼後變垃圾

症狀:parse multipart/mixed 附件 base64 失敗。 根因:MIME base64 含換行符,但用 b64decode 嚴格模式會抱怨。 解法:先去除所有 whitespace 再 decode;或用 email.message 套件處理。

import re, base64
clean = re.sub(r"\s+", "", raw_base64)
data = base64.b64decode(clean)

6. 用 SuperTools 線上版完成相同任務

不想寫程式直接 encode / decode?打開 SuperTools Base64 工具:

  • 文字 ↔ Base64:左右雙欄即時雙向轉換
  • 檔案上傳 → Base64:把圖片、PDF 一鍵變字串(含 Data URL prefix)
  • URL Safe 模式切換:右上角 toggle
  • 檔案下載:把 Base64 字串還原為二進位檔
  • 批次處理:一次貼多行,每行獨立 decode

工具完全 client-side,檔案不會上傳伺服器。即使你貼公司內部密鑰也只在你瀏覽器處理。

實際使用流程:

  1. 從 API response 複製可疑的 base64 字串
  2. 開 supertools.tw/encoding/base64 貼上左側
  3. 按「Decode」右側看明文(中文、JSON 自動 detect)
  4. 若失敗會 highlight padding / 字元集問題

立即使用 SuperTools Base64 工具:https://supertools.tw/encoding/base64

7. 其他語言實作

Python

import base64
base64.b64encode(b"hello").decode()      # 'aGVsbG8='
base64.b64decode("aGVsbG8=")              # b'hello'
base64.urlsafe_b64encode(b"hello").decode()

JavaScript (Node 16+)

Buffer.from("hello").toString("base64");   // 'aGVsbG8='
Buffer.from("aGVsbG8=", "base64").toString();

Go

import "encoding/base64"
base64.StdEncoding.EncodeToString([]byte("hello"))
base64.URLEncoding.EncodeToString([]byte("hello"))
base64.RawURLEncoding.EncodeToString([]byte("hello"))  // 無 padding

Rust

use base64::{engine::general_purpose, Engine};
let s = general_purpose::STANDARD.encode(b"hello");
let bytes = general_purpose::STANDARD.decode(s).unwrap();

Bash

echo -n "hello" | base64        # aGVsbG8=
echo "aGVsbG8=" | base64 -d     # hello

8. FAQ

Q1:Base64 跟 Base64URL 哪個用比較好? HTTP body / JSON 用標準 Base64;URL / JWT / cookie 用 URL safe Base64。

Q2:Base64 編碼會變大多少? 固定 4/3 倍,再加 padding 後約 33% 膨脹。

Q3:要不要再壓縮? 若原資料已壓縮(圖片、影片)則不需要;若是純文字(JSON、log)可先 gzip 再 base64 大幅省體積。

Q4:Base64 能解 URL escape 的內容嗎? 不能直接解。請先 decodeURIComponent 還原成原始 base64 字串再 decode。

Q5:為什麼有些字串 base64 過後特別長? 原始檔案大就會大。Base64 是固定 4/3 倍膨脹,沒有「壓縮」效果。

9. 實戰案例研究

案例 1:Email PDF 附件解碼失敗

某報表系統寄 PDF 附件給客戶,部分客戶反映附件打不開。debug:

  1. 用 SMTP 寄信,PDF 用 base64.b64encode 編碼
  2. 編碼結果不換行,整段是一行 50000 字元
  3. 部分 mail server(如 Microsoft Exchange 舊版)對單行限 998 字元,超過自動截斷
  4. 截斷後 base64 解碼缺 byte,PDF 損毀

修法:MIME 規範規定每 76 字元換行:

import base64
encoded = base64.encodebytes(pdf_bytes).decode()
# 自動每 76 字元加 \n

教訓:寄信用 encodebytes 不是 b64encode。

案例 2:JWT 簽章 padding 引發 401

某微服務群用 JWT 跨服務通訊。某天升級某 service 的 JWT lib 後,全部 401 拒絕。

debug:

  1. 新 lib 預設用 RawURLEncoding(無 padding)
  2. 舊服務 verify 時嚴格要求 padding
  3. token 變短,base64url decode 失敗

修法:

  1. 統一所有 service 用 RawURLEncoding(符合 JWT spec)
  2. verify 端先 add_padding 補 = 再 decode
  3. 加 contract test 防將來 lib 升級再噴

教訓:跨服務 token 處理要明確 spec 是否 padding。

案例 3:Data URL 拖累首頁 Lighthouse 分數

某 SaaS 首頁原本用 100+ 個 SVG icon inline 為 Data URL,CSS 檔案膨脹到 1.2MB。Lighthouse 評分:

  • FCP: 3.8s
  • LCP: 5.2s
  • Total Blocking Time: 1800ms

修法:

  1. 拆出 sprite SVG 走 HTTP 快取
  2. Critical inline 只留 5 個 above-the-fold icon
  3. 其餘 lazy load

改善後:

  • FCP: 1.2s
  • LCP: 1.8s
  • TBT: 150ms

教訓:Data URL 是雙面刃——小資源 inline 省 request,大資源 inline 拖效能。

10. 結論 + 延伸閱讀

Base64 是 Web 開發的隱形基石——你看不到它,但每個 JWT、圖片 inline、Email 附件背後都是它在工作。掌握本文 5 個核心觀念後,你已能:辨識任何 base64 字串、選擇正確變體、避開精度與 padding 陷阱。下次面對 data:image/png;base64,... 時,不再只是黑魔法。三條原則:Email 用 encodebytes 含換行、JWT / URL 用 urlsafe_b64encode、小資源 inline 大資源走 HTTP。

延伸閱讀: