AES 的演算法細節是全世界公開的,任何人都能下載規格書,把每一步運算讀得清清楚楚。這就像一座設計圖人人看得到的保險箱,守不守得住東西,完全取決於那把鑰匙有沒有被好好保管。這把鑰匙在加密的世界裡就叫 AES key,它不是登入密碼,而是一串拿來加密、解密的隨機位元組。
對多數網站來說,需要費心的地方通常不在演算法本身,而在金鑰怎麼來、怎麼存。有人自己想一串英文字當金鑰,有人用一般的亂數函式產生,有人把金鑰寫死在程式碼裡再一起推上版本庫,也有人加密模式選得不對。這些做法都不會讓程式報錯,加密照樣跑得動,卻等於沒有上鎖。
AES key 有 128、192、256 位元三種長度,產生方式有對有錯,模式與 nonce 的選擇會左右安全性,保管與輪替又是另一套學問。要把這些判斷做對,得先弄懂 AES 處理資料的幾個固定設計,因為它們直接決定了金鑰該長什麼樣子。
AES 是什麼?
AES(Advanced Encryption Standard,進階加密標準)是美國 NIST 在 FIPS 197 標準中規範的對稱式區塊加密演算法。「對稱」指的是加密與解密用同一把金鑰,所以金鑰本身就是整份祕密的核心,誰拿到它,誰就能讀。「區塊加密」則是指資料會被切成固定大小的區塊來處理,AES 的區塊固定是 128 位元,也就是 16 位元組。
這裡有個很常被搞混的地方。128 位元是區塊大小,不是金鑰長度,AES-128、AES-192、AES-256 都以 128 位元的區塊處理資料,名稱後面的數字才是金鑰的位元長度。NIST 在 2001 年 11 月發布最初的標準,2023 年 5 月發布更新版,並說明這次更新沒有對演算法做技術變更,只是編輯性的改善,所以現在談的 AES,和 20 多年來大家用的是同一套演算法。

AES 本身只是一個把 128 位元區塊轉成另一個 128 位元區塊的函式,加密能不能擋住人,取決於三件事,也就是金鑰是怎麼來的、超過一個區塊的資料用哪種模式處理、每次加密給的 nonce 有沒有重複。這三樣只要錯一樣,演算法再強也補不回來。至於 AES key,白話說就是加解密共用的那串隨機位元組。它長得像一長串亂碼,不是人記得住的密碼,也不該由人想出來。
AES-128、AES-192 與 AES-256 的規格與選用原則
既然區塊大小相同,三個版本的差別就落在金鑰長度,連帶也影響加密的輪數。實務上最容易搞混的是「位元、位元組、字元數」這幾種表示法,所以下表把它們一起列出來:
| 版本 | 金鑰位元 | 金鑰位元組 | 十六進位字元數 | Base64 字元數 | 輪數 |
|---|---|---|---|---|---|
| AES-128 | 128 | 16 | 32 | 24(含等號補位) | 10 |
| AES-192 | 192 | 24 | 48 | 32 | 12 |
| AES-256 | 256 | 32 | 64 | 44(含等號補位) | 14 |
從表格可以看出,金鑰的位元長度和字串長度是兩回事。AES-256 需要 32 位元組,也就是 256 位元的隨機資料;把它寫成十六進位要 64 個字元,寫成 Base64 是 44 個字元。反過來看,有些程式會要求一把「32 個字元的金鑰」,字串長度剛好是 32 位元組,函式庫也會照收,但那串字如果是人想出來的,實際的亂度就遠遠不到 256 位元。這個落差在產生金鑰時最關鍵。

