最近搞加密,APP需要將一些數據加密存儲,顯示的時候解密顯示。對於我們這種小衆app來說,自己寫個加密算法,雖然沒有主流的那麼安全,但誰會破解這麼個app啊。算了,老闆說萬一火起來呢,也對,那就用主流加密算法吧。
首選AES對稱加密算法,AES解密的時候需要加密的密鑰,爲了不被反編譯看到密鑰,肯定不能硬編碼在代碼中,而且最好每次對同一個數據加密後生成的祕文是不一樣的。網上基本都是算法調用,具體用到應用中可能就不那麼實用。
所以當下要解決的問題有如下幾點:
- AES密鑰如何保存才能不被泄漏
- 每次加密的時候用同一個密鑰,但要使每次加密後的祕文不一樣
先解決第二點,每次加密的祕文不一樣。明文不變,密鑰不變,要使密文改變,只能每次改變向量IV值。AES加密時需要一個向量IV,只要確保每次加密時它是變的,解密時依舊是加密時的IV就可以,我是用到ByteBuffer來處理,這裏應該還可以用其他的方式處理。代碼如下:
public class AES {
private static final String AES_MODE = "AES/GCM/NoPadding";
public static String encrypt(String txt, String alias) throws Exception{
Cipher cipher = Cipher.getInstance(AES_MODE);
//每次加密生成不同的IV
byte[] iv = new SecureRandom().generateSeed(12);
cipher.init(Cipher.ENCRYPT_MODE, KeyStoreManagement.getAESKey(alias), new IvParameterSpec(iv));
// 加密
byte[] encryptedBytes = cipher.doFinal(Base64.decode(txt, Base64.DEFAULT));
//將密文和iv放一塊
ByteBuffer byteBuffer = ByteBuffer.allocate(12 + encryptedBytes.length);
byteBuffer.put(iv);
byteBuffer.put(encryptedBytes);
byte[] all = byteBuffer.array();
// 編碼成string
return Base64.encodeToString(all, Base64.DEFAULT);
}
private String decrypt(String encryptedText, String alias) throws Exception {
// 使用AES解密
Cipher cipher = Cipher.getInstance(AES_MODE);
// 解碼成byte[]
byte[] encryptedBytes = Base64.decode(encryptedText.getBytes(), Base64.DEFAULT);
//分解成iv和真正的密文
ByteBuffer byteBuffer = ByteBuffer.wrap(encryptedBytes);
byte[] iv = new byte[12];
byteBuffer.get(iv);
byte[] realEncryptedBytes = new byte[byteBuffer.remaining()];
byteBuffer.get(realEncryptedBytes);
// 解密
cipher.init(Cipher.DECRYPT_MODE, KeyStoreManagement.getAESKey(alias), new IvParameterSpec(iv));
byte[] result = cipher.doFinal(realEncryptedBytes);
//編碼成祕文
return Base64.encodeToString(result, Base64.DEFAULT);
}
}
再解決第一點,其實Android系統提供了一套密鑰保存機制KeyStore,獨立於APP,也就是不保存在APP的存儲中,這樣即使手機被ROOT也拿不到密鑰,但是卸載APP或者清除緩存依舊會刪除的。但是在實際開發的時候,發現KeyStore中的AES密鑰管理只支持6.0以上的系統,wtf,這不是歧視低版本手機嗎,不行,換方案。眼角一瞄,發現RSA算法支持6.0以下的系統,
難道用RSA進行加密,有點不符合常理啊。然後一想,可以用RSA對AES密鑰進行管理,這樣就適配所有的版本了。
總結起來,用KeyStore來創建和管理RSA密鑰,用RSA來管理自己生成的AES密鑰。使用時,先用系統KeyStore生成RSA公司鑰,再用安全隨機數生成用於加密普通數據的AES密鑰,用RSA公鑰加密該AES密鑰並保存,加密數據時,取出保存加密過的AES密鑰,用RSA私鑰解密成AES密鑰,然後用該密鑰加密我們需要加密的數據。
萬事具備,代碼完成後,各個版本自測都OK。但測試說你用的RSA填充模式是RSA/ECB/PKCS1Padding,但是這種模式已經被列爲不安全,雖然很想反駁,但是畢竟不安全畢竟要靠這個發一筆呢。你說不安全,那就換模式吧,就用RSA/ECB/OAEPWithSHA-1AndMGF1Padding吧,但是這種只能在6.0以上使用,沒辦法,那就分版本,6.0以上的用安全的填充方式,以下的依舊用原來的,這下OK了。我還是太年輕,在8.0版本翻車了,RSA直接解密失敗,找了一天原因沒有找到(網上說是8.0以上改了加密方式什麼的),官網百度谷歌搜索反正最後沒有解決,如果恰巧你知道或者研究出來,請一定告訴我。
那就換方案,主框架不變,換加密思路。最終解決方法如下(工作密鑰即加密數據的密鑰;根密鑰即加密密鑰的密鑰):
step1.初始化時用KeyStore生成根密鑰,6.0以下生成RSA密鑰,6.0以上生成AES密鑰。
step2.初始化工作密鑰,加密數據用AES加密,用安全隨機數生成AES密鑰即工作密鑰,把工作密鑰用step1中生成的根密鑰加密然後保存。可能會由於業務數據的複雜性,不同類的用不同工作密鑰加密,可能會生成多組工作密鑰。
step3.加密數據時,取出保存的工作密鑰,用step1中的根密鑰解密工作密鑰,代碼中KeyStoreManagement.getAESKey(alias),然後再加密數據。
step4.解密數據時,取出保存的工作密鑰,用step1中的根密鑰解密工作密鑰,代碼中KeyStoreManagement.getAESKey(alias),然後再解密數據。
到此方案結束,如果有什麼問題或建議,歡迎提出。