前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android中的AES加密--上

Android中的AES加密--上

作者头像
g小志
发布2020-06-19 11:28:56
4.5K0
发布2020-06-19 11:28:56
举报
文章被收录于专栏:Android常用基础Android常用基础

前言

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

第一版

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

代码语言:javascript
复制
  //偏移量
    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,没问题,但是觉得好像哪里不对,我本来是为了安全考虑才加密数据的,结果这样把加密的密钥写在类文件是不是不太合适? 所以,又找了一下看如何安全一点。

第二版

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

代码语言:javascript
复制
 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";
    }

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

代码语言:javascript
复制
            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这个安全供应商,原因是不安全,也不可靠参考原因

第三版

兼容版本

代码语言:javascript
复制
  @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(译)

源码:

代码语言:javascript
复制
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 被废弃后的爬坑指南

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
    • 第一版
      • 第二版
      • 第三版
      • 第四版 KeyStore
      相关产品与服务
      容器服务
      腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档