zoukankan      html  css  js  c++  java
  • Android Keystore 对称-非对称加密

    Android数据加密:

    Anroid数据加密方式

    Android 提供了 KeyStore 等可以长期存储和检索加密密钥的机制,Android KeyStore 系统特别适合于存储加密密钥。

    “AndroidKeyStore” 是 KeyStore 的一个子集,存进 AndroidKeyStore 的 key 将受到签名保护,并且这些 key 是存在系统里的,而不是在 App 的 data 目录下,依托于硬件的 KeyChain 存储,可以做到 private key 一旦存入就无法取出,

    每个 App 自己创建的 key,别的应用是访问不到的。

    它提供了限制何时以何种方式使用密钥的方法,比如使用密钥时需要用户认证或限制密钥只能在加密模式下使用。

    一个应用程式只能编辑、保存、取出自己的密钥。

    App可以生成或者接收一个公私密钥对,并存储在Android的Keystore系统中。公钥可以用于在应用数据放置到特定文件夹前对数据进行加密,私钥可以在需要的时候解密相应的数据。

    作用:

    KeyStore 适用于生成和存储密钥,这些密钥可以用来加密运行时获取到的数据,比如运行时,用户输入的密码,或者服务端传下来的 token。

    操作方式

    建议做法

    1. 使用对称式加解密,但只能在Api Level 23+使用

    对称式加解密(AES)速度较快,但是对称式的Key若要存在KeyStore裡,Api level一定要在23以上才支持,23以下是无法存入KeyStore的,非对称式的Key則不在此限。

    2. 想兼容各Api版本(23以下也能用)

    • 若要存取的東西不多、字串長度也不長:直接使用非对称式加解密即可
    • 若要存取的東西很多或字串長度很長:由於非对称式加解密速度较慢,使用非对称式+对称式加解密可以解決此問題。

    考慮到加解密效能、版本兼容,下面會介紹用非对称式+对称式來加解密。

     

    KeyStore非对称+对称式加解密流程

    1. 使用KeyStore产生随机的RSA Key;
    2. 产生AES Key,并用RSA Public Key加密后存入SharedPrefs;
    3. 从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

    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

    RSA PKCS1 填充方式

    Where is the best place to store a password in your Android app?

    Securely Storing Secrets in an Android Application

    區塊加密法工作模式

  • 相关阅读:
    Server Tomcat v8.0 Server at localhost was unable to start within 45 seconds. If the server requires more time, try increasing the timeout in the server editor.
    用户画像——“打标签”
    python replace函数替换无效问题
    python向mysql插入数据一直报TypeError: must be real number,not str
    《亿级用户下的新浪微博平台架构》读后感
    【2-10】标准 2 维表问题
    【2-8】集合划分问题(给定要分成几个集合)
    【2-7】集合划分问题
    【2-6】排列的字典序问题
    【2-5】有重复元素的排列问题
  • 原文地址:https://www.cnblogs.com/CharlesGrant/p/8378854.html
Copyright © 2011-2022 走看看