Anroid數據加密方式html
做用:java
KeyStore 適用於生成和存儲密鑰,這些密鑰能夠用來加密運行時獲取到的數據,好比運行時,用戶輸入的密碼,或者服務端傳下來的 token。android
對稱式加解密(AES)速度較快,可是對稱式的Key若要存在KeyStore裡,Api level必定要在23以上才支持,23如下是沒法存入KeyStore的,非對稱式的Key則不在此限。git
考慮到加解密效能、版本兼容,下面會介紹用非對稱式+對稱式來加解密。github
主流的加密方式有:(對稱加密)AES、DES (非對稱加密)RSA、DSAapi
工做模式:緩存
DES一共有:服務器
電子密碼本模式(ECB)、加密分組連接模式(CBC)、加密反饋模式(CFB)、輸出反饋模式(OFB);app
AES一共有:dom
電子密碼本模式(ECB)、加密分組連接模式(CBC)、加密反饋模式(CFB)、輸出反饋模式(OFB)、計數器模式(CTR),伽羅瓦計數器模式(GCM)
PKCS5Padding是填充模式,還有其它的填充模式;
對於初始化向量iv: 初始化向量參數,AES 爲16bytes. DES 爲8bytes
1 private static final String KEYSTORE_PROVIDER = "AndroidKeyStore"; 2 private static final String AES_MODE = "AES/GCM/NoPadding"; 3 private static final String RSA_MODE = "RSA/ECB/PKCS1Padding"; 4 5 private static final String KEYSTORE_ALIAS = "KEYSTORE_DEMO"; 6 7 KeyStore mKeyStore = KeyStore.getInstance(KEYSTORE_PROVIDER); 8 mKeyStore.load(null);
(1)產生隨機的RSA Key
產生RSA Key會使用到KeyPairGenerator:
其中KeyPairGeneratorSpec在Api 23以上已經Deprecated了;
Api level 23以上改使用KeyGenParameterSpec;
1 private void genKeyStoreKey(Context context) throws Exception { 2 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 3 generateRSAKey_AboveApi23(); 4 } else { 5 generateRSAKey_BelowApi23(context); 6 } 7 }
api23 以上使用 KeyGenParameterSpec:
1 @RequiresApi(api = Build.VERSION_CODES.M) 2 private void generateRSAKey_AboveApi23() throws Exception { 3 KeyPairGenerator keyPairGenerator = KeyPairGenerator 4 .getInstance(KeyProperties.KEY_ALGORITHM_RSA, KEYSTORE_PROVIDER); 5 6 7 KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec 8 .Builder(KEYSTORE_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) 9 .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512) 10 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) 11 .build(); 12 13 keyPairGenerator.initialize(keyGenParameterSpec); 14 keyPairGenerator.generateKeyPair(); 15 16 }
api23 如下使用 KeyPairGeneratorSpec:
1 private void generateRSAKey_BelowApi23(Context context) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException { 2 Calendar start = Calendar.getInstance(); 3 Calendar end = Calendar.getInstance(); 4 end.add(Calendar.YEAR, 30); 5 6 KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context) 7 .setAlias(KEYSTORE_ALIAS) 8 .setSubject(new X500Principal("CN=" + KEYSTORE_ALIAS)) 9 .setSerialNumber(BigInteger.TEN) 10 .setStartDate(start.getTime()) 11 .setEndDate(end.getTime()) 12 .build(); 13 14 KeyPairGenerator keyPairGenerator = KeyPairGenerator 15 .getInstance(KeyProperties.KEY_ALGORITHM_RSA, KEYSTORE_PROVIDER); 16 17 keyPairGenerator.initialize(spec); 18 keyPairGenerator.generateKeyPair(); 19 }
(2)產生AES Key後, 並用RSA Public Key加密後存入SharedPrefs
1 private void genAESKey() throws Exception { 2 // Generate AES-Key 3 byte[] aesKey = new byte[16]; 4 SecureRandom secureRandom = new SecureRandom(); 5 secureRandom.nextBytes(aesKey); 6 7 // Generate 12 bytes iv then save to SharedPrefs 8 byte[] generated = secureRandom.generateSeed(12); 9 String iv = Base64.encodeToString(generated, Base64.DEFAULT); 10 prefsHelper.setIV(iv); 12 13 // Encrypt AES-Key with RSA Public Key then save to SharedPrefs 14 String encryptAESKey = encryptRSA(aesKey); 15 prefsHelper.setAESKey(encryptAESKey); 16 }
1] 加密存儲:使用RSA Public Key 加密 AES Key,存入緩存中。
2] 解密使用:使用RSA Private Key 解密 獲得 AES Key。
1 private String encryptRSA(byte[] plainText) throws Exception { 2 PublicKey publicKey = keyStore.getCertificate(KEYSTORE_ALIAS).getPublicKey(); 3 4 Cipher cipher = Cipher.getInstance(RSA_MODE); 5 cipher.init(Cipher.ENCRYPT_MODE, publicKey); 6 7 byte[] encryptedByte = cipher.doFinal(plainText); 8 return Base64.encodeToString(encryptedByte, Base64.DEFAULT); 9 } 11 12 private byte[] decryptRSA(String encryptedText) throws Exception { 13 PrivateKey privateKey = (PrivateKey) keyStore.getKey(KEYSTORE_ALIAS, null); 14 15 Cipher cipher = Cipher.getInstance(RSA_MODE); 16 cipher.init(Cipher.DECRYPT_MODE, privateKey); 17 18 byte[] encryptedBytes = Base64.decode(encryptedText, Base64.DEFAULT); 19 byte[] decryptedBytes = cipher.doFinal(encryptedBytes); 20 21 return decryptedBytes; 22 }
獲取AES :
1 private SecretKeySpec getAESKey() throws Exception {
2 String encryptedKey = prefsHelper.getAESKey(); 3 byte[] aesKey = decryptRSA(encryptedKey); 4 5 return new SecretKeySpec(aesKey, AES_MODE); 6 }
再使用AES 加解密內容:
對於:Cipher 初始化
1 //實例化加密類,參數爲加密方式,要寫全 2 Cipher cipher = Cipher.getIntance("AES/CBC/PKCS5Padding"); 3 4 //初始化,此方法能夠採用三種方式,按服務器要求來添加。 5 //(1)無第三個參數 6 //(2)第三個參數爲SecureRandom random = new SecureRandom(); 7 // 中random對象,隨機數。(AES不可採用這種方法) 8 //(3)第三個參數:IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes); 9 cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec/random);
具體使用:
1 /** 2 * AES Encryption 3 * @param plainText: A string which needs to be encrypted. 4 * @return A base64's string after encrypting. 5 */ 6 private String encryptAES(String plainText) throws Exception { 7 Cipher cipher = Cipher.getInstance(AES_MODE); 8 cipher.init(Cipher.ENCRYPT_MODE, getAESKey(), new IvParameterSpec(getIV())); 9 10 // 加密過後的byte 11 byte[] encryptedBytes = cipher.doFinal(plainText.getBytes()); 12 13 // 將byte轉為base64的string編碼 14 return Base64.encodeToString(encryptedBytes, Base64.DEFAULT); 15 } 17 18 private String decryptAES(String encryptedText) throws Exception { 19 // 將加密過後的Base64編碼格式 解碼成 byte 20 byte[] decodedBytes = Base64.decode(encryptedText.getBytes(), Base64.DEFAULT); 21 22 // 將解碼過後的byte 使用AES解密 23 Cipher cipher = Cipher.getInstance(AES_MODE); 24 cipher.init(Cipher.DECRYPT_MODE, getAESKey(), new IvParameterSpec(getIV())); 25 26 return new String(cipher.doFinal(decodedBytes)); 27 }
iv 初始化向量
1 private byte[] getIV() { 2 String prefIV = prefsHelper.getIV(); 3 return Base64.decode(prefIV, Base64.DEFAULT); 4 }
關於RSA:
使用RSA加解密時,在較低版本的手機上可能沒法選擇OAEP(最優非對稱加密填充,RSA的加密解密是基於OAEP的)這個模式;
所以能夠改使用RSA_PKCS1_PADDING模式,使用這個模式的話,輸入必須比RSA的Key至少11個字節,若是須要被加密的字串過長的話,能夠在產生Key時指定Key Size長度,或是將字串分段加密。
以預設Key Size = 2048bit(256byte)來說,輸入最長只能到256–11=245byte,我們能夠透過setKeySize(int keySize)指定Key的長度,可是Key Size越大,加解密時速度就越慢。
1 KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec 2 .Builder(KEYSTORE_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) 3 .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512) 4 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) 5 .setKeySize(4096) 6 .build();
引言:
RSA Supported sizes: 512, 768, 1024, 2048, 3072, 4096
Where is the best place to store a password in your Android app?