Android中的AES加密--上

2020-06-16  本文已影响0人  g小志

前言

最近需要一个加密一下用户信息,想到用到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();

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

问题也时出现再这里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 被废弃后的爬坑指南

上一篇下一篇

猜你喜欢

热点阅读