使用Android Keystore进行加解密

https://www.jianshu.com/p/06775ddf435f

国内使用Android Keystore加解密的应该很少吧,搜出来也基本都是Android打包时的Keystore,其实谷歌在很早之前就已经为Android提供了类似IOS的KeyChain功能,私钥存储在trustzone系统中,这个trustzone系统独立于Android系统,能做到私钥安全。
具体怎么安全,我们来了解一下加解密与签名的过程,本文不做复杂的深度解析,普通人也完全不需要了解这么透彻,想深入了解的可以google trustzon。
keystore加解密与签名的安全性其实很好理解,因为不管是应用还是Android系统本身都无法访问私钥,只能选择创建、删除与使用,从根源角度来杜绝私钥泄露的可能。当然因为google设计问题,早期安全性还是有一些问题的,建议直接从android6.0开始使用,android4.4与5.0也能用只是需要使用早期的KeyPairGeneratorSpec来创建,此类在android6.0已经被废弃。
说了那么多,可能有些人还是一头雾水,那么罗列一下android keystore的用处吧:

  1. 可以安全的加密本地数据,方便想安全存储但是不知道私钥怎么存放的用户(apk不管如何加固都存在被破解风险)
  2. 可以结合指纹进行指纹授权解密
  3. 在区块链行业加解密签名等尤为显著,此功能要比ios的安全区好用很多(据我所知,应用卸载,ios安全区的内容还在,这毫无安全可言)
    在使用过程中,我也遇到了几个问题,这里需要注意一下,不然一不小心就被坑了:
  4. keystore加解密是非线程安全的,所以一定要加锁(被网上的网友坑了一把,使用了别人提供的util工具,简直日了狗,毁人不倦)
  5. 不要轻易删除keystore,否则加密过的数据会面临无法解密的风险
  6. 私钥创建初始化耗时比较久,尽量一个私钥重复使用(此耗时和手机性能几乎无关,速度取决于trustzone的系统)
    好了,到了贴代码时间:

/**
 * @date 2019/11/12
 * @since 2.0
 * description:
 */

import android.security.KeyPairGeneratorSpec;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Base64;

import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Calendar;
import java.util.GregorianCalendar;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.security.auth.x500.X500Principal;

/**
 * 使用KeyStore+AES对称加密的方式
 *
 * 通过AES加密方式,用KeyGenerator生成秘钥,保存在Android Keystore中
 * 对数据进行加解密
 * <p>
 * 1、创建秘钥,保存在AndroidKeystore里面,秘钥别名为alias
 * 2、创建并初始化cipher对象,获取秘钥,对数据进行加解密
 *
 * https://blog.csdn.net/hpp_1225/article/details/83786134
 */

public class AESKeystoreUtils {

  //  private static final String ALIAS = "123";
    //  算法/模式/补码方式
    private static String TRANSFORMATION = "AES/GCM/NoPadding";
    private static byte[] encryptIv;

    /**
     * 创建秘钥
     */
    private static void createKey(String alias) {
        //获取Android KeyGenerator的实例
        //设置使用KeyGenerator的生成的密钥加密算法是AES,在 AndroidKeyStore 中保存密钥/数据
        final KeyGenerator keyGenerator;
        AlgorithmParameterSpec spec = null;
        try {
            keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
            //使用KeyGenParameterSpec.Builder 创建KeyGenParameterSpec ,传递给KeyGenerators的init方法
            //KeyGenParameterSpec 是生成的密钥的参数
            //setBlockMode保证了只有指定的block模式下可以加密,解密数据,如果使用其它的block模式,将会被拒绝。
            //使用了“AES/GCM/NoPadding”变换算法,还需要设置KeyGenParameterSpec的padding类型
            //创建一个开始和结束时间,有效范围内的密钥对才会生成。
            Calendar start = new GregorianCalendar();
            Calendar end = new GregorianCalendar();
            end.add(Calendar.YEAR, 10);//往后加十年

            //todo 高于6.0才可以使用KeyGenParameterSpec 来生成秘钥,低版本呢?
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
                spec = new KeyGenParameterSpec.Builder(alias,
                        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                        .setCertificateNotBefore(start.getTime())
                        .setCertificateNotAfter(end.getTime())
                        .build();
            } else {
                //android6.0以下谷歌还不支持keystore的对称加解密?
//                spec = new ;

            }

            keyGenerator.init(spec);
            keyGenerator.generateKey();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchProviderException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        }
    }


    public static String encryptData(String needEncrypt,String alias) {

        if (!isHaveKeyStore(alias)) {
            createKey(alias);
        }

        try {
            KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
            keyStore.load(null);

            KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry) keyStore.getEntry(alias, null);

            SecretKey secretKey = secretKeyEntry.getSecretKey();

            //KeyGenParameterSpecs中设置的block模式是KeyProperties.BLOCK_MODE_GCM,所以这里只能使用这个模式解密数据。
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
            //ciphers initialization vector (IV)的引用,用于解密
            encryptIv = cipher.getIV();
            return Base64.encodeToString(cipher.doFinal(needEncrypt.getBytes()), Base64.NO_WRAP);
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (UnrecoverableEntryException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (NullPointerException e) {
            e.printStackTrace();
            return "空指针异常";
        }
        return "";
    }

    public static String decryptData(String needDecrypt,String alias) {
        if (!isHaveKeyStore(alias)) {
            createKey(alias);
        }

        try {
            KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
            keyStore.load(null);

            KeyStore.SecretKeyEntry secretKeyEntry = (KeyStore.SecretKeyEntry) keyStore.getEntry(alias, null);

            SecretKey secretKey = secretKeyEntry.getSecretKey();

            //KeyGenParameterSpecs中设置的block模式是KeyProperties.BLOCK_MODE_GCM,所以这里只能使用这个模式解密数据。
            Cipher cipher = Cipher.getInstance(TRANSFORMATION);
            //需要为GCMParameterSpec 指定一个认证标签长度(可以是128、120、112、104、96这个例子中我们能使用最大的128),
            // 并且用到之前的加密过程中用到的IV。
            GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, encryptIv);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmParameterSpec);
            return new String(cipher.doFinal(Base64.decode(needDecrypt, Base64.NO_WRAP)));

        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnrecoverableEntryException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        }
        return "";
    }

    public static void clearKeystore(String alias) {
        try {
            KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
            keyStore.load(null);
            keyStore.deleteEntry(alias);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 是否创建过秘钥
     *
     * @return
     */
    private static boolean isHaveKeyStore(String alias) {
        try {
            KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
            keyStore.load(null);

            KeyStore.Entry keyentry = keyStore.getEntry(alias, null);
            if (null != keyentry) {
                return true;
            }

        } catch (KeyStoreException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (CertificateException e) {
            e.printStackTrace();
        } catch (UnrecoverableEntryException e) {
            e.printStackTrace();
        }
        return false;
    }
}
import android.content.Context;
import android.os.Build;
import android.security.KeyPairGeneratorSpec;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;

import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Calendar;

import javax.crypto.Cipher;
import javax.security.auth.x500.X500Principal;

/**
 * @date 2019/11/12
 * @since 2.0
 * description: 使用KeyStore+RSA非对称加密的方式
 * https://www.jianshu.com/p/06775ddf435f
 */
public class EncryptSafeUtil {

    private static final String TAG = "EncryptSafeUtil";
    private static EncryptSafeUtil encryptSafeUtilInstance = new EncryptSafeUtil();

    private KeyStore keyStore;

    private Context context;
    // 单位年
    private final int maxExpiredTime = 1000;

    private String x500PrincipalName = "CN=MyKey, O=Android Authority";

    // RSA有加密字符长度限制,所以需要分段加密
    private int rsaEncryptBlock = 244;
    private int rsaDecryptBlock = 256;

    private EncryptSafeUtil() {
    }

    public static EncryptSafeUtil getInstance() {
        return encryptSafeUtilInstance;
    }

    public void init(Context context, String x500PrincipalName) {
        this.context = context;
        this.x500PrincipalName = x500PrincipalName;
    }

    public void initKeyStore(String alias) {
        synchronized (EncryptSafeUtil.class) {
            try {
                if (null == keyStore) {
                    keyStore = KeyStore.getInstance("AndroidKeyStore");
                    keyStore.load(null);
                }
                createNewKeys(alias);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void createNewKeys(String alias) {
        if (TextUtils.isEmpty(alias)) {
            return;
        }
        try {
            if (keyStore.containsAlias(alias)) {
                return;
            }
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                // Create new key
                Calendar start = Calendar.getInstance();
                Calendar end = Calendar.getInstance();
                end.add(Calendar.YEAR, maxExpiredTime);
                KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context)
                        .setAlias(alias)
                        .setSubject(new X500Principal(x500PrincipalName))
                        .setSerialNumber(BigInteger.ONE)
                        .setStartDate(start.getTime())
                        .setEndDate(end.getTime())
                        .build();
                KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
                generator.initialize(spec);
                generator.generateKeyPair();//使用AndroidKeyStore生成密钥对
            } else {
                KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
                keyPairGenerator.initialize(
                        new KeyGenParameterSpec.Builder(
                                alias,
                                KeyProperties.PURPOSE_DECRYPT)
                                .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
                                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
                                .setUserAuthenticationRequired(false)
                                .build());
                keyPairGenerator.generateKeyPair();//使用AndroidKeyStore生成密钥对
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void clearKeystore(String alias) {
        try {
            keyStore = KeyStore.getInstance("AndroidKeyStore");
            keyStore.load(null);
            keyStore.deleteEntry(alias);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * RSA加密方法
     *
     * @param needEncryptWord  需要加密的字符串
     * @param alias            加密秘钥
     * @return
     */
    public String encryptString(String needEncryptWord, String alias) {
        if (TextUtils.isEmpty(needEncryptWord) || TextUtils.isEmpty(alias)) {
            return "";
        }
        String encryptStr = "";
        synchronized (EncryptSafeUtil.class) {
            initKeyStore(alias);
            try {
                PublicKey publicKey = keyStore.getCertificate(alias).getPublicKey();
                Cipher inCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
                inCipher.init(Cipher.ENCRYPT_MODE, publicKey);

                ByteArrayOutputStream out = new ByteArrayOutputStream();
                int offSet = 0;
                int inputLen = needEncryptWord.length();
                byte[] inputData = needEncryptWord.getBytes();
                for (int i = 0; inputLen - offSet > 0; offSet = i * rsaEncryptBlock) {
                    byte[] cache;
                    if (inputLen - offSet > rsaEncryptBlock) {
                        cache = inCipher.doFinal(inputData, offSet, rsaEncryptBlock);
                    } else {
                        cache = inCipher.doFinal(inputData, offSet, inputLen - offSet);//RSA每块加密后的密文长度都是256
                    }
                    out.write(cache, 0, cache.length);
                    ++i;
                }
                byte[] encryptedData = out.toByteArray();
                out.close();

                encryptStr = Base64.encodeToString(encryptedData, Base64.URL_SAFE);
            } catch (Exception e) {
                e.printStackTrace();
                Log.e(TAG, "in encryptString error:" + e.getMessage());
            }
        }
        return encryptStr;
    }


    /**
     * RSA解密方法
     *
     * @param needDecryptWord 需要解密的字符串
     * @param alias           key的别称
     * @return
     */
    public String decryptString(String needDecryptWord, String alias) {
        if (TextUtils.isEmpty(needDecryptWord) || TextUtils.isEmpty(alias)) {
            return "";
        }
        String decryptStr = "";
        synchronized (EncryptSafeUtil.class) {
            initKeyStore(alias);
            try {
                PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, null);
                Cipher outCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
                outCipher.init(Cipher.DECRYPT_MODE, privateKey);

                ByteArrayOutputStream out = new ByteArrayOutputStream();
                int offSet = 0;
                byte[] encryptedData = Base64.decode(needDecryptWord, Base64.URL_SAFE);//RSA密文一般都是256的倍数
                int inputLen = encryptedData.length;
                for (int i = 0; inputLen - offSet > 0; offSet = i * rsaDecryptBlock) {
                    byte[] cache;
                    if (inputLen - offSet > rsaDecryptBlock) {
                        cache = outCipher.doFinal(encryptedData, offSet, rsaDecryptBlock);
                    } else {
                        cache = outCipher.doFinal(encryptedData, offSet, inputLen - offSet);
                    }
                    out.write(cache, 0, cache.length);
                    ++i;
                }
                byte[] decryptedData = out.toByteArray();
                out.close();

                decryptStr = new String(decryptedData, 0, decryptedData.length, "UTF-8");
            } catch (Exception e) {
                e.printStackTrace();
                Log.e(TAG, "in decryptString error:" + e.getLocalizedMessage());
            }
        }
        return decryptStr;
    }
}

import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;

import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.Random;

import static org.junit.Assert.*;

/**
 * Instrumented test, which will execute on an Android device.
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {


    public static String getRandomString(int DataLen) {
        int len=DataLen /2;
        if(DataLen %2 !=0)
        {
            len=len+1;
        }
        byte [] buffer=new byte[len];
        Random random=new Random();
        random.nextBytes(buffer);
        String line=byte2Hex(buffer);
        if(DataLen %2 !=0)
        {
            line=line.substring(0, line.length()-1);
        }
        return line;
    }
    public static String byte2Hex(byte[] bytes){
        StringBuffer stringBuffer = new StringBuffer();
        String temp = null;
        for (int i=0;i<bytes.length;i++){
            temp = Integer.toHexString(bytes[i] & 0xFF).toUpperCase();
            if (temp.length()==1){
                //1得到一位的进行补0操作
                stringBuffer.append("0");
            }
            stringBuffer.append(temp);
        }
        return stringBuffer.toString();
    }


    @Test
    public void testRSAEncrypt() {
        Context appContext = InstrumentationRegistry.getTargetContext();
        System.out.println("run ok");

        {
            String data = "from android";
            String alias = "yunshouhu";
            Log.i(MainActivity.TAG, "data=" + data);
            String cipherText = EncryptSafeUtil.getInstance().encryptString(data, alias);
            Log.i(MainActivity.TAG, "cipherText=" + cipherText);
            String text = EncryptSafeUtil.getInstance().decryptString(cipherText, alias);
            Log.i(MainActivity.TAG, "text=" + text);
            EncryptSafeUtil.getInstance().clearKeystore(alias);
        }
        {
            {
                String data = getRandomString(550)+"中文";
                Log.i(MainActivity.TAG, "data=" + data);
                String alias = "yunshouhu";
                String cipherText = EncryptSafeUtil.getInstance().encryptString(data, alias);
                Log.i(MainActivity.TAG, "cipherText=" + cipherText);
                String text = EncryptSafeUtil.getInstance().decryptString(cipherText, alias);
                Log.i(MainActivity.TAG, "text=" + text);
                EncryptSafeUtil.getInstance().clearKeystore(alias);
            }
        }
    }

        @Test
        public void testAESEncrypt() {
            Context appContext = InstrumentationRegistry.getTargetContext();
            System.out.println("run ok");

            {
                String data="from android你好";
                String alias="yunshouhu002";
                String cipherText=AESKeystoreUtils.encryptData(data,alias);
                Log.i(MainActivity.TAG,"cipherText="+cipherText);
                String text=AESKeystoreUtils.decryptData(cipherText,alias);
                Log.i(MainActivity.TAG,"text="+text);
                AESKeystoreUtils.clearKeystore(alias);
            }
            {
                {
                    String data="from android你好-test";
                    String alias="yunshouhu002";
                    String cipherText=AESKeystoreUtils.encryptData(data,alias);
                    Log.i(MainActivity.TAG,"cipherText="+cipherText);
                    String text=AESKeystoreUtils.decryptData(cipherText,alias);
                    Log.i(MainActivity.TAG,"text="+text);
                    AESKeystoreUtils.clearKeystore(alias);
                }
            }

        }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值