戰(zhàn):從核心原理到安全實(shí)現(xiàn)與避坑指南)
1. 為什么AES至今仍是繞不開的加密基石如果你寫過登錄接口、做過文件傳輸、配過數(shù)據(jù)庫透明加密大概率繞不開一個名字——AES。全稱Advanced Encryption Standard中文叫高級加密標(biāo)準(zhǔn)是一種對稱分組密碼算法。說人話就是加密和解密用同一把鑰匙數(shù)據(jù)按固定長度一塊一塊地處理。它解決的問題很直接——讓數(shù)據(jù)在不可信的信道上傳輸或存儲時即使被截獲也無法被還原出明文。我第一次在生產(chǎn)環(huán)境里正經(jīng)用AES是給一個用戶信息導(dǎo)出功能做字段級加密。當(dāng)時想得很簡單調(diào)個庫函數(shù)不就完了結(jié)果踩了一連串坑IV復(fù)用導(dǎo)致相同明文加密結(jié)果一樣、密鑰長度選錯導(dǎo)致性能拉胯、填充模式配錯導(dǎo)致解密報(bào)錯。后來才明白AES本身只是一個“核心引擎”真正決定安全性的是它外圍的工作模式、填充方式、IV管理和密鑰派生。這篇文章就把這些年在AES上踩過的坑、總結(jié)的經(jīng)驗(yàn)從原理到實(shí)操完整梳理一遍適合剛接觸加密的開發(fā)者也適合想回頭把細(xì)節(jié)補(bǔ)齊的老手。2. AES核心原理拆解與關(guān)鍵參數(shù)選擇2.1 分組密碼到底在“分”什么AES是分組密碼這個“分組”指的是它一次只處理固定長度的數(shù)據(jù)塊。AES的分組長度固定為128位也就是16個字節(jié)。不管你加密的是一個字節(jié)還是一個G的文件它都是按16字節(jié)為單位切開來處理的。這跟流密碼不一樣流密碼是逐字節(jié)或逐位處理的。為什么是128位這是當(dāng)年NIST公開征集AES算法時定下的規(guī)格。128位分組在安全性和效率之間取得了很好的平衡。分組太短容易被窮舉分析太長則每輪運(yùn)算的數(shù)據(jù)量增大硬件實(shí)現(xiàn)成本上升。128位分組配合128/192/256位密鑰構(gòu)成了AES的三種標(biāo)準(zhǔn)配置。這里有個容易混淆的點(diǎn)分組長度和密鑰長度是兩回事。分組長度永遠(yuǎn)是128位不會變密鑰長度可以是128、192或256位。很多人說“AES-256”指的是密鑰256位不是分組256位。這個區(qū)別在選型時很重要后面會細(xì)說。2.2 S盒AES的靈魂部件AES的每一輪運(yùn)算里有一個步驟叫SubBytes就是把每個字節(jié)通過一張固定的查找表替換成另一個字節(jié)。這張表就是S盒Substitution Box。S盒是AES安全性的核心來源之一它的設(shè)計(jì)目標(biāo)是提供非線性變換讓輸入和輸出之間的關(guān)系盡可能復(fù)雜從而抵抗線性分析和差分分析。S盒不是隨便拍腦袋生成的。它是基于有限域GF(2^8)上的乘法逆元運(yùn)算再疊加一個仿射變換構(gòu)造出來的。具體來說對每個字節(jié)先求它在GF(2^8)中的乘法逆元0映射到0然后做一個固定的仿射變換。這樣構(gòu)造出來的S盒具有良好的密碼學(xué)性質(zhì)差分均勻性、非線性度、代數(shù)次數(shù)等都經(jīng)過嚴(yán)格驗(yàn)證。實(shí)際開發(fā)中你不需要自己實(shí)現(xiàn)S盒但理解它的存在有助于你明白為什么AES能抵抗各種已知攻擊。如果你看到某個“魔改AES”換了S盒那基本可以判定它不再是標(biāo)準(zhǔn)AES了安全性無法保證。2.3 密鑰擴(kuò)展一把鑰匙變出一串鑰匙AES的加密過程是多輪迭代的。128位密鑰對應(yīng)10輪192位對應(yīng)12輪256位對應(yīng)14輪。每一輪都需要一個輪密鑰這些輪密鑰都是從原始密鑰通過密鑰擴(kuò)展算法派生出來的。密鑰擴(kuò)展的過程大致是把原始密鑰按4字節(jié)一組排列然后按照特定規(guī)則不斷生成新的字。每生成一個字都要經(jīng)過RotWord循環(huán)移位、SubWord過S盒和與輪常量Rcon異或的步驟。這個設(shè)計(jì)保證了輪密鑰之間的差異性和不可預(yù)測性。為什么需要這么多輪密鑰因?yàn)槿绻恳惠営孟嗤拿荑€攻擊者可以通過分析輪與輪之間的關(guān)系來反推密鑰。輪密鑰的引入打亂了這種規(guī)律性使得每一輪的變換都是獨(dú)立的。2.4 三種密鑰長度怎么選這是實(shí)際項(xiàng)目中最常被問到的問題。128、192、256到底選哪個密鑰長度輪數(shù)安全強(qiáng)度性能影響適用場景128位10輪足夠抵御當(dāng)前所有實(shí)用攻擊最快絕大多數(shù)業(yè)務(wù)場景192位12輪更高安全邊際約慢20%對安全有額外要求的場景256位14輪最高安全邊際約慢40%長期數(shù)據(jù)保護(hù)、合規(guī)要求我的建議很直接除非有明確的合規(guī)要求或長期存檔需求否則選128位就夠了。128位AES在當(dāng)前計(jì)算能力下依然是安全的暴力破解需要的時間以宇宙年齡為單位計(jì)算。選256位帶來的性能損耗在大多數(shù)業(yè)務(wù)場景下不值得。當(dāng)然如果你的系統(tǒng)要保護(hù)的數(shù)據(jù)需要保密20年以上那256位是更穩(wěn)妥的選擇。還有一個坑有些加密庫默認(rèn)用256位有些默認(rèn)用128位??缦到y(tǒng)對接時一定要確認(rèn)雙方用的密鑰長度一致否則解密必然失敗。3. 工作模式與IV比算法本身更容易出錯的地方3.1 ECB模式為什么不能用ECBElectronic Codebook是最簡單的模式每個16字節(jié)塊獨(dú)立加密相同的明文塊產(chǎn)生相同的密文塊。這聽起來沒什么問題但實(shí)際上是個災(zāi)難。經(jīng)典的例子是“ECB企鵝圖”一張圖片用ECB加密后雖然整體數(shù)據(jù)變了但圖片的輪廓依然清晰可見。因?yàn)閳D片中大量重復(fù)的像素塊加密后還是重復(fù)的攻擊者可以通過模式分析還原出原始結(jié)構(gòu)。在實(shí)際業(yè)務(wù)中ECB的問題更隱蔽也更危險(xiǎn)。比如你加密一個用戶表所有用戶的“性別男”字段加密后都是一樣的密文攻擊者不需要解密就能統(tǒng)計(jì)出男女比例。如果加密的是登錄令牌相同令牌產(chǎn)生相同密文攻擊者可以據(jù)此判斷兩個會話是否屬于同一用戶。結(jié)論很簡單生產(chǎn)環(huán)境永遠(yuǎn)不要用ECB模式。它只適合用來理解AES的基本原理不適合任何真實(shí)場景。3.2 CBC模式與IV的正確用法CBCCipher Block Chaining模式解決了ECB的模式泄露問題。它的思路是每個明文塊在加密前先與前一個密文塊異或然后再送入AES加密。第一個塊沒有前一個密文塊就用一個初始向量IV來代替。IV的作用就是讓相同的明文在不同次加密時產(chǎn)生不同的密文。這就像做菜時加鹽同樣的食材每次加鹽的時機(jī)和量略有不同最終味道就有差異。IV不需要保密但必須滿足兩個條件一是隨機(jī)性要好二是每次加密都要用新的IV。我見過最常見的錯誤是IV固定不變。有些開發(fā)者圖省事把IV寫死在代碼里或者用全零IV。這樣一來CBC就退化成了ECB的效果——相同明文塊在相同位置產(chǎn)生相同密文。攻擊者可以通過對比不同密文的相同位置來判斷明文是否相同。正確的做法是每次加密時用密碼學(xué)安全的隨機(jī)數(shù)生成器產(chǎn)生一個新的IV然后把IV和密文一起存儲或傳輸。解密時先取出IV再用它來初始化解密過程。IV的長度必須等于分組長度也就是16字節(jié)。3.3 GCM模式帶認(rèn)證的加密CBC模式只保證機(jī)密性不保證完整性。也就是說攻擊者雖然不能解密你的數(shù)據(jù)但可以篡改密文導(dǎo)致解密后得到錯誤但看似合法的明文。這在很多場景下是危險(xiǎn)的比如你加密的是一個轉(zhuǎn)賬指令。GCMGalois/Counter Mode模式同時提供機(jī)密性和完整性認(rèn)證。它在加密的同時生成一個認(rèn)證標(biāo)簽Tag解密時會驗(yàn)證這個標(biāo)簽。如果密文被篡改過標(biāo)簽驗(yàn)證就會失敗解密直接報(bào)錯。GCM的另一個優(yōu)勢是支持并行處理性能比CBC好。在支持AES-NI指令集的CPU上GCM的吞吐量可以輕松達(dá)到數(shù)GB每秒。使用GCM時需要注意IV的長度通常是12字節(jié)不是16字節(jié)而且絕對不能重復(fù)使用。同一個密鑰下如果IV重復(fù)不僅會泄露明文異或關(guān)系還可能導(dǎo)致認(rèn)證密鑰被恢復(fù)。這是GCM最致命的坑務(wù)必用隨機(jī)數(shù)生成器產(chǎn)生IV并確保每次加密都不同。3.4 填充模式的選擇與坑AES的分組長度是16字節(jié)但實(shí)際數(shù)據(jù)長度不一定是16的整數(shù)倍。比如你要加密一個11字節(jié)的字符串就需要填充到16字節(jié)。最常見的填充方式是PKCS#7在AES語境下也叫PKCS#5。PKCS#7的規(guī)則是缺幾個字節(jié)就填充幾個字節(jié)每個填充字節(jié)的值等于填充的長度。比如缺5個字節(jié)就填充5個0x05。如果數(shù)據(jù)剛好是16的整數(shù)倍那就額外填充一個完整的16字節(jié)塊每個字節(jié)都是0x10。這里有個經(jīng)典的攻擊叫Padding Oracle Attack。如果服務(wù)端在解密后對填充是否合法給出不同的錯誤提示攻擊者可以通過大量請求逐步推斷出明文。防御方法很簡單解密失敗時統(tǒng)一返回相同的錯誤信息不要區(qū)分“填充錯誤”和“其他錯誤”。實(shí)操建議如果條件允許優(yōu)先用GCM模式它不需要填充天然避免了填充相關(guān)的攻擊。如果必須用CBC確保錯誤處理統(tǒng)一并且加上消息認(rèn)證碼如HMAC來防篡改。4. 從零實(shí)現(xiàn)一個安全的AES加解密模塊4.1 密鑰管理別把密鑰寫在代碼里這是老生常談但依然頻繁發(fā)生的問題。我見過太多項(xiàng)目把AES密鑰硬編碼在源碼里然后代碼提交到了版本控制系統(tǒng)。一旦代碼泄露密鑰就泄露了。正確的密鑰管理方式取決于你的部署環(huán)境。如果是單機(jī)應(yīng)用可以把密鑰放在環(huán)境變量或獨(dú)立的配置文件中并設(shè)置嚴(yán)格的文件權(quán)限。如果是分布式系統(tǒng)應(yīng)該用專門的密鑰管理服務(wù)來存儲和分發(fā)密鑰。如果是移動端可以考慮用系統(tǒng)提供的密鑰庫。密鑰本身也要足夠隨機(jī)。不要用“1234567890123456”這種可預(yù)測的字符串。用密碼學(xué)安全的隨機(jī)數(shù)生成器產(chǎn)生16或32字節(jié)的隨機(jī)密鑰。如果必須從用戶密碼派生密鑰一定要用PBKDF2、bcrypt或Argon2這類慢哈希函數(shù)加足夠的迭代次數(shù)和隨機(jī)鹽值。4.2 Java實(shí)現(xiàn)從報(bào)錯到跑通Java里做AES加密最常見的報(bào)錯就是java.security.InvalidKeyException: Wrong algorithm: AES or Rijndael required。這個錯誤通常是因?yàn)槟阌昧隋e誤的密鑰規(guī)格類。比如用SecretKeySpec時傳入了不支持的算法名或者密鑰長度不符合要求。下面是一個完整的Java AES-GCM加解密示例import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.security.SecureRandom; import java.util.Base64; public class AesGcmDemo { private static final int GCM_IV_LENGTH 12; private static final int GCM_TAG_LENGTH 128; public static byte[] generateKey() throws Exception { KeyGenerator keyGen KeyGenerator.getInstance(AES); keyGen.init(128); return keyGen.generateKey().getEncoded(); } public static String encrypt(String plaintext, byte[] key) throws Exception { byte[] iv new byte[GCM_IV_LENGTH]; new SecureRandom().nextBytes(iv); Cipher cipher Cipher.getInstance(AES/GCM/NoPadding); SecretKeySpec keySpec new SecretKeySpec(key, AES); GCMParameterSpec gcmSpec new GCMParameterSpec(GCM_TAG_LENGTH, iv); cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec); byte[] ciphertext cipher.doFinal(plaintext.getBytes(UTF-8)); byte[] combined new byte[iv.length ciphertext.length]; System.arraycopy(iv, 0, combined, 0, iv.length); System.arraycopy(ciphertext, 0, combined, iv.length, ciphertext.length); return Base64.getEncoder().encodeToString(combined); } public static String decrypt(String encrypted, byte[] key) throws Exception { byte[] combined Base64.getDecoder().decode(encrypted); byte[] iv new byte[GCM_IV_LENGTH]; System.arraycopy(combined, 0, iv, 0, iv.length); byte[] ciphertext new byte[combined.length - iv.length]; System.arraycopy(combined, iv.length, ciphertext, 0, ciphertext.length); Cipher cipher Cipher.getInstance(AES/GCM/NoPadding); SecretKeySpec keySpec new SecretKeySpec(key, AES); GCMParameterSpec gcmSpec new GCMParameterSpec(GCM_TAG_LENGTH, iv); cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec); return new String(cipher.doFinal(ciphertext), UTF-8); } }這段代碼有幾個關(guān)鍵點(diǎn)。第一IV是隨機(jī)生成的每次加密都不同并且和密文拼在一起存儲。第二用了GCM模式不需要手動填充也不需要額外的HMAC。第三密鑰用SecretKeySpec包裝時算法名必須是AES不能寫成AES/GCM/NoPadding。如果你遇到InvalidKeyException先檢查密鑰長度。Java默認(rèn)策略文件可能限制密鑰長度128位一般沒問題256位可能需要安裝無限強(qiáng)度策略文件較新版本的JDK已經(jīng)默認(rèn)支持。再檢查SecretKeySpec的算法參數(shù)必須是AES。4.3 Python實(shí)現(xiàn)cryptography庫的正確姿勢Python里我推薦用cryptography庫它比pycryptodome更現(xiàn)代API設(shè)計(jì)也更安全。下面是一個AES-GCM的示例import os from cryptography.hazmat.primitives.ciphers.aead import AESGCM def encrypt(plaintext: str, key: bytes) - bytes: aesgcm AESGCM(key) nonce os.urandom(12) ciphertext aesgcm.encrypt(nonce, plaintext.encode(utf-8), None) return nonce ciphertext def decrypt(encrypted: bytes, key: bytes) - str: nonce encrypted[:12] ciphertext encrypted[12:] aesgcm AESGCM(key) return aesgcm.decrypt(nonce, ciphertext, None).decode(utf-8) key AESGCM.generate_key(bit_length128) encrypted encrypt(hello world, key) print(decrypt(encrypted, key))cryptography庫的AESGCM類已經(jīng)幫你處理好了IV生成、填充和認(rèn)證標(biāo)簽。你只需要傳入密鑰和明文它會返回nonce密文標(biāo)簽的組合。解密時傳入相同的密鑰和組合數(shù)據(jù)即可。注意AESGCM.generate_key生成的密鑰是隨機(jī)的每次調(diào)用都不同。實(shí)際項(xiàng)目中你需要把密鑰安全地保存下來不能每次重新生成。4.4 前端JavaScript實(shí)現(xiàn)Web Crypto API瀏覽器端做AES加密現(xiàn)在標(biāo)準(zhǔn)做法是用Web Crypto API不需要引入第三方庫。下面是一個示例async function generateKey() { return await crypto.subtle.generateKey( { name: AES-GCM, length: 128 }, true, [encrypt, decrypt] ); } async function encrypt(plaintext, key) { const iv crypto.getRandomValues(new Uint8Array(12)); const encoded new TextEncoder().encode(plaintext); const ciphertext await crypto.subtle.encrypt( { name: AES-GCM, iv: iv }, key, encoded ); const combined new Uint8Array(iv.length ciphertext.byteLength); combined.set(iv, 0); combined.set(new Uint8Array(ciphertext), iv.length); return btoa(String.fromCharCode(...combined)); } async function decrypt(encryptedBase64, key) { const combined Uint8Array.from(atob(encryptedBase64), c c.charCodeAt(0)); const iv combined.slice(0, 12); const ciphertext combined.slice(12); const decrypted await crypto.subtle.decrypt( { name: AES-GCM, iv: iv }, key, ciphertext ); return new TextDecoder().decode(decrypted); }Web Crypto API的一個限制是它只在安全上下文HTTPS或localhost中可用。如果你在HTTP頁面里調(diào)用crypto.subtle會是undefined。這是瀏覽器的安全策略不是bug。另外Web Crypto API的密鑰對象不能直接序列化存儲。如果你需要持久化密鑰要先用exportKey導(dǎo)出成原始字節(jié)存儲時再做好保護(hù)。前端存儲密鑰本身就是一個難題通常建議密鑰由服務(wù)端下發(fā)前端只負(fù)責(zé)加密不負(fù)責(zé)密鑰的長期保管。5. 常見問題排查與避坑指南5.1 解密失敗問題速查表現(xiàn)象可能原因排查方法解密后亂碼密鑰不一致確認(rèn)雙方密鑰字節(jié)完全相同解密報(bào)填充錯誤IV不一致或密文被篡改檢查IV是否隨密文一起傳輸相同明文加密結(jié)果相同IV固定或用了ECB檢查IV生成邏輯報(bào)Wrong algorithmSecretKeySpec算法名錯誤改為AES256位密鑰報(bào)錯JDK策略文件限制升級JDK或安裝無限強(qiáng)度策略GCM解密報(bào)Tag mismatchIV重復(fù)或密文被修改確保IV每次不同檢查傳輸完整性跨語言解密失敗編碼或字節(jié)序不一致統(tǒng)一用Base64或Hex傳輸5.2 跨語言對接的坑Java加密、Python解密或者前端加密、后端解密這種跨語言場景特別容易出問題。最常見的原因是Base64編碼的變體不同。Java的Base64.getEncoder()用的是標(biāo)準(zhǔn)Base64Python的base64.b64encode也是標(biāo)準(zhǔn)Base64但有些庫會用URL-safe Base64把和/換成-和_。對接時一定要確認(rèn)雙方用的編碼方式一致。另一個坑是字符串編碼。Java的getBytes()默認(rèn)用平臺編碼Windows上可能是GBKLinux上通常是UTF-8。加密前一定要顯式指定UTF-8否則同樣的中文在不同平臺上加密結(jié)果不同。還有一個隱蔽的坑是GCM的Tag長度。Java默認(rèn)用128位Tag但有些庫默認(rèn)用96位或104位。對接時如果Tag長度不一致解密必然失敗。建議統(tǒng)一用128位。5.3 性能優(yōu)化的幾個實(shí)用技巧AES的性能瓶頸通常不在算法本身而在模式選擇和實(shí)現(xiàn)方式。以下是我實(shí)測有效的優(yōu)化手段第一優(yōu)先用GCM而不是CBC。GCM支持并行處理在現(xiàn)代CPU上配合AES-NI指令集吞吐量可以比CBC高好幾倍。第二避免頻繁創(chuàng)建Cipher對象。Cipher的初始化有一定開銷如果要在循環(huán)中加密大量數(shù)據(jù)可以復(fù)用Cipher對象但要注意每次加密前重新初始化IV。第三大文件加密用流式處理不要一次性讀入內(nèi)存。第四如果只是做數(shù)據(jù)完整性校驗(yàn)而不需要保密用HMAC或SHA就夠了不需要AES。實(shí)測數(shù)據(jù)在一臺普通服務(wù)器上AES-128-GCM的吞吐量約為2GB/sAES-128-CBC約為1.2GB/sAES-256-GCM約為1.5GB/s。這個數(shù)據(jù)隨CPU型號和庫實(shí)現(xiàn)不同會有差異但趨勢是一致的。5.4 那些年我踩過的真實(shí)坑第一個坑IV復(fù)用。早期做一個文件加密功能為了“方便”把IV固定成了文件名的哈希。結(jié)果相同文件名的文件加密結(jié)果一樣而且如果文件名可預(yù)測IV就可預(yù)測。后來改成每次加密隨機(jī)生成IV問題解決。第二個坑密鑰派生太簡單。用用戶密碼直接截取前16字節(jié)當(dāng)AES密鑰。這樣密鑰空間受限于密碼強(qiáng)度而且沒有鹽值彩虹表可以直接攻擊。后來改用PBKDF2迭代10萬次加隨機(jī)鹽安全性大幅提升。第三個坑錯誤處理泄露信息。解密失敗時返回了詳細(xì)的異常堆棧攻擊者可以通過不同的錯誤信息判斷填充是否合法。后來統(tǒng)一改成返回“解密失敗”不區(qū)分具體原因。第四個坑GCM的IV長度搞錯。GCM標(biāo)準(zhǔn)推薦12字節(jié)IV但我一開始用了16字節(jié)雖然也能工作但性能略差而且和某些庫對接時出現(xiàn)兼容問題。后來統(tǒng)一改成12字節(jié)。6. 對稱加密與非對稱加密的配合使用6.1 什么時候用AES什么時候用RSAAES是對稱加密加密解密用同一把鑰匙速度快適合加密大量數(shù)據(jù)。RSA是非對稱加密公鑰加密私鑰解密速度慢適合加密少量數(shù)據(jù)或做密鑰交換。實(shí)際項(xiàng)目中兩者通常是配合使用的。比如TLS協(xié)議握手階段用RSA或ECDHE交換密鑰數(shù)據(jù)傳輸階段用AES加密。這樣既解決了密鑰分發(fā)問題又保證了數(shù)據(jù)傳輸效率。如果你要加密一個幾百M(fèi)B的文件直接用RSA加密是不現(xiàn)實(shí)的速度太慢。正確做法是隨機(jī)生成一個AES密鑰用AES加密文件然后用RSA公鑰加密這個AES密鑰把加密后的AES密鑰和加密后的文件一起傳輸。接收方用RSA私鑰解密得到AES密鑰再用AES密鑰解密文件。6.2 國密算法與AES的對比在合規(guī)要求較高的場景下可能會遇到國密算法。SM1是對稱加密算法硬件實(shí)現(xiàn)128位密鑰參數(shù)不公開。SM2是非對稱算法256位用于簽名、加密和密鑰交換。SM3是雜湊算法輸出256位。SM1和AES都是對稱分組密碼但SM1的細(xì)節(jié)不公開通常以硬件形式提供。SM2和RSA都是非對稱算法但SM2基于橢圓曲線256位密鑰的安全強(qiáng)度相當(dāng)于RSA 3072位。SM3和SHA-256都是雜湊算法輸出長度相同。如果你的項(xiàng)目需要支持國密通常會用專門的密碼庫或硬件模塊。純軟件實(shí)現(xiàn)SM1比較少見因?yàn)槠鋮?shù)不公開。SM2和SM3有公開的軟件實(shí)現(xiàn)可以用BouncyCastle等庫來支持。6.3 密鑰交換的安全通道無論用AES還是國密密鑰交換都是最薄弱的環(huán)節(jié)。如果AES密鑰在傳輸過程中被截獲加密就形同虛設(shè)。安全的密鑰交換方式有幾種。一是用非對稱加密保護(hù)對稱密鑰比如用RSA公鑰加密AES密鑰。二是用密鑰協(xié)商協(xié)議比如ECDH雙方各自生成臨時密鑰對交換公鑰后計(jì)算出相同的共享密鑰。三是用預(yù)共享密鑰雙方提前通過安全渠道交換好密鑰但這不適合動態(tài)場景。實(shí)操建議如果條件允許用ECDH做密鑰協(xié)商它提供了前向安全性——即使長期私鑰泄露之前的會話密鑰也不會被恢復(fù)。如果必須用RSA加密AES密鑰確保RSA用OAEP填充不要用PKCS#1 v1.5。7. 個人實(shí)操體會與后續(xù)擴(kuò)展方向這些年用AES下來最大的體會是算法本身很少出問題出問題的永遠(yuǎn)是外圍——IV管理、密鑰派生、錯誤處理、跨語言兼容。我見過太多項(xiàng)目在AES實(shí)現(xiàn)上翻車不是因?yàn)锳ES被破解了而是因?yàn)镮V復(fù)用了、密鑰硬編碼了、錯誤信息泄露了。如果你剛開始接觸AES我的建議是先用現(xiàn)成的庫跑通一個最小示例然后逐步加上IV隨機(jī)化、密鑰派生、錯誤統(tǒng)一處理。不要一上來就追求“自己實(shí)現(xiàn)AES”除非你是做密碼學(xué)研究的。用經(jīng)過審計(jì)的庫把精力放在密鑰管理和協(xié)議設(shè)計(jì)上這才是安全性的關(guān)鍵。后續(xù)如果想深入可以研究幾個方向一是AEAD模式的更多選擇比如ChaCha20-Poly1305它在沒有AES-NI的移動設(shè)備上性能更好。二是密鑰輪換策略如何在不中斷服務(wù)的情況下定期更換加密密鑰。三是硬件安全模塊的使用把密鑰存儲在專門的硬件中即使服務(wù)器被入侵密鑰也不會泄露。這些內(nèi)容展開又是另一個話題了有機(jī)會再單獨(dú)聊。