前言
目前移動APP鋪天蓋地,移動網(wǎng)關也是必備的系統(tǒng),移動端安全我會單獨寫一篇文章,這篇文章主要是介紹下移動網(wǎng)關的安全設計。做報文安全無非就是解決三個問題,防竊取、防篡改、防抵賴,下面我們就如何解決這3個問題來給出具體的安全方案。
1. 防抵賴
先從最簡單的問題說起,這3個問題中,防抵賴是最簡單的實現(xiàn)的,方案在業(yè)界也是比較統(tǒng)一的,就是通過雙方簽名來防抵賴。實現(xiàn)雙方簽名那就必須要兩套密鑰,一套是客戶端公私鑰,一套是網(wǎng)關端公私鑰。在客戶端保存客戶端私鑰(CK-private)和網(wǎng)關端公鑰(SK-public),網(wǎng)關端保存客戶端公鑰(CK-public)和網(wǎng)關端私鑰(SK-private)。
對報文的簽名流程其實比較簡單:
在整個過程中,因為是經(jīng)過雙方驗簽的一旦交易成功,所以一旦交易成功雙方不能對交易進行抵賴,因為報文簽名都是各自使用私鑰進行簽名,其他人無法仿造簽名發(fā)起交易,所以雙向驗簽的過程起到了一個防抵賴的作用。
安全問題:整個簽名的方案實現(xiàn)防抵賴的前提是客戶端私鑰、服務端私鑰沒有泄漏,客戶端如何安全保存客戶端私鑰?這個問題后續(xù)會在移動端安全的文章中詳解,有一些業(yè)界的標準做法,還有一些小的tips可以參考。服務端私鑰安全存儲問題一般大公司會直接保存在加密機中,很多互聯(lián)網(wǎng)初創(chuàng)公司沒有加密機一般就直接硬編碼到代碼中或者保存在用C寫的代碼中打成so包。
加密方式:非對稱(RSA、SM2)、散列算法(SHA256、SHA1、MD5、SM3)
2. 防篡改
通過防抵賴的技術一樣可以做到防篡改,因為如果報文被篡改了,那么交易簽名一定會驗證失敗,安全問題也是和防抵賴一樣,要保存好雙方的私鑰,只要在私鑰沒有丟的情況下,報文一旦被篡改,驗簽一定會失敗。
3. 防竊取
防竊取主要就是要解決報文安全性問題,很多app因為報文中沒有敏感信息所以都沒有做防竊取保護,信息除了傳輸層https加密,就沒有了,https如果沒做雙向認證,那么也比較容易被中間人攻擊,很多金融類app和網(wǎng)關之間都做了應用層的加密來防報文信息被竊取。下面我們重點來聊下如何做到報文防竊取。
報文防竊取其實就是做到報文加密,我們知道報文加密無非是對稱加密和非對稱加密,但是對加密有些了解的同學應該知道,對稱加解密和非對稱加解密之間性能差異是巨大的,對稱加密的性能是非對稱加密的幾十倍,所以對于單純的業(yè)務報文加密一般都是使用對稱加密,常用的對稱加密算法有AES、3DES、SM4,因為對稱密鑰的安全性比較低,尤其是對于APP客戶端,一旦客戶端的對稱密鑰被竊取那么整個報文加密體系將被攻破,那么我們一般會定期進行對稱密鑰的更新來保證密鑰定期被更新。下面就介紹下常用的一些報文加密方案。
3.1 傳統(tǒng)報文加密方式
為了保證報文的安全傳統(tǒng)的做法一般是客戶端和服務端都保存對稱密鑰使用對稱加密算法進行加密傳輸,對稱加密對于一般的交易是可以保證性能和安全性的。但是客戶端一定要保存好對稱密鑰,而且因為需要有一個密鑰更新體系,能夠做到定期自動更新密鑰,如果長期不更新密鑰,存在的風險就比較大。
傳統(tǒng)的報文對稱加密一般通過軟件實現(xiàn),對于安全性有要求的金融類公司一般會使用加密機來提高加解密性能。
加密:對稱加密一般通過AES、3DES、SM4來實現(xiàn),各個語言也都有這些算法的實現(xiàn)。也可以通過加密機使用加密機相關指令進行加密。
密鑰更新: 定期進行密鑰更新保證對稱密鑰定期變化,可以通過非對稱算法來加密密鑰來進行密鑰傳遞,服務端使用服務端私鑰對新的對稱密鑰加密,客戶端使用服務端公鑰進行解密,得到加密報文的對稱密鑰。
安全問題:密鑰更新(秘鑰傳輸)、密鑰安全存儲
報文對稱加密
對稱密鑰更新
3.2 一機一密方案
傳統(tǒng)的報文對稱加密方案在很多公司應用的比較廣泛,也是實施起來相對比較容易的一種方案,配合https一起使用,能防住大部分的黑客攻擊。不過隨著目前黑產(chǎn)盛行,這樣傳統(tǒng)的對稱加密方案已經(jīng)不夠安全,現(xiàn)在有些銀行和基金公司在自己的app中使用了一機一密方案,一機一密指的是每部手機都有單獨的對稱密鑰,就算一部手機的對稱密鑰被盜取了,因為每部手機工作密鑰不一樣,最后受影響的也就是一個用戶,不會影響到其他用戶。這種一機一密的方案是目前相對來說比較安全的報文加密方案,但是這種方案最大的缺點就是性能問題,因為在每次打開app的時候都需要驗證密鑰,當并發(fā)量大的時候這就使得加密機性能可能存在壓力,而且服務端在每次加解密報文的時候都要先去查詢到當前用戶使用的對稱密鑰,再去解密處理,查詢密鑰必定存在一次IO操作。
安全問題:客戶端預制根密鑰的安全存儲
一機一密的方案目前最大的問題就是交換密鑰的性能問題,如果對于金融公司要求安全性極高,那么還可以將交換密鑰的請求和相應報文全部改為數(shù)字信封的方式,那么性能將更低,但同時也更加安全,對于請求并發(fā)量很大的平臺類系統(tǒng)其實不建議采用這種方案,這種方案會使得平臺本身成為性能瓶頸,對于這類平臺有大并發(fā)量又要保證安全一個可以通過前置的WAF防火墻之類的硬件來防DDos、洪流等惡意訪問,也可以通過在平臺照顧性能稍微降低安全性,在后臺相關業(yè)務系統(tǒng)或者整體的監(jiān)控系統(tǒng)來做整體的全鏈路安全,全鏈路安全也是后續(xù)的一個安全的發(fā)展方向,因為攻破一個系統(tǒng)的安全防護容易,但是要攻破一堆系統(tǒng)的安全防護那是很難的。
3.3 動態(tài)密鑰庫方案
前面提到的方案其實都涉及到本地的密鑰安全存儲和密鑰傳遞的安全問題,那么有沒有更好的方案可以兼顧這兩個問題,并且能保證性能呢?答案肯定是有。動態(tài)密鑰庫的方案,其實可以算是白盒加密的一種簡化版,這里先解釋下白盒加密,白盒加密屬于對稱加密,是指能夠在白盒環(huán)境(密鑰和算法完全可見)下抵御攻擊的一種特殊加密方法。一般來說算法和密鑰匙獨立的,白盒加密將算法和密鑰緊密捆綁在了一起,由算法和密鑰生成一個加密表和一個解密表,然后可以獨立用查找加密表來加密,用解密表解密,不再依賴于原來的加解密算法和密鑰。
如果對安全不是很了解的朋友可能不太能看得懂,這里大白話說就是在客戶端保存了一組密鑰,但是密鑰是和算法混合在了一起,并不是簡單的在代碼中靜態(tài)final變量定義的一個密鑰字符串,而是要通過算法一定的運算才能夠得到的對稱密鑰,服務端根據(jù)同樣的算法生成解密的對稱密鑰進行報文解密。那么這種方案就很好的解決了一個密鑰安全存儲的問題和交換密鑰的問題,因為根本不需要安全存儲密鑰和交換密鑰。
但是白盒加密算法目前沒有開源的,并且都是各個安全公司的核心機密,所以我們可以通過一個變種簡化方案來實現(xiàn)類似的功能,雖然安全性比白盒加密算法弱化了,但是相對傳統(tǒng)的加密方案還是要安全很多,并且因為全程不需要交換密鑰,全程對稱加密,所以性能是能夠得到很好的保障的。
下面我就介紹下動態(tài)秘鑰庫的簡化方案,就是保留加解密密碼表,但是還是將加密算法和從密鑰表里選取密鑰的算法還有密鑰表三者分離,這樣也實現(xiàn)了密鑰的白盒,就算被拿到了,黑客也不知道每次交易要用哪把密鑰,這里最重要的就是保證密鑰選取算法的安全,密鑰選取算法需要進行高強度的混淆并且要用C來編寫要盡興防反編譯和防調試特殊處理,下面說下動態(tài)秘鑰庫的具體簡化方案,大家可以根據(jù)各自公司的特點進行優(yōu)化和升級,
1)本地預制密鑰庫,密鑰庫可以有很多種形式保存,可以保存為一個密鑰字符串數(shù)組,也可以保存為一個超長密鑰字符串類似以前的密碼表,每次取其中的幾位來作為密鑰,具體的密鑰庫形式多種多樣,自己可以自由定制,雖然說密鑰庫可以被暴露,但是還是建議通過一些手段來安全保存;
//可以將密鑰保存在本地用C需要寫的代碼中,編程成so包,防止被太容易查看到
//這里介紹一種通過超長密碼字符串作為密碼表的例子,密鑰可以在代碼中分散保存使用的時候拼接后使用
private static final String key = "45b13X60U4VFqH18rJ8fZdF5rBGt52N02G4N068RVy2c203I3GiDeg36ED0HUo347b81BWZ5wFPu59ufx23C7048fDdr927h9GP6Y49IfR76Ba295A001S6wdNDnM4gOu7608r19i4272NKVF52ahN6GEwW77850U5l4555KQ18c6OMTtAo2gH3E590q51l9gBs45xo6N547445wHoqCxUH9Hd0eO9cx33208Q43B2hRA3600V6hA860sw0e8RiR3Wv1M5601ttgQvgk432xL5IZ73885J4CnB2g9glJ697454W45P2X9310893LpQe39K921u17rSvCRs454r3Rn6N61B3VE2A27d362OV8C8R1x37Bcv4lD1t5434TV5w202WWU1c4mI5grN97H12KW4Epv2v3R904jzj23qEqh532882n3S35vz993P0fQ38iXo8xK8A29tp27Aw3D1Junwh1231y7k5uN597p158Vu8OMM1a0H2T9bn991161HX5MP1680HNgI9a1600Z78K6gujN8QmxsN2p35b10n458eB613W1RJgI3Z4j3344q9G3ZmH083379DT9e9JbGGR0bYyCbJQ1WD8yl4956I33xM762496pqYrWoTxZ3ifLI40ifRbP60gsX3183YI83ayjgoIYG8x462N7srIa6peN0Z55RG68175M7P561c0KD655p11o1363BwLVT0b032M6321mS81c5h5ZbSIkH3G83y9p431bR9JL9I8B55h8D6484Wz7Ai65Hi04qeYcvK4lIz6Q76sYPO";
2)密鑰選擇算法,在客戶端和服務端都需要設計密鑰選擇算法
上面我們定義了一個密碼表,這個密碼表,就算被別人拿到了,他也不知道取哪些字符去加解密,這就起到了一個偽白盒的功能,那么別人不知道,程序怎么知道呢?我們這里就需要一個密鑰選擇算法,這里的密鑰選擇算法我只是介紹一個簡單的例子,大家可以根據(jù)各家公司的要求要加強密鑰選擇算法的強度,對于密鑰選擇算法一定要用C寫好后混淆后編譯成so包供客戶端和服務端使用,并且要做到so包能防反編譯和防動態(tài)調試,防反編譯和防動態(tài)調試可以通過外部的三方工具來實現(xiàn),目前360加固和梆梆這塊做的比較好,之前也測試過卻是能夠達到比較高的級別。下面介紹一個簡單的基于上面密碼表的密鑰選擇算法:
- 獲取密碼表,這里根據(jù)對稱加密算法和密碼表版本號獲取本地的密碼表;
2)根據(jù)時間戳和簡單的字符串裁剪再根據(jù)100取模獲取到密鑰開始索引;
3)根據(jù)開始索引號從超長密碼字符串中向后偏移8位獲取到本次交易對稱加密密鑰(密碼表長度總長度為800,開始索引是根據(jù)100取模,所以每次一定能取到一個8個字符長的對稱密鑰);
4)獲取到本次交易的對稱密鑰后,客戶端就可以通過這個密鑰進行對稱加密了,服務端采用一樣的算法取本地密鑰后去對稱解密。
public String getDesKey(String version, long timeStamp) {
String keys = null;
LOGGER.info("請求時間戳{} ,使用版本{}", timeStamp, version);
keys = sysConfigService.getValue(BaseConstant.DESKEY + version);
int index = chooseByTimeStamp(timeStamp);
String key = keys.substring(index * 8, index * 8 + 8);
return key;
}
private int chooseByTimeStamp(long timeStamp) {
String enStr = PhoneMD5.GetMD5Code(String.valueOf(timeStamp));
String subStr = enStr.substring(enStr.length() - 2, enStr.length());
Integer resNum = Integer.parseInt(subStr, 16);
Integer result = resNum % 100;
return result;
}
因為這個方案中整體使用的是對稱加密方案,只是選取密鑰花費了一些時間,但是在內存中操作所以是非常快的,耗時可以忽略不計,所以并發(fā)性能也是能夠保證的。
總結
移動網(wǎng)關的加密方案除了上面介紹的這些其實還有很多,筆者工作過的每家公司都有相關的不同安全方案,個人覺得目前性價比最高的方案當然是動態(tài)秘鑰庫方案,該方案已經(jīng)能夠防住絕大部分的黑客攻擊,這個方案之前也可以好幾家安全公司做滲透測試的朋友聊過,它們對這個方案也做過評估,并且也測試過,發(fā)現(xiàn)在做好密鑰選擇算法的防防編譯和防動態(tài)調試的前提下,這個方案確實是可以保證性能的情況下還能提供足夠的安全性。當然密鑰選擇算法大家也有很多選擇,甚至可以使用OTP算法來實現(xiàn),這里就不詳細展開了,感興趣的朋友可以留言一起討論。