三種長度的強度怎麼比?NIST 的 SP 800-57 把 AES-128、AES-192、AES-256 分別對應到 128、192、256 位元的安全強度,三種都在這張對照表內。換句話說,AES-128 並不是不安全的選擇,只是安全強度比另外兩種低一級。
OWASP 的建議是對稱加密以 AES 為首選,金鑰至少 128 位元,理想是 256 位元。沒有特殊限制的話,直接選 AES-256 最省事,以後不必再為「128 夠不夠」傷腦筋;如果使用的系統只支援 AES-128,或基於相容性只能用 AES-128,那也是標準內的正規選擇,不需要為此焦慮。至於 AES-192,IETF 現行的 TLS 1.3 規範 RFC 9846(2026 年 7 月發布,取代 RFC 8446)所定義的 AES 套件只有 128 與 256 位元兩種,沒有 192 位元。也就是說,至少在 HTTPS 連線這一層,碰到的會是 AES-128 或 AES-256,這可能也是 AES-192 平常比較少被提起的原因之一。
AES key 必須用密碼學安全的亂數產生
金鑰怎麼來,是 AES key 最核心的操作問題。OWASP 的規則很明確,金鑰要用密碼學安全的亂數函式產生,不能基於常見的單字或片語,也不能靠用手亂敲鍵盤得到的字元。人腦想出來的字串、鍵盤敲出來的字串,都帶著看不見的規律,亂度遠低於它看起來的樣子。
另一個常見的錯誤,是直接拿程式語言內建的一般亂數函式來用。這類偽隨機數產生器(PRNG)的輸出可以被預測,OWASP 指出它們不能用在任何與安全相關的用途,像 Python 的 random、Node.js 的 Math.random()、PHP 的 rand() 與 mt_rand() 都在其列。對應的安全選擇是 Python 的 secrets、Node.js 的 crypto.randomBytes()、PHP 的 random_bytes()、Java 的 java.security.SecureRandom,以及 Go 的 crypto/rand。除此之外,金鑰的長度也要跟演算法對應,AES-256 就是 32 位元組,不能多也不能少。
用 Python 的 cryptography 套件產生金鑰
寫程式的人,最省事的做法是用 cryptography 套件提供的 AESGCM.generate_key。它會以密碼學安全的亂數產生金鑰,bit_length 只接受 128、192、256 三個值,傳其他數字會直接丟出 ValueError,不會默默產生一把長度不對的金鑰。
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import base64
key = AESGCM.generate_key(bit_length=256) # 32 位元組的隨機金鑰
print(len(key)) # 32
print(len(key.hex())) # 64
print(len(base64.b64encode(key))) # 44Code language: Python (python)
這裡拿到的 key 是原始的位元組,也就是 32 個位元組的隨機資料,不是一串可以直接閱讀的文字。要存進設定檔或在系統之間傳遞時,才轉成十六進位(64 個字元)或 Base64(44 個字元)。如果不想引入這個套件,Python 標準庫的 secrets.token_bytes(32) 同樣是密碼學安全等級的做法,產生的也是 32 位元組。
用 OpenSSL 指令產生並輸出成十六進位或 Base64
不寫程式的站長,也能用一行指令拿到可用的 AES-256 金鑰。OpenSSL 的 rand 指令加上輸出格式即可,兩種格式的結果對應同一個 256 位元的長度。
openssl rand -hex 32 # 64 個十六進位字元,對應 256 位元
openssl rand -base64 32 # 44 個 Base64 字元,對應 256 位元Code language: Bash (bash)
指令最後的 32 指的是位元組數,不是位元數,所以要 AES-256 就寫 32,AES-128 則寫 16。看到 64 個十六進位字元或 44 個 Base64 字元,就知道長度是對的。還有一點要留意,金鑰顯示在畫面上之後,會留在終端機視窗的捲動紀錄裡,分享螢幕或截圖時也可能一起被帶出去,所以產生之後要立刻放進適當的保管處,不要貼進聊天室或客服工單。

