戰(zhàn):crypto-js實(shí)現(xiàn)敏感數(shù)據(jù)安全傳輸)
前端和后端之間傳敏感數(shù)據(jù)很多團(tuán)隊(duì)第一反應(yīng)是“我上了HTTPS就行了”但實(shí)際做過安全評(píng)審的都知道HTTPS只保護(hù)鏈路不保護(hù)業(yè)務(wù)數(shù)據(jù)本身。一旦日志被撈走、反向代理被碰、或者上游服務(wù)明文落庫數(shù)據(jù)照樣裸奔。我之前的項(xiàng)目里正好碰到類似的硬需求Vue前端要把用戶敏感信息傳到某后端服務(wù)鏈路要求不能出現(xiàn)明文于是就把AES加密這套方案完整落地了。這篇文章會(huì)把前端Vue用crypto-js做AES加密、后端做解密的完整過程講清楚包括參數(shù)選擇、代碼封裝、踩過的坑以及一些常規(guī)文檔里不會(huì)寫的東西。如果你正打算做前后端數(shù)據(jù)加密或者只是好奇AES在真實(shí)項(xiàng)目里怎么用這篇文章應(yīng)該是能直接拿去參考的。1. 為什么要在前后端之間做AES加密1.1 這個(gè)方案解決的真實(shí)問題先說結(jié)論AES加密不是用來替代HTTPS的它解決的是HTTPS兜不住的那一層。幾個(gè)實(shí)際場(chǎng)景你自己感受一下。第一前端頁面在瀏覽器里跑用戶輸入的身份證號(hào)、手機(jī)號(hào)、銀行卡信息如果直接用明文拼進(jìn)請(qǐng)求體哪怕有HTTPS只要任何一層日志記錄了請(qǐng)求體敏感字段就漏了。第二很多系統(tǒng)會(huì)加網(wǎng)關(guān)或者日志采集組件請(qǐng)求體經(jīng)常會(huì)被整體打印這就是明文大范圍暴露的高發(fā)區(qū)域。第三后端的數(shù)據(jù)庫表如果也按明文存儲(chǔ)一旦備份文件泄露那更是一鍋端。當(dāng)初項(xiàng)目組的做法是把“傳輸加密”和“存儲(chǔ)加密”分開設(shè)計(jì)存儲(chǔ)部分單獨(dú)走庫表加密傳輸部分就是用AES做業(yè)務(wù)字段加密。需求很明確前端在發(fā)送前把明文變成密文后端收到以后解密中間任何環(huán)節(jié)看到的都是不可讀的密文串。1.2 對(duì)稱加密為什么適合這個(gè)場(chǎng)景對(duì)稱加密和非對(duì)稱加密選型的時(shí)候確實(shí)糾結(jié)過一輪。RSA是非對(duì)稱公鑰加密、私鑰解密好處是公鑰可以隨便丟私鑰留后端前端不需要知道私鑰。聽起來很完美但問題是RSA加密的數(shù)據(jù)有長度限制2048位密鑰最多只能加密約117字節(jié)的明文你要傳一個(gè)幾百字的表單得分片加密繁瑣不說性能還慢。AES是對(duì)稱加密加解密用的都是同一把密鑰速度快適合批量數(shù)據(jù)單次能處理的數(shù)據(jù)量遠(yuǎn)大于RSA。密鑰交給前后端各持一份前端加密后端解密。它的弱點(diǎn)也很直觀密鑰不能泄露一旦密鑰出了前端代碼倉庫或者后端配置中心整個(gè)體系就廢了。這個(gè)項(xiàng)目選AES還有一個(gè)原因前后端都有成熟的實(shí)現(xiàn)庫前端是crypto-js后端無論是Node.js還是Java標(biāo)準(zhǔn)庫直接支持不需要額外引入重量級(jí)依賴。整個(gè)鏈路做起來非常輕半天就能把代碼跑通。1.3 方案落地后的整體架構(gòu)整個(gè)加密鏈路按三個(gè)階段劃分。第一階段前端在JS層調(diào)用封裝好的AES工具函數(shù)把原始請(qǐng)求體里的敏感字段做加密生成的是Base64編碼的密文。第二階段走正常的HTTP請(qǐng)求請(qǐng)求體里攜帶的是密文字符串。第三階段后端接口收到密文調(diào)用AES解密工具類還原出明文再做業(yè)務(wù)處理。從調(diào)用方視角看只是傳了一個(gè)“看起來像亂碼”的字符串加密細(xì)節(jié)全被封裝在工具類里。從后端視角看解密邏輯也封裝在服務(wù)端工具類里Controller層基本上無感。這套架構(gòu)好處在于加解密邏輯都收斂在同一個(gè)模塊后續(xù)要換密鑰或者升級(jí)算法不用全工程到處改。2. AES加密的核心概念2.1 密鑰、IV、分組模式AES本身是分組加密算法明文會(huì)被切成固定長度的塊AES-128按16字節(jié)分塊AES-192和AES-256也按16字節(jié)分塊只是密鑰長度不同。每個(gè)明文塊獨(dú)立加密但如果你直接每個(gè)塊獨(dú)立加密相同明文塊會(huì)產(chǎn)生相同密文塊這就有嚴(yán)重的模式泄露問題。所以實(shí)際使用時(shí)必須配合分組模式讓每個(gè)密文塊依賴于前一個(gè)塊把明文規(guī)律打散。這里不得不提兩個(gè)參數(shù)密鑰和IV。密鑰Key是真正的加密鑰匙AES-128就是16字節(jié)AES-256就是32字節(jié)。IV初始化向量是給第一塊明文做“隨機(jī)擾動(dòng)”的相當(dāng)于給加密過程加一個(gè)初始偏移量每個(gè)分組模式對(duì)IV的處理不一樣但I(xiàn)V的常見長度也是16字節(jié)。項(xiàng)目里選擇的是AES-256-CBC組合32字節(jié)密鑰16字節(jié)IVCBC分組模式。為什么是256而不是128純屬安全冗余考量既然前后端跑AES在性能上差異不大能上256就不留128的短板。2.2 ECB和CBC怎么選ECB是最簡(jiǎn)單的分組模式每個(gè)明文塊獨(dú)立加密不依賴其他塊。它的致命傷在于如果明文里有兩塊內(nèi)容相同密文里也會(huì)出現(xiàn)相同的塊這樣明文的排列規(guī)律就暴露了。最典型的例子就是加密一張圖片用ECB模式加密之后圖片輪廓還能隱約看出來。這種模式在真實(shí)項(xiàng)目里基本被廢了。CBC模式是每個(gè)明文塊先與前一個(gè)密文塊做異或然后再加密。第一個(gè)明文塊沒有前一個(gè)密文塊就用IV來異或。這樣一來相同明文在不同位置、不同IV下加密出來的結(jié)果完全不一樣明文規(guī)律被打亂了。代價(jià)是不能并行加密而且前一個(gè)塊出錯(cuò)會(huì)影響下一個(gè)塊但對(duì)前后端小數(shù)據(jù)量加密來說這個(gè)代價(jià)可以忽略。項(xiàng)目里用CBC而不是更先進(jìn)的GCM主要考慮還是兼容性和實(shí)現(xiàn)成熟度。CBC是目前各種語言之間最容易對(duì)齊的AES模式加上PKCS7填充前后端基本不會(huì)出現(xiàn)“加密能過、解密不過”的問題。GCM雖然帶認(rèn)證能防篡改但跨語言實(shí)現(xiàn)時(shí)需要注意的細(xì)節(jié)更多第一次搞加解密不建議直接上。2.3 填充、編碼、Base64AES要求明文長度必須是16字節(jié)的整數(shù)倍實(shí)際業(yè)務(wù)數(shù)據(jù)很難恰好滿足比如加密一個(gè)“123456”只有6個(gè)字節(jié)所以必須填充。最常見的填充模式是PKCS7缺幾個(gè)字節(jié)就補(bǔ)幾個(gè)值為“缺少數(shù)”的字節(jié)。缺5個(gè)字節(jié)就補(bǔ)5個(gè)0x05如果明文剛好是16的倍數(shù)還要額外加一整塊0x10否則解密端沒辦法判斷末尾是不是填充數(shù)據(jù)。Java里寫的PKCS5Padding和前端crypto-js里的Pkcs7實(shí)際它們?cè)谶@個(gè)場(chǎng)景下是同一回事因?yàn)镴ava的PKCS5Padding在AES分組大小16字節(jié)的語境下底層實(shí)現(xiàn)跟PKCS7一致。這個(gè)知識(shí)點(diǎn)特別容易讓第一次做AES的人迷茫我第一次對(duì)接時(shí)也繞了一下。編碼環(huán)節(jié)加密結(jié)果是二進(jìn)制字節(jié)流直接放進(jìn)JSON會(huì)亂碼所以要轉(zhuǎn)成可打印字符串。最通用的方式是Base64前端加密后用toString()直接得到Base64串后端用Base64解碼器還原字節(jié)數(shù)組再解密。整個(gè)鏈路統(tǒng)一走Base64基本不用考慮其他編碼方案。3. 前端Vue工程實(shí)現(xiàn)AES加密3.1 引入crypto-js依賴前端庫選了crypto-js它是一個(gè)非常成熟的純JS加密庫支持AES、DES、SHA系列等各種算法體積不算小但項(xiàng)目里只用到AES按需引入模塊就好。安裝命令很簡(jiǎn)單npm或者yarn都行npm install crypto-js這里有個(gè)坑crypto-js的版本差異會(huì)導(dǎo)致模塊導(dǎo)入方式不一樣。如果你用的Vue2配合較老的項(xiàng)目可能直接import CryptoJS from crypto-js而新版項(xiàng)目里這么做也沒問題因?yàn)樗J(rèn)導(dǎo)出了整個(gè)庫。如果你只想引入AES模塊可以這么做import CryptoJS from crypto-js;用整個(gè)庫的導(dǎo)入方式在構(gòu)建時(shí)體積會(huì)大一點(diǎn)但如果項(xiàng)目里已經(jīng)有crypto-js其他算法在跑那就直接復(fù)用全局導(dǎo)入。我的項(xiàng)目里是統(tǒng)一用全局導(dǎo)入省得維護(hù)多個(gè)子模塊路徑。3.2 封裝加密工具類前端不能把加密邏輯散落在各個(gè)頁面組件里所有加解密必須收斂到一個(gè)工具文件統(tǒng)一出入口。我習(xí)慣建一個(gè)src/utils/crypto.js專門放AES加密相關(guān)邏輯后續(xù)要改密鑰或者調(diào)整算法只動(dòng)這個(gè)文件就夠了。核心代碼看起來是這樣import CryptoJS from crypto-js; const AES_KEY your-32-byte-secret-key-0123456789abcdef; const AES_IV your-16-byte-iv-string; export function encryptByAES(plainText) { const key CryptoJS.enc.Utf8.parse(AES_KEY); const iv CryptoJS.enc.Utf8.parse(AES_IV); const encrypted CryptoJS.AES.encrypt(plainText, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); return encrypted.toString(); }幾個(gè)細(xì)節(jié)說明一下。密鑰長度必須嚴(yán)格匹配算法AES-256要求密鑰是32字節(jié)如果字符串長度不夠或者超過crypto-js不會(huì)直接報(bào)錯(cuò)但后端解密時(shí)會(huì)得到一串亂碼或者直接異常這一點(diǎn)特別坑。IV固定是16字節(jié)不能隨便寫個(gè)8位字符串湊數(shù)。encrypted.toString()默認(rèn)輸出Base64格式后端拿到以后要按Base64解不要再去額外做URL編碼除非你想把密文直接塞到URL的query參數(shù)里那又是另一種處理方式。3.3 請(qǐng)求攔截器集成工具類封裝好以后怎么接入業(yè)務(wù)代碼是個(gè)選擇。最省事的做法是在axios請(qǐng)求攔截器里統(tǒng)一處理但有個(gè)前提只對(duì)指定的敏感字段做加密而不是對(duì)整個(gè)請(qǐng)求體加密。如果對(duì)整個(gè)請(qǐng)求體加密后端所有接口的入?yún)⒛P投家某山邮彰芪母膭?dòng)面太大。我的做法是請(qǐng)求攔截器里遍歷一個(gè)配置好的敏感字段列表只要請(qǐng)求參數(shù)里出現(xiàn)這些字段名就原地替換成加密后的密文。字段列表比如[phone, idCard, account]每個(gè)字段的值在發(fā)送前都會(huì)被加密。這一段代碼大致長這樣import { encryptByAES } from /utils/crypto; const sensitiveFields [phone, idCard, account]; service.interceptors.request.use((config) { if (config.data typeof config.data object) { const data { ...config.data }; Object.keys(data).forEach((key) { if (sensitiveFields.includes(key) typeof data[key] string) { data[key] encryptByAES(data[key]); } }); config.data data; } return config; });這里有幾個(gè)坑需要特別提示。第一有些表單字段值是數(shù)字類型比如手機(jī)號(hào)存成了number你得先把數(shù)字轉(zhuǎn)成字符串再加密否則crypto-js會(huì)做隱式轉(zhuǎn)換加密出來的結(jié)果可能跟后端期望的不一致。第二GET請(qǐng)求的query參數(shù)也有敏感字段那就不能只在body里處理需要把query參數(shù)也走一層加密。第三如果字段值本身已經(jīng)是密文比如編輯頁面回顯時(shí)后端返回的就是密文前端就不要二次加密這個(gè)要做一次狀態(tài)判斷否則后端解密出來就是個(gè)雙重加密的亂碼。4. 后端服務(wù)的解密實(shí)現(xiàn)4.1 Node.js版解密服務(wù)后端如果用的是Node.js不需要裝第三方庫直接使用內(nèi)置的crypto模塊就能完成解密。Node.js的crypto模塊功能非常強(qiáng)唯一要注意的是API風(fēng)格新版本支持createDecipheriv別再用已經(jīng)廢棄的createDecipher。解密函數(shù)封裝如下const crypto require(crypto); const ALGORITHM aes-256-cbc; const AES_KEY your-32-byte-secret-key-0123456789abcdef; const AES_IV your-16-byte-iv-string; function decryptByAES(encryptedBase64) { const key Buffer.from(AES_KEY, utf8); const iv Buffer.from(AES_IV, utf8); const decipher crypto.createDecipheriv(ALGORITHM, key, iv); let decrypted decipher.update(encryptedBase64, base64, utf8); decrypted decipher.final(utf8); return decrypted; } module.exports { decryptByAES };這里必須要強(qiáng)調(diào)final()方法。很多新手寫到這里只調(diào)用了update()發(fā)現(xiàn)解出來還是不完整的字符串或者直接報(bào)錯(cuò)原因就是final()才是把解密結(jié)果完整輸出的最后一步。update()方法在讀入密文數(shù)據(jù)時(shí)由于分組大小對(duì)齊的原因可能只是部分輸出最后一點(diǎn)數(shù)據(jù)必須通過final()沖刷出來。Buffer.from(AES_KEY, utf8)特別關(guān)鍵。有人習(xí)慣直接傳字符串給createDecipheriv比如crypto.createDecipheriv(ALGORITHM, AES_KEY, AES_IV)這種寫法在Node.js 12之前的版本還能正常工作新版本里密鑰和IV必須是Buffer類型否則會(huì)拋異常。為了統(tǒng)一我都是顯式轉(zhuǎn)Buffer。4.2 Java版解密實(shí)現(xiàn)后端的另一個(gè)常見技術(shù)棧就是Java。很多公司的服務(wù)端是Java Spring Boot跟Vue前端對(duì)接時(shí)解密邏輯用Java實(shí)現(xiàn)也很順手。Java標(biāo)準(zhǔn)庫自帶的javax.crypto完全夠用不需要引入額外的三方庫。核心代碼如下import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.util.Base64; public class AesUtil { private static final String ALGORITHM AES/CBC/PKCS5Padding; private static final String AES_KEY your-32-byte-secret-key-0123456789abcdef; private static final String AES_IV your-16-byte-iv-string; public static String decrypt(String encryptedBase64) throws Exception { byte[] encryptedBytes Base64.getDecoder().decode(encryptedBase64); SecretKeySpec keySpec new SecretKeySpec( AES_KEY.getBytes(StandardCharsets.UTF_8), AES); IvParameterSpec ivSpec new IvParameterSpec( AES_IV.getBytes(StandardCharsets.UTF_8)); Cipher cipher Cipher.getInstance(ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); byte[] decryptedBytes cipher.doFinal(encryptedBytes); return new String(decryptedBytes, StandardCharsets.UTF_8); } }這里有個(gè)關(guān)鍵點(diǎn)Java里寫的是PKCS5Padding前端crypto-js寫的是Pkcs7但它們?cè)贏ES/CBC模式下是兼容的。原理我在前面提過Java的PKCS5Padding在AES的16字節(jié)分組下實(shí)際行為就是PKCS7。如果你在Java端寫成NoPadding那和前端就沒法對(duì)齊了解密一定要用PKCS5Padding。cipher.doFinal()在密鑰錯(cuò)誤、密文被篡改、填充不合法等情況下都會(huì)拋異常異常類型是BadPaddingException或IllegalBlockSizeException。這里要提醒的是解密失敗時(shí)不要直接把異常堆棧拋給前端應(yīng)該統(tǒng)一轉(zhuǎn)成業(yè)務(wù)異常返回一個(gè)通用的“數(shù)據(jù)解析失敗”錯(cuò)誤避免泄露內(nèi)部實(shí)現(xiàn)細(xì)節(jié)。4.3 返回?cái)?shù)據(jù)同樣走加密鏈路一開始項(xiàng)目只做了前端請(qǐng)求加密、后端解密后來安全評(píng)審提了個(gè)問題接口響應(yīng)里的敏感數(shù)據(jù)也是明文。比如用戶詳情接口返回手機(jī)號(hào)、身份證號(hào)前端頁面直接渲染那中間鏈路照樣能看到。所以后來把響應(yīng)鏈路也加上了加密后端返回敏感字段前先加密前端拿到響應(yīng)后解密。這里就要反向再寫一個(gè)工具函數(shù)。前端解密時(shí)用同樣的密鑰和IV因?yàn)槭菍?duì)稱加密加解密同源。后端加密則復(fù)用同一個(gè)解密函數(shù)里的密鑰和IV只是把Cipher初始化模式改成ENCRYPT_MODE。Java后端響應(yīng)加密的輔助方法如下public static String encrypt(String plainText) throws Exception { byte[] plainBytes plainText.getBytes(StandardCharsets.UTF_8); SecretKeySpec keySpec new SecretKeySpec( AES_KEY.getBytes(StandardCharsets.UTF_8), AES); IvParameterSpec ivSpec new IvParameterSpec( AES_IV.getBytes(StandardCharsets.UTF_8)); Cipher cipher Cipher.getInstance(ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); byte[] encryptedBytes cipher.doFinal(plainBytes); return Base64.getEncoder().encodeToString(encryptedBytes); }前端對(duì)應(yīng)的解密函數(shù)也必須補(bǔ)上export function decryptByAES(encryptedBase64) { const key CryptoJS.enc.Utf8.parse(AES_KEY); const iv CryptoJS.enc.Utf8.parse(AES_IV); const decrypted CryptoJS.AES.decrypt(encryptedBase64, key, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }); return decrypted.toString(CryptoJS.enc.Utf8); }注意decrypt方法返回的是WordArray對(duì)象要輸出字符串必須用toString(CryptoJS.enc.Utf8)很多人在這一步卡住解出來是亂碼就是因?yàn)闆]指定編碼轉(zhuǎn)換。5. 踩坑記錄幾個(gè)“解密失敗”的真相5.1 密鑰和IV不一致導(dǎo)致的詭異結(jié)果前后端密鑰完全一致看起來是基本要求但實(shí)際項(xiàng)目里出問題最多的恰恰是這里。比如前端密鑰末尾多了個(gè)空格肉眼根本看不出來后端密鑰是標(biāo)準(zhǔn)32字節(jié)前端是33字節(jié)此時(shí)crypto-js還會(huì)用這個(gè)多一位的密鑰去加密但后端解密時(shí)會(huì)直接報(bào)密鑰長度錯(cuò)誤。IV不一致就更隱蔽了。CBC模式第一個(gè)塊會(huì)跟IV做異或運(yùn)算如果IV不一致解密出來的第一個(gè)塊必然是亂碼后面的塊可能正常也可能連帶報(bào)填充錯(cuò)誤。現(xiàn)實(shí)場(chǎng)景是前端把IV寫成了另一個(gè)項(xiàng)目的IV后端還是老項(xiàng)目的IV兩邊接口聯(lián)調(diào)時(shí)發(fā)現(xiàn)第一個(gè)字段解密出來是個(gè)空值或者一長串亂碼。排查這類問題的思路是先在本地做一次自測(cè)前端加密一段固定的字符串把密文打印出來后端用同一段密文解密如果本地都過不了就別浪費(fèi)時(shí)間聯(lián)調(diào)了。5.2 Padding模式不匹配的問題前端Pkcs7和后端PKCS5Padding這對(duì)組合在實(shí)際工作中經(jīng)常會(huì)遇到反向問題有人隨意把后端改成NoPadding因?yàn)橛X得反正數(shù)據(jù)不需要填充。這么一改解密出來的字符串末尾就會(huì)帶著一堆\u0000或者奇怪的二進(jìn)制填充符最典型的是字符串末尾多了幾個(gè)不可見字符。如果確實(shí)要用NoPadding那就要求明文長度本來就是16的倍數(shù)但業(yè)務(wù)上很難保障。所以最穩(wěn)妥的就是維持PKCS7/PKCS5Padding組合兩邊都不動(dòng)這是最容易對(duì)齊的那條路。5.3 Base64傳輸中的加號(hào)問題前端生成的Base64字符串里可能帶、/、這些字符。如果在傳輸過程中密文被放在了URL的query參數(shù)里沒有做encodeURIComponent處理后端接收到的字符串里會(huì)被解析成空格/在部分網(wǎng)關(guān)里會(huì)被當(dāng)作路徑分隔符處理解密自然失敗。解決方案很簡(jiǎn)單要么把密文放在請(qǐng)求體里而不是URL里要么拼接URL時(shí)統(tǒng)一做encodeURIComponent(encryptedStr)。后端取參時(shí)要記得URL解碼一次Java里就是URLDecoder.decode(param, UTF-8)Node.js就是decodeURIComponent(param)。我在項(xiàng)目里遇到過一次線上問題排查了半下午最后發(fā)現(xiàn)就是號(hào)變成了空格。前端打印密文也正常后端日志打出來也正常但就是莫名其妙多了一個(gè)空格檢查傳輸層編碼才發(fā)現(xiàn)問題。5.4 加密字段類型與空值處理敏感字段如果是null、空字符串、或者純數(shù)字處理邏輯一定要統(tǒng)一。前端加密空字符串時(shí)加密函數(shù)依然會(huì)輸出一串有效密文后端解密出來是空字符串這沒毛病。但如果你在前端先判斷非空才加密空值就傳了明文這就有邏輯漏洞。建議統(tǒng)一策略所有敏感字段不管是否為空都走同樣的加密函數(shù)讓全鏈路保持一致。數(shù)字類型也要注意。手機(jī)號(hào)如果前端存的是number加密函數(shù)會(huì)先轉(zhuǎn)成字符串再加密后端拿到解密后是字符串“13800138000”前后端類型對(duì)不上可能導(dǎo)致數(shù)據(jù)校驗(yàn)出問題。建議前端在表單預(yù)處理階段就把這類值統(tǒng)一轉(zhuǎn)成字符串。6. 幾點(diǎn)安全建議和項(xiàng)目心得6.1 密鑰硬編碼是最大的短板整個(gè)AES方案的阿喀琉斯之踵就是密鑰管理。前端代碼跑在瀏覽器里任何代碼都可能被查看crypto-js加密邏輯會(huì)被直接看到密鑰也會(huì)暴露在被壓縮的JS文件中。所以必須清醒認(rèn)識(shí)到前端加密只是提高攻擊成本的“門鎖”不是防破解的“保險(xiǎn)柜”。項(xiàng)目里做的折中方案是分層管理密鑰上線環(huán)境通過環(huán)境變量注入不寫進(jìn)前端源碼倉庫構(gòu)建時(shí)替換占位符。后端密鑰存儲(chǔ)在配置中心不落普通配置文件。這樣至少避免了“代碼倉庫泄露等于密鑰泄露”的最壞情況。6.2 性能和體量的權(quán)衡AES加密對(duì)CPU開銷不算大但要注意密文體積。Base64編碼會(huì)把數(shù)據(jù)增大四分之一到三分之一如果你對(duì)整個(gè)請(qǐng)求體加密請(qǐng)求體積會(huì)明顯變大。項(xiàng)目里只挑了敏感字段加密最多也就十幾個(gè)字段對(duì)整體性能影響很小。如果加密字段特別多建議把所有敏感字段統(tǒng)一拼成一個(gè)JSON字符串加密后放入一個(gè)單獨(dú)字段比如encryptedData傳給后端。后端解密后從JSON中取字段。這么做不僅減少加解密調(diào)用次數(shù)還讓網(wǎng)關(guān)注意到只有一個(gè)密文段日志被碰時(shí)暴露面更小。這個(gè)方案唯一要注意的是大字段拆包和超時(shí)配置如果密鑰和加密數(shù)據(jù)量大后端接口的響應(yīng)時(shí)間會(huì)有所增加。6.3 這套方案適合用在哪里回到標(biāo)題本身Vue前端加密、后端解密AES實(shí)現(xiàn)。這套方案真正適合的項(xiàng)目類型是HTTPS已經(jīng)上了但業(yè)務(wù)方有明確的數(shù)據(jù)安全審計(jì)要求敏感字段必須在傳輸鏈路中不可讀。它適合中小型系統(tǒng)、前后端分離的業(yè)務(wù)后臺(tái)、金融類Demo、需要快速演示加密效果的項(xiàng)目。如果項(xiàng)目要求防篡改、防重放建議后續(xù)再疊加消息認(rèn)證機(jī)制比如CBC模式下再對(duì)密文做HMAC或者直接升級(jí)到GCM模式。因?yàn)锳ES-CBC本身不提供完整性保護(hù)密文被篡改以后解密端只能得到亂碼卻無法識(shí)別這是攻擊行為還是傳輸損壞。我個(gè)人在實(shí)際落地中的體會(huì)是前后端加密最怕的不是算法選錯(cuò)而是兩邊標(biāo)準(zhǔn)沒對(duì)齊。出問題的時(shí)候先確認(rèn)密鑰、IV、模式、填充、編碼五個(gè)點(diǎn)至少能把九成以上的解密失敗問題找到。先把工具類寫好、參數(shù)統(tǒng)一約定好再談安全增強(qiáng)和密鑰托管這條路走下來是最穩(wěn)的。