Android Keystore 对称-非对称加密
2018-01-30 10:32
411 查看
Android 提供了 KeyStore 等可以长期存储和检索加密密钥的机制,Android KeyStore 系统特别适合于存储加密密钥。
“AndroidKeyStore” 是 KeyStore 的一个子集,存进 AndroidKeyStore 的 key 将受到签名保护,并且这些 key 是存在系统里的,而不是在 App 的 data 目录下,依托于硬件的 KeyChain 存储,可以做到 private key 一旦存入就无法取出,
每个 App 自己创建的 key,别的应用是访问不到的。
它提供了限制何时以何种方式使用密钥的方法,比如使用密钥时需要用户认证或限制密钥只能在加密模式下使用。
一个应用程式只能编辑、保存、取出自己的密钥。
App可以生成或者接收一个公私密钥对,并存储在Android的Keystore系统中。公钥可以用于在应用数据放置到特定文件夹前对数据进行加密,私钥可以在需要的时候解密相应的数据。
作用:KeyStore 适用于生成和存储密钥,这些密钥可以用来加密运行时获取到的数据,比如运行时,用户输入的密码,或者服务端传下来的 token。
操作方式
存:使用Keystore 加密信息后存入SharedPreferences取:从SharedPreferences取出信息並使用Keystore 解密
建议做法
1. 使用对称式加解密,但只能在Api Level 23+使用
对称式加解密(AES)速度较快,但是对称式的Key若要存在KeyStore裡,Api level一定要在23以上才支持,23以下是无法存入KeyStore的,非对称式的Key則不在此限。2. 想兼容各Api版本(23以下也能用)
若要存取的東西不多、字串長度也不長:直接使用非对称式加解密即可若要存取的東西很多或字串長度很長:由於非对称式加解密速度较慢,使用非对称式+对称式加解密可以解決此問題。
考慮到加解密效能、版本兼容,下面會介紹用非对称式+对称式來加解密。
KeyStore非对称+对称式加解密流程
使用KeyStore产生随机的RSA Key;产生AES Key,并用RSA Public Key加密后存入SharedPrefs;
从SharedPrefs取出AES Key,並用RSA Private Key解密,用這把AES Key來加解密信息;
主流的加密方式有:(对称加密)AES、DES (非对称加密)RSA、DSA
工作模式:
DES一共有:
电子密码本模式(ECB)、加密分组链接模式(CBC)、加密反馈模式(CFB)、输出反馈模式(OFB);
AES一共有:
电子密码本模式(ECB)、加密分组链接模式(CBC)、加密反馈模式(CFB)、输出反馈模式(OFB)、计数器模式(CTR),伽罗瓦计数器模式(GCM)
PKCS5Padding是填充模式,还有其它的填充模式;
对于初始化向量iv: 初始化向量参数,AES 为16bytes. DES 为8bytes
private static final String KEYSTORE_PROVIDER = "AndroidKeyStore"; private static final String AES_MODE = "AES/GCM/NoPadding"; private static final String RSA_MODE = "RSA/ECB/PKCS1Padding"; private static final String KEYSTORE_ALIAS = "KEYSTORE_DEMO"; KeyStore mKeyStore = KeyStore.getInstance(KEYSTORE_PROVIDER); mKeyStore.load(null);
(1)产生随机的RSA Key
产生RSA Key会使用到KeyPairGenerator:
其中KeyPairGeneratorSpec在Api 23以上已經Deprecated了;
Api level 23以上改使用KeyGenParameterSpec;
private void genKeyStoreKey(Context context) throws Exception { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { generateRSAKey_AboveApi23(); } else { generateRSAKey_BelowApi23(context); } }
api23 以上使用 KeyGenParameterSpec:
@RequiresApi(api = Build.VERSION_CODES.M) private void generateRSAKey_AboveApi23() throws Exception { KeyPairGenerator keyPairGenerator = KeyPairGenerator .getInstance(KeyProperties.KEY_ALGORITHM_RSA, KEYSTORE_PROVIDER); KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec .Builder(KEYSTORE_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) .build(); keyPairGenerator.initialize(keyGenParameterSpec); keyPairGenerator.generateKeyPair(); }
api23 以下使用 KeyPairGeneratorSpec:
private void generateRSAKey_BelowApi23(Context context) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException { Calendar start = Calendar.getInstance(); Calendar end = Calendar.getInstance(); end.add(Calendar.YEAR, 30); KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context) .setAlias(KEYSTORE_ALIAS) .setSubject(new X500Principal("CN=" + KEYSTORE_ALIAS)) .setSerialNumber(BigInteger.TEN) .setStartDate(start.getTime()) .setEndDate(end.getTime()) .build(); KeyPairGenerator keyPairGenerator = KeyPairGenerator .getInstance(KeyProperties.KEY_ALGORITHM_RSA, KEYSTORE_PROVIDER); keyPairGenerator.initialize(spec); keyPairGenerator.generateKeyPair(); }
(2)产生AES Key后, 并用RSA Public Key加密后存入SharedPrefs
private void genAESKey() throws Exception { // Generate AES-Key byte[] aesKey = new byte[16]; SecureRandom secureRandom = new SecureRandom(); secureRandom.nextBytes(aesKey); // Generate 12 bytes iv then save to SharedPrefs byte[] generated = secureRandom.generateSeed(12); String iv = Base64.encodeToString(generated, Base64.DEFAULT); prefsHelper.setIV(iv); // Encrypt AES-Key with RSA Public Key then save to SharedPrefs String encryptAESKey = encryptRSA(aesKey); prefsHelper.setAESKey(encryptAESKey); }
1] 加密存储:使用RSA Public Key 加密 AES Key,存入缓存中。
2] 解密使用:使用RSA Private Key 解密 得到 AES Key。
private String encryptRSA(byte[] plainText) throws Exception { PublicKey publicKey = keyStore.getCertificate(KEYSTORE_ALIAS).getPublicKey(); Cipher cipher = Cipher.getInstance(RSA_MODE); cipher.init(Cipher.ENCRYPT_MODE, publicKey); byte[] encryptedByte = cipher.doFinal(plainText); return Base64.encodeToString(encryptedByte, Base64.DEFAULT); } private byte[] decryptRSA(String encryptedText) throws Exception { PrivateKey privateKey = (PrivateKey) keyStore.getKey(KEYSTORE_ALIAS, null); Cipher cipher = Cipher.getInstance(RSA_MODE); cipher.init(Cipher.DECRYPT_MODE, privateKey); byte[] encryptedBytes = Base64.decode(encryptedText, Base64.DEFAULT); byte[] decryptedBytes = cipher.doFinal(encryptedBytes); return decryptedBytes; }
获取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 初始化
//实例化加密类,参数为加密方式,要写全 Cipher cipher = Cipher.getIntance("AES/CBC/PKCS5Padding"); //初始化,此方法可以采用三种方式,按服务器要求来添加。 //(1)无第三个参数 //(2)第三个参数为SecureRandom random = new SecureRandom(); // 中random对象,随机数。(AES不可采用这种方法) //(3)第三个参数:IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes); cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec/random);
具体使用:
/** * AES Encryption * @param plainText: A string which needs to be encrypted. * @return A base64's string after encrypting. */ private String encryptAES(String plainText) throws Exception { Cipher cipher = Cipher.getInstance(AES_MODE); cipher.init(Cipher.ENCRYPT_MODE, getAESKey(), new IvParameterSpec(getIV())); // 加密過後的byte byte[] encryptedBytes = cipher.doFinal(plainText.getBytes()); // 將byte轉為base64的string編碼 return Base64.encodeToString(encryptedBytes, Base64.DEFAULT); } private String decryptAES(String encryptedText) throws Exception { // 將加密過後的Base64編碼格式 解碼成 byte byte[] decodedBytes = Base64.decode(encryptedText.getBytes(), Base64.DEFAULT); // 將解碼過後的byte 使用AES解密 Cipher cipher = Cipher.getInstance(AES_MODE); cipher.init(Cipher.DECRYPT_MODE, getAESKey(), new IvParameterSpec(getIV())); return new String(cipher.doFinal(decodedBytes)); }
iv 初始化向量
private byte[] getIV() { String prefIV = prefsHelper.getIV(); return Base64.decode(prefIV, Base64.DEFAULT); }
关于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越大,加解密时速度就越慢。
KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec .Builder(KEYSTORE_ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) .setKeySize(4096) .build();
引言:
RSA Supported sizes: 512, 768, 1024, 2048, 3072, 4096
RSA PKCS1 填充方式
Where is the best place to store a password in your Android app?
Securely Storing Secrets in an Android Application
區塊加密法工作模式
相关文章推荐
- Android: AndroidKeyStore 非对称RSA加密解密
- Android 中 非对称(RSA)加密和对称(AES)加密
- Android 中 非对称(RSA)加密和对称(AES)加密
- Android 采用keyStore的方式非对称加密,配合Rxjava2更好用
- Android 中 非对称(RSA)加密和对称(AES)加密
- [置顶] Android 中 非对称(RSA)加密和对称(AES)加密
- Java安全之对称加密、非对称加密、数字签名
- [svc]openssl对称非对称加密实战
- Java安全之对称加密、非对称加密、数字签名
- 通过代理截取并修改非对称密钥加密信息 加密、解密算法总的来说可以分称对称密钥加密以及非对称密钥加密算法。 对称密钥加密算法要求加密和解密都用同一把密钥。这可能是对称密码体制的主要弱点(为了让对方能够
- 密码学之可逆加密 杂谈(对称、非对称)
- Md5与对称非对称加密的比较区别
- Java安全之对称加密、非对称加密、数字签名
- Android安全加密:非对称加密
- 数据传输加密:非对称加密算法RSA+对称算法AES(适用于java,android和Web)
- java对称加为密DESede 与 非对称加密RSA 示例
- 从https的实现看数字证书、SSL、数字签名、摘要算法、对称/非对称加密
- Java进阶(八)Java加密技术之对称加密 非对称加密 不可逆加密算法
- 两种常用的加密方式--对称与非对称