手上只有密碼時,先經金鑰推導函式處理
很多時候手上的不是一串隨機位元組,而是使用者自己輸入的密碼。人記得住的密碼亂度很低,長度也不固定,不能直接補零或截斷之後就拿來當 AES 金鑰。正確的做法是先經過金鑰推導函式(KDF),由它把密碼加上鹽值、處理成指定長度的金鑰。OWASP 在密碼儲存規範中推薦的幾種慢速演算法裡,Argon2id、scrypt 與 PBKDF2 都能從密碼推導出金鑰。這裡只需要記得選用這類專為此設計的函式,參數怎麼調則是另一個題目。
OWASP 的加密儲存規範還提到一種分層做法,是用 KDF 從使用者輸入的密碼導出金鑰加密金鑰(KEK),再由它去加密另一把隨機產生的資料金鑰(DEK)。好處是使用者改密碼時,只要換掉 KEK 重新保護 DEK 就好,不必把所有資料重新加密一遍。順帶一提,如果要存的是使用者登入密碼本身,那就不該用 AES 加密,而是要用雜湊。
加密模式選錯,金鑰再長也補不回來
AES 一次只能處理 128 位元,但實際要加密的可能是一份訂單、一張圖片,或整個資料庫的匯出檔。「模式」就是規定多個區塊要怎麼串在一起處理的規則。模式選錯的傷害,常常比金鑰長短差一級還要大,因為它會讓攻擊者不需要碰金鑰就有機會讀出資料的規律,或改動資料而不被發現。
OWASP 的優先序很清楚,有認證功能的模式優先,最常用的是 GCM 與 CCM;沒有它們時才用 CTR 或 CBC,但要另外補上驗證;ECB 除非是非常特殊的情況,否則不應該使用。這裡的「認證」指的是 AEAD(附帶關聯資料的認證加密),同時提供機密性與完整性,也就是別人看不懂內容,內容被改動過也會被發現。一般站長與開發者的實務結論只有一句,就是用函式庫提供的 AES-GCM 高階介面,不要自己組模式。OWASP 也提到,模式需要的 IV 這類參數,原則上應該由函式庫自動處理。
ECB 會讓相同的明文區塊產生相同的密文
ECB 的問題不在 AES 本身,而在它讓每個區塊各自獨立加密。NIST 的 SP 800-38A 說得直接,在 ECB 模式下,同一把金鑰加密同一個明文區塊,永遠得到同一個密文區塊;如果這個特性對應用來說不可接受,就不該使用 ECB。
這個特性在多數情況下都不可接受。只要資料有結構、有重複的內容,例如固定格式的欄位、重複出現的字詞或大面積相同的像素,這些重複就會原封不動地反映在密文裡,密文看起來雖然是亂碼,輪廓與規律卻還留著。所以看到程式碼裡出現 ECB,很值得停下來確認是不是真有特殊理由,絕大多數的情況答案是沒有。
GCM 一次完成加密和完整性驗證
GCM 是目前最推薦的預設選擇,因為它把加密和完整性驗證合在一起,加密時會多產生一段認證標籤,解密時先驗證標籤,被改動過的密文會直接被拒絕,不會輸出一份被竄改過的明文。下面是完整可執行的範例,示範每則訊息都產生新的 nonce,並且把 nonce 與密文放在一起保存。
import os
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
key = AESGCM.generate_key(bit_length=256)
aesgcm = AESGCM(key)
nonce = os.urandom(12) # 每則訊息都要新產生
aad = b"order-1001" # 選用,綁定上下文,解密時要給同一份
ciphertext = aesgcm.encrypt(nonce, "龐果設計 王小明".encode("utf-8"), aad)
# 保存時把 nonce 與密文放在一起;解密時再拆開
plaintext = aesgcm.decrypt(nonce, ciphertext, aad).decode("utf-8")
print(plaintext) # 龐果設計 王小明Code language: Python (python)
範例裡的 nonce 用 12 位元組,也就是 96 位元,每次加密都重新產生,它不需要保密,和密文放在一起保存即可。密文會比明文多出 16 位元組的認證標籤,例如這段範例的 22 位元組明文,加密後會變成 38 位元組。aad 是附加資料,不會被加密,但會被綁進驗證,範例裡用訂單編號示意,這樣一來,同一份密文被搬到別筆訂單底下時,驗證就會失敗。
解密時只要金鑰、nonce、附加資料三者任何一個不對,或密文被改動了任何一個位元組,cryptography 都會丟出 InvalidTag 例外,而不是給出一份亂碼。程式要處理的就是這個例外,也就是當成解密失敗,拒絕這筆資料,不要嘗試修補或猜測。
同一把金鑰下,nonce 不可重複
GCM 最容易出事的地方不在演算法,而在 nonce,在 NIST 的文件裡又叫 IV。規則只有一條,同一把金鑰下,每次加密用的 nonce 都必須不同。NIST 的 SP 800-38D 明確警告,只要有一個 IV 被重複使用,實作就可能遭到偽造攻擊,所以如果無法確保 IV 的唯一性,就不應該部署 GCM。
NIST 同時建議實作僅支援 96 位元的 IV,這在互通性、效率與設計簡單上都有好處,也就是範例裡 12 位元組的來源。用隨機方式產生 IV 的話,NIST 規定同一把金鑰下的認證加密總次數不得超過 2 的 32 次方,約 43 億次。這個上限的實務意義是,金鑰不能無限期地用下去,用量大的系統要有定期輪替金鑰的機制。
實務上有兩個常見的出錯方式。一是圖省事,把 nonce 寫死成固定值;二是改用計數器產生 nonce,卻在多台伺服器上各自從 0 開始數,結果不同機器對同一把金鑰產生了相同的 nonce。這兩種情況加密都能跑、解密都能過,錯誤不會顯現,卻已經破壞了 GCM 的安全前提。

CBC、CTR 這類模式要自行補上驗證
在舊的程式碼或舊的系統裡,常會遇到 CBC 與 CTR 這兩種模式。它們仍然在標準裡,但本身不保證資料的真實性,也就是無法發現密文是否被改動。OWASP 把它們定位成 GCM、CCM 不可用時的備案,使用時要另外做驗證,例如 Encrypt-then-MAC,而且處理變長訊息時要特別小心。
所以遇到舊系統用 CBC,判斷的方向是「有沒有補上驗證」,而不是「一律不能用」。有補上驗證、做法正確,那就是一個能運作的舊系統;沒有補,就是需要優先處理的缺口。新寫的程式如果有得選,直接用 GCM,把這些細節交給函式庫即可。
金鑰保管的難處,以及金鑰與資料分開存放的原則
OWASP 把安全地保管加密金鑰形容為最難解決的問題之一,原因是應用程式為了解密,一定得在某種程度上拿得到金鑰。這句話點出了保管的本質,沒有任何做法能讓金鑰「絕對拿不到」,能做的是提高別人取得金鑰的門檻。
OWASP 也建議,金鑰與加密過的資料盡量放在不同的位置,例如資料放在資料庫、金鑰放在檔案系統。這樣攻擊者只攻破其中一邊時,手上不是一堆打不開的密文,就是一把沒有東西可解的鑰匙。反過來說,如果金鑰和密文躺在同一個資料庫、同一個備份檔裡,一次外洩就同時失去兩者,加密幾乎就形同虛設。
金鑰不寫進程式碼,也不提交到版本庫
在沒有專用金鑰儲存機制可用的環境,例如共用主機,OWASP 仍列出幾條最基本的規則:
- 不要把金鑰寫死在應用程式的原始碼裡。程式碼會被複製、被分享、被備份,金鑰跟著走,就收不回來了。
- 不要把金鑰提交到版本控制系統,例如 Git 儲存庫。版本庫會保留曾經提交過的內容,就算之後把那一行刪掉,歷史紀錄裡仍然查得到。
- 放金鑰的設定檔要用嚴格的檔案權限保護,只讓執行程式的帳號讀取。
環境變數常被拿來當折衷方案,但 OWASP 在這份規則裡的說法是盡量避免把金鑰放在環境變數,因為環境變數可能經由 phpinfo() 這類函式,或 /proc/self/environ 這個檔案意外外洩。公平地說,環境變數仍然比直接寫死在程式碼裡好,至少金鑰不會跟著程式碼一起被複製、被提交,但它不是萬無一失的做法,不該被當成標準答案。
共用主機環境本來就沒辦法取得高強度的金鑰保護,這是環境的限制,不是站長做錯了什麼。在這種情況下,把能做的做滿,也就是金鑰放在程式碼之外、設定檔權限收緊、不進版本庫,並且與加密資料分開存放。
金鑰管理服務和資料金鑰、金鑰加密金鑰的分層
有條件的話,OWASP 建議改用作業系統、框架或雲端服務提供的專用金鑰儲存機制,類型包括實體的硬體安全模組(HSM)、虛擬的 HSM,以及金鑰保管庫這類由雲端平台提供的金鑰管理服務(KMS)或外部的 secrets 管理系統。和把金鑰放在設定檔相比,它們有幾項好處:
- 集中管理金鑰,在容器化的環境尤其明顯。
- 輪替與更換金鑰比較容易。
- 金鑰由系統安全地產生。
- 比較容易符合 FIPS 140 或 PCI DSS 這類規範。
- 攻擊者較難把金鑰匯出或竊取。
這類服務通常會搭配兩層金鑰的設計。資料用資料金鑰(DEK)加密,DEK 本身再用金鑰加密金鑰(KEK)加密。加密後的 DEK 可以和資料放在一起,但 KEK 必須與 DEK 分開存放,沒有 KEK,旁邊那把加密後的 DEK 就打不開。另外,KEK 的強度至少要和 DEK 一樣。
不過這種分層在簡單的架構裡價值有限。如果系統小到 KEK 與 DEK 必須放在同一處,OWASP 指出,攻擊者很可能同時取得兩把金鑰,這層設計就幫不上什麼忙。所以是否要導入專用的金鑰管理機制,要看自己的架構有沒有辦法真的把兩者分開放。
金鑰輪替的時機與遺失金鑰的後果
金鑰不是設定一次就永遠不換。依 OWASP 的說明,需要輪替金鑰的情況包括已知或疑似外洩(例如有權限的人離職)、到了預定的使用期限(稱為 cryptoperiod)、接近所選加密模式的使用上限(像前一節提到的 GCM 次數限制),以及演算法的安全性出現重大變化。使用期限要訂多久,OWASP 指向 NIST SP 800-57 的建議作為參考。
換了金鑰之後,舊資料有兩種處理方式。第一種是把舊資料解密,再用新金鑰重新加密,這是 OWASP 偏好的做法;第二種是替每筆資料標記它使用的金鑰編號,並保留多把金鑰以便解密。不論哪一種,被退役的舊金鑰在一段期間內都要保留,因為舊的備份可能還需要它才打得開。輪替的程式與流程,也要在需要之前先準備好,等到金鑰外洩的那一天才開始寫,通常來不及。
反過來說,金鑰一旦遺失,資料就等於再也打不開。OWASP 的金鑰管理規範也提醒,用遺失的金鑰加密的資料永遠無法復原,所以應用程式要具備安全的金鑰備份機制,而且這份備份同樣不能和加密資料放在同一個地方。

網站上的 AES,連線層和資料層是兩回事
AES 在網站上出現的位置,可以分成兩層。連線層是 HTTPS 背後的 TLS,由伺服器與瀏覽器自動處理,站長不需要自己產生、管理 AES key;資料層則是自己的應用程式、資料庫或備份要加密資料的時候,這時才輪到前面談的金鑰與模式知識。
釐清這個分界,就能判斷網站上哪些 AES 要自己管、哪些不用管。把 SSL 憑證裝好,不等於握有一把 AES key,它們是兩件不同的事。
HTTPS 連線的 AES 由 TLS 1.3 自動協商
TLS 的運作分成兩個主要元件。依 IETF 的 RFC 9846,握手協定負責認證通訊雙方、協商加密模式與參數,並建立共用的金鑰材料;之後的記錄協定,再用握手建立的參數去保護雙方的通訊內容。連線時用來加密資料的 AES 金鑰,就是在這個過程中自動導出的,站長不需要設定,也沒有機會看到。
TLS 1.3 移除舊演算法之後,留下來的全是 AEAD 演算法,也就是前面說的認證加密。RFC 9846 規定,TLS 1.3 的實作必須支援 TLS_AES_128_GCM_SHA256,並建議支援 TLS_AES_256_GCM_SHA384 與 TLS_CHACHA20_POLY1305_SHA256;另外還定義了 TLS_AES_128_CCM_SHA256 等 CCM 套件。和前面提到的一樣,這些 AES 套件都沒有 192 位元的選項。
憑證裡的 RSA 或 ECC 金鑰屬於另一層,負責在握手時證明伺服器的身分,不是拿來加密網頁內容的 AES 金鑰。站長要做的是把憑證與 HTTPS 設定好,連線用的 AES 金鑰則交給 TLS 自動處理。
資料庫與備份檔的加密要自己決定層級與保管金鑰
資料層才是站長或開發者要自己決定加密、並負責管理金鑰的地方。OWASP 指出,加密可以發生在應用層、資料庫層(例如 TDE)、檔案系統層(例如 BitLocker、LUKS)與硬體層,選哪一層取決於要防的威脅。硬體層加密可以防止實體失竊,但伺服器被遠端入侵時,它幫不上忙;應用層加密則由程式自己控制,金鑰管理的責任也在自己身上。
在動手加密之前,還有兩個更優先的原則。第一,能不存就不存,OWASP 認為保護敏感資訊最好的方式就是一開始不要儲存。第二,加密不能取代存取控制,應用程式的設計要讓加密失效時仍然安全,被加密的資訊也還需要有其他安全層保護。
備份檔加密時,金鑰(或密語)同樣要自己保管,而且不能和備份檔放在同一處,這就是前面金鑰保管那一節的原則在備份上的應用。最後還有一條,不要自己發明加密演算法,OWASP 對這件事的態度只有一句「Don’t do this」,改用維護中的函式庫就好。
AES key、wp-config 的 salts 與密碼雜湊是 3 件事
WordPress 站長很容易把三種東西混在一起。wp-config.php 裡有八組以 KEY 與 SALT 結尾的設定,名稱裡的 KEY 讓人以為是加密金鑰,其實它們是 WordPress 在處理登入 cookie 與 nonce 這類資料時,加進雜湊裡的祕密值。WordPress 的開發者文件把 wp_salt() 描述為回傳一個要加進雜湊的 salt,而 wp-config-sample.php 的註解也說明,這些值隨時可以更換,更換之後所有現有的 cookie 會失效,所有使用者都得重新登入。
就 WordPress 核心來說,這些值和 AES 加解密無關,換掉只會讓大家被登出;不過有些外掛會借用這些值來加密自己儲存的資料,更換之前要先確認站上有沒有這類外掛,否則那些資料可能會讀不回來。
另一個混淆是使用者密碼。WordPress 從 6.8 起,改用 bcrypt 來雜湊儲存使用者密碼,過去用的則是 phpass。依 WordPress 核心團隊在 2025 年 2 月的公告,為了避開 bcrypt 的 72 位元組長度限制,系統會先以 SHA-384 預雜湊;站長與使用者不需要做任何動作,舊的密碼雜湊仍然有效,使用者下次登入時會自動重新雜湊。OWASP 的立場則更直接,密碼不應該用可逆的加密儲存,而應該用安全的密碼雜湊演算法。
雜湊和加密的用途不同,關鍵在能不能還原。AES 是可逆的,有金鑰就能把密文還原成原文,適合需要再讀出來的資料;雜湊是單向的,驗證時只比對結果,適合登入密碼這種不需要被還原的東西。下表把三者放在一起:
| 項目 | 是什麼 | 用途 | 更換的後果 |
|---|---|---|---|
| AES key | 加解密共用的隨機位元組 | 保護日後還要讀出來的資料 | 舊密文要用舊金鑰解密,或解密後重新加密 |
| wp-config 的 KEY 與 SALT | 登入 cookie 與 nonce 用的祕密值 | 加進雜湊,驗證登入狀態 | 所有現有 cookie 失效,所有人重新登入 |
| 密碼雜湊 | 單向運算的結果 | 登入時比對密碼 | 不涉及金鑰,沒有可更換的金鑰 |
這也說明了兩種常見的誤用。拿 AES 去加密使用者密碼,等於把一個本來不需要還原的東西做成可以還原,一旦金鑰外洩,所有密碼都會一起露出;把 wp-config 的 salts 當成 AES key 來用,則會讓日後想換 salts 強制登出時,舊的加密資料跟著打不開。至於雜湊本身的運作細節,是另一個值得獨立談的題目,這裡只需要記得它和加密是兩種用途。

接下來若要替網站的資料加上 AES 加密,先別急著挑 128 還是 256,該先決定的是這把 AES key 會從哪裡來、放在哪裡、什麼時候換掉。三個問題都答得出來,這份加密才有意義;答不出來的,先補上答案,再動手加密。
