「我把它 Base64 編碼一下,這樣就沒人能讀了。」這句話出現在程式碼審查、Slack 訊息以及 Stack Overflow 答案中的頻率,比它應該的高得多。Base64 不是加密。它不是混淆。它不是任何形式的安全機制。把它解碼回來只需要一次函式呼叫,且不需要任何金鑰或秘密。本文講清楚 Base64 到底是什麼、誤解為何根深柢固,以及當你真的需要保護資料時該用什麼。
Base64 實際是什麼
Base64 是一種編碼方案 —— 二進位資料與 64 個可列印 ASCII 字元(A–Z、a–z、0–9、+、/,以及 = 作為填補)之間的可逆對映。
每 3 位元組的輸入會變成 4 個 Base64 字元。演算法完全是決定性的,不需要任何金鑰:
// 編碼
btoa("hello") // → "aGVsbG8="
btoa("secret password") // → "c2VjcmV0IHBhc3N3b3Jk"
// 解碼 —— 一次呼叫,不需要金鑰
atob("c2VjcmV0IHBhc3N3b3Jk") // → "secret password"就這樣。拿到編碼字串的任何人都能立刻解碼,在任何語言、任何環境中,不需要任何額外資訊。
誤解為何存在
Base64 編碼後的字串看起來像加密後的 資料。它們由大小寫字母、數字和特殊字元混合而成,與原始輸入毫無可見相似之處。這種視覺雜訊足以矇騙一個非技術觀察者 —— 而出乎意料的是,常常也能矇騙技術人員。
這個模式還會被「位置鄰近」所強化。Base64 在安全相關的場合中到處都是:
- HTTPS 憑證是 Base64 編碼的(PEM 格式)
- JWT token 的 header 與 payload 使用 Base64url 編碼
- HTTP Basic 認證以 Base64 傳送憑證
- SSH 金鑰以 Base64 儲存
- 加密後的密文經常以 Base64 作為傳輸形式
在這些與安全相關的位置反覆見到 Base64,開發者就把編碼與安全混為一談。編碼只是外包裝;安全來自底層的密碼學操作。
編碼 vs 加密:核心差別
差別簡單且絕對:
| 編碼(Base64) | 加密(AES、RSA…) | |
|---|---|---|
| 目的 | 把二進位資料表示為文字 | 對未授權方隱藏資料 |
| 需要金鑰? | 否 | 是 |
| 任何人都能反向? | 能 —— 一次函式呼叫 | 只有掌握正確金鑰才能 |
| 提供機密性? | 否 | 是(正確使用時) |
| 輸出大小 | 比輸入大約多 33% | 因演算法而異 |
加密用金鑰變換資料,使得在沒有金鑰的情況下還原原文在計算上不可行。Base64 編碼沒有這樣的屬性。
現實世界的安全錯誤
把「編碼過的」秘密放在原始碼裡
// ❌ 這並不秘密 —— 任何人都能解碼
const API_KEY = atob("c2stbGl2ZS1hYmMxMjM0NTY3ODk=");
// 任何讀到這段程式碼的人都會跑:
atob("c2stbGl2ZS1hYmMxMjM0NTY3ODk=") // → "sk-live-abc123456789"自動化的金鑰掃描工具(GitHub 的 secret scanning、GitGuardian、truffleHog)在偵測時都會解碼 Base64 字串。如果你指望 Base64 能擋住它們,辦不到。
HTTP Basic 認證
// Authorization 標頭看起來像這樣:
Authorization: Basic dXNlcjpwYXNzd29yZA==
// 這段 Base64 一解就明:
atob("dXNlcjpwYXNzd29yZA==") // → "user:password"HTTP Basic 認證只有在 HTTPS 上才安全 —— 真正的加密由 TLS 層提供。標頭中的 Base64 純粹是為了把 username:password 編碼為單一文字值,並不是為了隱藏。在明文 HTTP 上,任何攔截流量的人都能立刻讀到憑證。
把 JWT「token」當作秘密看待
// 一個 JWT 看起來是用點號連接的三段 Base64url:
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjQyfQ.abc123
// 中間一段(payload)解碼後是:
atob("eyJ1c2VySWQiOjQyfQ") // → '{"userId":42}'JWT payload 沒有被加密 —— 它們是 Base64url 編碼並被簽章的。任何拿到 token 的人都能讀 payload。簽章能防篡改,但不提供機密性。永遠不要把敏感資料(密碼、信用卡號、個人資訊)放進 JWT payload,除非你在用 JWE(JSON Web Encryption)—— 那是另一個、相當少見的標準。
在 URL 中混淆資料
// 舊應用中常見的模式
/profile?data=eyJ1c2VySWQiOjQyfQ==
// 貼到任何解碼器裡:
atob("eyJ1c2VySWQiOjQyfQ==") // → '{"userId":42}' URL 中的 Base64 沒有安全增益。它還很容易出錯 —— 標準 Base64 中的 + 與 / 不是 URL 安全的,必須做百分號編碼,因此才有 Base64url 這個變體(+ → -、/ → _,不填補)。
Base64 真正的用途
Base64 有合理且重要的用途 —— 只是不用來做安全:
- 把二進位資料嵌入文字格式中 —— 把圖片、字型、檔案作為
data:URI 嵌入 HTML/CSS,或附在 JSON API 回應中 - 電子郵件附件 —— MIME 編碼以 Base64 在僅支援 7 位元 ASCII 的協定上傳輸二進位檔案
- 傳輸安全 —— 某些通道會破壞二進位資料(空位元組、控制字元);Base64 保證內容完整通過
- 不透明識別符 —— 把 ID 或游標編碼為 Base64,向 API 使用者傳達「別試著解析它」,儘管它並不提供任何安全性
真正需要安全時該用什麼
如果你的目標是防止未授權存取資料,請使用真正的密碼學工具:
| 目標 | 用什麼 |
|---|---|
| 對靜態資料加密 | AES-256-GCM(對稱加密) |
| 對傳輸中資料加密 | TLS 1.3(HTTPS) |
| 雜湊密碼 | bcrypt、scrypt 或 Argon2 —— 永遠不要只用 SHA-256 |
| 簽章 token | HMAC-SHA256(如 JWT 的 HS256)或 RS256 |
| 存放秘密 | 環境變數、秘密管理器(Vault、AWS Secrets Manager) |
| 端對端加密 token | JWE(JSON Web Encryption)—— 而不是普通 JWT |
當你確實需要加密 token 時:JWE 與 JOSE
標準 JWT 是簽章的(JWS),不是加密的 —— payload 可讀。如果你真的需要讓 claims 具有機密性,請改用 JWE(JSON Web Encryption,RFC 7516)。JWE 屬於 JOSE 標準家族:
- JOSE 演算法 —— 內容加密(例如
A256GCM)與金鑰管理(例如RSA-OAEP-256、ECDH-ES、A256KW)。「alg」決定內容加密金鑰如何被包裝;「enc」決定 payload 本身如何被加密。 - 金鑰包裝 —— JWE 為每條訊息產生新鮮的內容加密金鑰,並把它在接收方的金鑰下包裝(非對稱或對稱)。被包裝的金鑰隨 token header 傳送。這樣同一明文可以加密給不同接收方,而不必重新加密本體。
- JWE 緊湊形式 —— 用點號連接的五段 Base64url(header.key.iv.ciphertext.tag),視覺上像 JWT,但是四段而非三段。
請選擇經過審計的函式庫(jose、node-jose、python-jose),不要自己實作 JWE —— 演算法與金鑰格式的組合很微妙,悄無聲息地回退到弱演算法是經典的 JOSE 漏洞。
針對你自己程式碼的快速自查
在你的程式碼庫中搜 btoa(、atob( 與 Buffer.from(…, 'base64')。對每一處問自己:
- 我這樣做是為了讓文字能塞進某個欄位或完整通過傳輸?→ 沒問題。
- 我這樣做是為了對某人隱藏資料?→ 改用真正的加密或存取控制。
常見問題
Base64 是加密嗎?
不是。Base64 是一種沒有金鑰的可逆編碼 —— 任何人一次函式呼叫(atob())就能解碼。加密需要金鑰,沒有金鑰時還原原文是不可行的。
用 Base64 存密碼或 API 金鑰安全嗎?
不安全。解碼極其容易,金鑰掃描器還會自動解碼 Base64。請使用秘密管理器或環境變數,並以 bcrypt、scrypt 或 Argon2 雜湊密碼。
任何人都能讀 JWT payload 嗎?
能。JWT payload 是 Base64url 編碼並被簽章的,不是加密 —— 任何拿到 token 的人都能讀到每一個 claim。除非用 JWE,否則永遠不要把秘密放進 JWT。把 token 貼進線上工具有風險;見 為什麼不要把敏感 JSON 貼到線上工具中