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,檔案不會上傳伺服器。即使你貼公司內部密鑰也只在你瀏覽器處理。
實際使用流程:
- 從 API response 複製可疑的 base64 字串
- 開 supertools.tw/encoding/base64 貼上左側
- 按「Decode」右側看明文(中文、JSON 自動 detect)
- 若失敗會 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:
- 用 SMTP 寄信,PDF 用
base64.b64encode編碼 - 編碼結果不換行,整段是一行 50000 字元
- 部分 mail server(如 Microsoft Exchange 舊版)對單行限 998 字元,超過自動截斷
- 截斷後 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:
- 新 lib 預設用
RawURLEncoding(無 padding) - 舊服務 verify 時嚴格要求 padding
- token 變短,base64url decode 失敗
修法:
- 統一所有 service 用
RawURLEncoding(符合 JWT spec) - verify 端先
add_padding補=再 decode - 加 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
修法:
- 拆出 sprite SVG 走 HTTP 快取
- Critical inline 只留 5 個 above-the-fold icon
- 其餘 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。
延伸閱讀: