美文网首页
Android中的AES加密--上

Android中的AES加密--上

作者: g小志 | 来源:发表于2020-06-16 20:14 被阅读0次

    前言

    最近需要一个加密一下用户信息,想到用到AES,加密,没想到苦难重重。

    第一版

    随便上晚上找了一下代码如下:

      //偏移量
        public static final String VIPARA = "1234567876543210";   //AES 为16bytes. DES 为8bytes
        //编码方式
        public static final String CODE_TYPE = "UTF-8";
        //填充类型
        public static final String AES_TYPE = "AES/ECB/PKCS5Padding";
        //私钥
        private static final String AES_KEY="1111222233334444";   //AES固定格式为128/192/256 bits.即:16/24/32bytes。DES固定格式为128bits,即8bytes。
    
    
        /**
         * 加密
         *
         * @param cleartext
         * @return
         */
        public static String encrypt(String cleartext) {
            try {
                //两个参数,第一个为私钥字节数组, 第二个为加密方式 AES或者DES
                SecretKeySpec key = new SecretKeySpec(AES_KEY.getBytes(), "AES");
                //实例化加密类,参数为加密方式,要写全
                Cipher cipher = Cipher.getInstance(AES_TYPE); //PKCS5Padding比PKCS7Padding效率高,PKCS7Padding可支持IOS加解密
                //初始化,此方法可以采用三种方式,按加密算法要求来添加。(1)无第三个参数(2)第三个参数为SecureRandom random = new SecureRandom();中random对象,随机数。(AES不可采用这种方法)(3)采用此代码中的IVParameterSpec
                //加密时使用:ENCRYPT_MODE;  解密时使用:DECRYPT_MODE;
                cipher.init(Cipher.ENCRYPT_MODE, key); //CBC类型的可以在第三个参数传递偏移量zeroIv,ECB没有偏移量
                //加密操作,返回加密后的字节数组,然后需要编码。主要编解码方式有Base64, HEX, UUE,7bit等等。此处看服务器需要什么编码方式
                byte[] encryptedData = cipher.doFinal(cleartext.getBytes(CODE_TYPE));
    
                return Base64.encodeToString(encryptedData,Base64.DEFAULT);
            } catch (Exception e) {
                e.printStackTrace();
                return "";
            }
        }
    
        /**
         * 解密
         *
         * @param encrypted
         * @return
         */
        public static String decrypt(String encrypted) {
            try {
                byte[] byteMi = Base64.decode(encrypted,Base64.DEFAULT);
                SecretKeySpec key = new SecretKeySpec(
                        AES_KEY.getBytes(), "AES");
                Cipher cipher = Cipher.getInstance(AES_TYPE);
                //与加密时不同MODE:Cipher.DECRYPT_MODE
                cipher.init(Cipher.DECRYPT_MODE, key);  //CBC类型的可以在第三个参数传递偏移量zeroIv,ECB没有偏移量
                byte[] decryptedData = cipher.doFinal(byteMi);
                return new String(decryptedData, CODE_TYPE);
            } catch (Exception e) {
                e.printStackTrace();
                return "";
            }
        }
    

    测试一下,OK,没问题,但是觉得好像哪里不对,我本来是为了安全考虑才加密数据的,结果这样把加密的密钥写在类文件是不是不太合适? 所以,又找了一下看如何安全一点。

    第二版

    先只展示加密,解密道理相同,最后会附上完整代码

     public static String encrypt(String key, String content) {
            try {
                KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
                @SuppressLint("DeletedProvider") SecureRandom secureRandom =SecureRandom.getInstance("SHA1PRNG","Crypto");
                secureRandom.setSeed(key.getBytes());
                keyGenerator.init(128, secureRandom);
    
                SecretKey secretKey = keyGenerator.generateKey();
                byte[] keyEncoded = secretKey.getEncoded();
    
                SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), "AES");
    
                Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
                cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
                byte[] doFinal = cipher.doFinal(content.getBytes(CODE_TYPE));
    
                return Base64.encodeToString(doFinal, Base64.DEFAULT);
            } catch (Exception  e) {
                e.printStackTrace();
            }
            return "error encrypt";
        }
    

    又是再网上找了一段代码,比之前多了下面这几行:

                KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
                @SuppressLint("DeletedProvider") SecureRandom secureRandom =SecureRandom.getInstance("SHA1PRNG","Crypto");
                secureRandom.setSeed(key.getBytes());
                keyGenerator.init(128, secureRandom);
    
                SecretKey secretKey = keyGenerator.generateKey();
                byte[] keyEncoded = secretKey.getEncoded();
    

    简单介绍下新增这几个类的作用:

    • KeyGenerator 密钥生成器,传入AES,说明我们最后要生成的时AES的密钥
    • SecureRandom 安全随机算法,他的作用时将我们的密钥经过一定的算法("SHA1PRNG"强随机算法),并通过"Crypto"安全供应商返回,其实说白了。就是给密钥利用随机算法加盐,使得密钥更安全。
    • 最后返回新的密钥keyEncoded

    问题也时出现再这里AndroidN(API=27),不再支持SHA1PRNG算法的实现以及Crypto这个安全供应商,原因是不安全,也不可靠参考原因

    第三版

    兼容版本

      @SuppressLint("DeletedProvider")
        public static String encrypt(String key, String content) {
            try {
                KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
                SecureRandom secureRandom =null;
                // 在4.2以上版本中,SecureRandom获取方式发生了改变
                int sdk_version = android.os.Build.VERSION.SDK_INT;
                // Android  6.0 以上
                if (sdk_version > 23) {
                    secureRandom = SecureRandom.getInstance("SHA1PRNG", new AesUtil.CryptoProvider());
                    //4.2及以上
                } else if (android.os.Build.VERSION.SDK_INT >= 17) {
    
                    secureRandom = SecureRandom.getInstance("SHA1PRNG", "Crypto");
                } else {
                    secureRandom = SecureRandom.getInstance("SHA1PRNG");
                }
                secureRandom.setSeed(key.getBytes());
                keyGenerator.init(128, secureRandom);
    
                SecretKey secretKey = keyGenerator.generateKey();
                byte[] keyEncoded = secretKey.getEncoded();
    
    //            Log.e(TAG, "keyEncoded: " + keyEncoded.length);
                SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), "AES");
    
                Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
                cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
                byte[] doFinal = cipher.doFinal(content.getBytes(CODE_TYPE));
    
                return Base64.encodeToString(doFinal, Base64.DEFAULT);
            } catch (Exception  e) {
                e.printStackTrace();
            }
            return "error encrypt";
        }
    

    测试后好像更安全了,因为密钥多进行了一次加密。 现在要考虑的问题是,如何保存要是密钥字符串,本地文件好像也不安全,JNI编译后后生成so,单单加密一个用户信息,有点太重了。
    那么放在哪里呢?

    第四版 KeyStore

    这个是Google建议使用的,翻译如下:

    Android的Keystore系统可以把密钥保持在一个难以从设备中取出数据的容器中。
    当密钥保存到Keystore之后,可以在不取出密钥的状态下进行私密操作。
    此外,它提供了限制何时以何种方式使用密钥的方法,比如使用密钥时需要用户认证或限制密钥只能在加密模式下使用

    简单来说就是,我们生成密钥,然后保存再自己手机的内部缓存目录(也就是只有应用自己可见的目录),KeyStore是个系统,一个应用程式只能编辑、保存、取出自己的密钥。这样就大大提升密钥的安全性,再加上之前的代码,问题就解决了。

    具体参考这篇译文Android保存私密信息-强大的keyStore(译)

    源码:

    public class CryptoUtils {
    
        private static final String TAG = "TAG";
        public static final String DEFAULT_SECRETKEY_NAME = "default_secretkey_name";
    
        public static final String STORE_FILE_NAME = "crypto";
        private KeyStore keyStore;
        private CryptoUtils(KeyStore keyStore) {
            this.keyStore = keyStore;
        }
    
        @SuppressWarnings("ResultOfMethodCallIgnored")
        public synchronized static CryptoUtils getInstance(Context context) {
            KeyStore keyStore;
            File file = new File(context.getFilesDir(), STORE_FILE_NAME);
    
            Log.e(TAG, "file path: " + file.getPath());
            try {
    
                keyStore = getKeyStore(file);
    
                initKey(keyStore, file);
    
                CryptoUtils crypto = new CryptoUtils(keyStore);
                return crypto;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
    
        private static void initKey(KeyStore keyStore, File file) throws Exception {
            if (!keyStore.containsAlias(DEFAULT_SECRETKEY_NAME)) { // 秘钥不存在,则生成秘钥
                KeyGenerator keyGenerator = generateKeyGenerator();
                SecretKey secretKey = keyGenerator.generateKey();
    
                storeKey(keyStore, file, secretKey);
            }
        }
    
        private static void storeKey(KeyStore keyStore, File file, SecretKey secretKey) throws Exception {
            if (Build.VERSION.SDK_INT >= 23) {
                keyStore.setKeyEntry(DEFAULT_SECRETKEY_NAME, secretKey, null, null);
            } else {
                keyStore.setKeyEntry(DEFAULT_SECRETKEY_NAME, secretKey, null, null);
                FileOutputStream fos = null;
                try {
                    fos = new FileOutputStream(file);
                    keyStore.store(fos, null);
                    fos.close();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (fos != null) {
                        fos.close();
                    }
                }
            }
        }
    
        private static KeyStore getKeyStore(File file) throws Exception {
            if (Build.VERSION.SDK_INT >= 23) {
                KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
                keyStore.load(null);
                return keyStore;
            } else {
                KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    
                if (!file.exists()) {
                    boolean isSuccess = file.createNewFile();
    
                    if (!isSuccess) {
                        throw new SecurityException("创建内部存储文件失败");
                    }
    
                    keyStore.load(null, null);
                } else if (file.length() <= 0) {
                    keyStore.load(null, null);
                } else {
                    FileInputStream fis = null;
                    try {
                        fis = new FileInputStream(file);
                        keyStore.load(fis, null);
                        fis.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        if (fis != null) {
                            fis.close();
                        }
                    }
                }
                return keyStore;
            }
        }
    
        @SuppressLint("DeletedProvider")
        private static KeyGenerator generateKeyGenerator() throws Exception {
            KeyGenerator keyGenerator;
            if (Build.VERSION.SDK_INT >= 23) {
                keyGenerator = KeyGenerator.getInstance("AES", "AndroidKeyStore");
                keyGenerator.init(new KeyGenParameterSpec.Builder(DEFAULT_SECRETKEY_NAME,
                        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                        .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                        .setUserAuthenticationRequired(false)
                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
                        .build());
            } else {
                keyGenerator = KeyGenerator.getInstance("AES");
                SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG", "Crypto");
                secureRandom.setSeed(generateSeed());
                keyGenerator.init(128, secureRandom);
            }
    
            return keyGenerator;
        }
    
        private static byte[] generateSeed() {
            try {
                ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream();
                DataOutputStream seedBufferOut =
                        new DataOutputStream(seedBuffer);
                seedBufferOut.writeLong(System.currentTimeMillis());
                seedBufferOut.writeLong(System.nanoTime());
                seedBufferOut.writeInt(android.os.Process.myPid());
                seedBufferOut.writeInt(android.os.Process.myUid());
                seedBufferOut.write(Build.BOARD.getBytes());
                return seedBuffer.toByteArray();
            } catch (IOException e) {
                throw new SecurityException("Failed to generate seed", e);
            }
        }
    
        /**
         * AES加密
         *
         * @param content
         * @return
         */
        public EncryptData aesEncrypt(String alias, String content) {
            try {
                SecretKey secretKey = getSecretKey(keyStore);
                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
                cipher.init(Cipher.ENCRYPT_MODE, secretKey);
                byte[] bytes = cipher.doFinal(StringUtils.string2Bytes(content));
                byte[] iv = cipher.getIV();
                String encryptString = Base64.encodeToString(bytes, Base64.NO_WRAP);
                return new EncryptData(alias, encryptString, iv);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
        /**
         * AES解密
         *
         * @param encryptData
         * @return
         */
        public String aesDecrypt(EncryptData encryptData) {
            try {
                SecretKey secretKey = getSecretKey(keyStore);
                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
                cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(encryptData.getIv()));
                byte[] bytes = cipher.doFinal(Base64.decode(encryptData.getEncryptString()
                        , Base64.NO_WRAP));
                return StringUtils.bytes2String(bytes);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
        private static SecretKey getSecretKey(KeyStore keyStore) {
            try {
                return (SecretKey) keyStore.getKey(DEFAULT_SECRETKEY_NAME, null);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    }
    
    

    文章参考 :

    『译文』Security "Crypto" provider deprecated in Android N - Android N中不再支持“Crypto”安全供应商的相关方法

    Android 9.0 加密适配

    Java实现AES加密

    Android KeyStore密钥存储

    Android:7.0 后加密库 Crypto 被废弃后的爬坑指南

    相关文章

      网友评论

          本文标题:Android中的AES加密--上

          本文链接:https://www.haomeiwen.com/subject/cotdxktx.html