zoukankan      html  css  js  c++  java
  • AndroidKeystore密钥库系统,保证本地加密算法的密钥安全性

    1、AndroidKeystore密钥库系统介绍

    AndroidKeystore系统是一个密钥库管理系统,谷歌设计这个系统的初衷应该是为了对标苹果的钥匙串KeyChain,有意思的是谷歌在Android4.0(API14)时便引入了KeyChain,但是并未提供详尽的说明文档,仅仅提供了一个API文档,网上也没有什么对它的文章,着实让人费解。话不多说,有兴趣的同学可以自行搜索,下面我来给大家介绍一下今天的主角——AndroidKeystore

    谷歌在Android4.3(API18)中引入了AndroidKeystore,在Android6.0(API23)中对AndroidKeystore进行了一波升级优化,目前来说应该是最安全的加解密方案。根据官方文档说明,原文对它的介绍如下:

    利用 Android Keystore 系统,您可以在容器中存储加密密钥,从而提高从设备中提取密钥的难度。在密钥进入 Keystore 后,可以将它们用于加密操作,而密钥材料仍不可导出。此外,它提供了密钥使用的时间和方式限制措施,例如要求进行用户身份验证才能使用密钥,或者限制为只能在某些加密模式中使用。

    乍一看,这似乎是一个密钥保险箱,安全性高,可以把加密密钥存入其中,需要的时候再通过它用我们的密钥。可是!!这货的使用根本就不是个存储空间!!我们先来看看这东西大致的使用流程是怎样的,让大家直观的明白它的作用:

    // step 1  通过密钥别名判断是否已有密钥
    keyStore.containsAlias(keyAlias);
    
    // step 2  没有密钥,生成一个
    spec = new KeyGenParameterSpec.Builder(keyAlias, ...)...;
    keyPairGenerator.initialize(spec);
    keyPairGenerator.generateKeyPair();
    
    // step 3  取出公钥,进行加密
    publicKey = ((KeyStore.PrivateKeyEntry)entry).getCertificate().getPublicKey();
    cipher.init(Cipher.ENCRYPT_MODE, publicKey);
    cipher.doFinal(data);
    
    // step 4  取出私钥,进行解密
    privateKey = ((KeyStore.PrivateKeyEntry)entry).getPrivateKey();
    cipher.init(Cipher.DECRYPT_MODE, privateKey);
    cipher.doFinal(data);
    

    如此看来,AndroidKeystore根本就不是个密钥存储库。这和EncryptUtil这样的工具类几乎是一样的,先init,再encrypt,然后decrypt,俨然一个加解密工具。说白了,它在内部生成一个密钥,我们可以用它来加解密数据,并不能把我们自己生成的密钥送进去。

    根据文档的介绍,它的大部分功能都只在Android6.0(API23)及以上支持,最低支持的Android4.3(API18)只提供RSA/ECB算法的使用。

    2、密钥的检测与生成

    AndroidKeystore是通过密钥的别名来进行加解密的,那么我们在使用之前必然是需要判断一下密钥是否存在的,AndroidKeystore提供了containsAlias()方法来完成密钥的检测,具体使用如下:

    public boolean hasKey(String keyAlias) {
    	try {
    		// 加载一个AndroidKeyStore类型的KeyStore,貌似是定死的类型。
    		KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
    		keyStore.load(null);
    		return keyStore.containsAlias(keyAlias);
    	} catch (KeyStoreException e) {
    		e.printStackTrace();
    	} catch (CertificateException e) {
    		e.printStackTrace();
    	} catch (NoSuchAlgorithmException e) {
    		e.printStackTrace();
    	} catch (IOException e) {
    		e.printStackTrace();
    	}
    	return false;
    }
    

    这里可以"AndroidKeyStore"用常量存一下,后面还会用到,避免魔法字符串。关于字符串常量我就不单独提出来了,不方便阅读,接下来有使用的字符串都建议在项目中提取为字符串常量。

    密钥的检测很简单,我就不赘述了,这里不得不吐槽一句,异常真多,当然后面也是一样的多异常,总让人用起来不踏实的感觉。

    如果密钥库中没有该别名的密钥,那么我们得让它生成一个,重头戏来了,敲黑板!!!

    public void buildKey(Context context, String keyAlias) {
    	try{
    		// 先获取密钥对生成器,采用RSA算法,AndroidKeyStore类型
    		KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
    		// 加密算法的相关参数
    		AlgorithmParameterSpec spec;
    		// 密钥的有效起止时间,从现在到999年后,时间大家自己定
    		Calendar start = Calendar.getInstance();
    		Calendar end = Calendar.getInstance();
    		end.add(Calendar.YEAR, 999)
    		// 生成加密参数,从Android6.0(API23)开始有所不同
    		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    			// 根据密钥别名生成加密参数,提供加密和解密操作
    			spec = new KeyGenParameterSpec.Builder(keyAlias, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
    				// SHA (Secure Hash Algorithm,译作安全散列算法) 是美国国家安全局 (NSA) 设计,美国国家标准与技术研究院 (NIST) 发布的一系列密码散列函数。 感兴趣的同学可以了解一下
    				.setDigests(KeyProperties.DIGEST_SHA512)
    				// 填充模式,一般RSA加密常用PKCS1Padding模式
    				.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
    				// 限定密钥有效期起止时间
    				.setCertificateNotBefore(start.getTime())
    				.setCertificateNotAfter(end.getTime())
    				.build();
    		} else {
    			// 相对于Android6.0(API23)的方式,这种稍显简单
    			spec = new KeyPairGeneratorSpec.Builder(context.getApplicationContext())
    				.setAlias(keyAlias)
    				// 设置用于生成的密钥对的自签名证书的主题,X500Principal这东西不认识,资料真少,看的头大
    				.setSubject(new X500Principal("CN=" + keyAlias))
    				// 设置用于生成的密钥对的自签名证书的序列号,从BigInteger取即可
    				.setSerialNumber(BigInteger.TEN)
    				// 限定密钥有效期起止时间
    				.setStartDate(start.getTime())
    				.setEndDate(end.getTime())
    				.build()
    		}
    		// 用加密参数初始化密钥对生成器,生成密钥对
    		keyPairGenerator.initialize(spec);
    		keyPairGenerator.generateKeyPair();
    	} catch (NoSuchAlgorithmException e) {
    		e.printStackTrace();
    	} catch (NoSuchProviderException e) {
    		e.printStackTrace();
    	} catch (InvalidAlgorithmParameterException e) {
    		e.printStackTrace();
    	}
    }
    

    话不多说,都在代码注释里了,有没讲清楚的地方,欢迎评论区指出。

    3、加解密流程

    来吧,进入令人颤抖的加解密流程,真的,当我写完我都不可思议。

    加解密的流程非常相似,先加载KeyStore,再拿到公钥/私钥,最后进行加密/解密。代码实现也基本一致,那么问题来了,令人颤抖的是什么呢?亮代码:

    // 加密方法
    public byte[] encrypt(Context context, String keyAlias, byte[] data) {
    	if(!hasKey(keyAlias)) {
    		buildKey(context, keyAlias);
    	}
    	try {
    		// 获取"AndroidKeyStore"类型的KeyStore,加载
    		KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
    		keyStore.load(null);
    		// 拿到密钥别名对应的Entry
    		KeyStore.Entry entry = keyStore.getEntry(keyAlias, null);
    		if (entry instanceof KeyStore.PrivateKeyEntry) {
    			// 通过Entry拿到公钥对象(并不是真实的公钥,仅供加密方法使用)
    			PublicKey publicKey = ((KeyStore.PrivateKeyEntry)entry).getCertificate().getPublicKey();
    			// 使用"RSA/ECB/PKCS1Padding"模式进行加密
    			Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
    			cipher.init(Cipher.ENCRYPT_MODE, publicKey);
    			return cipher.doFinal(data);
    		}
    	} catch (IOException e) {
    		e.printStackTrace();
    	} catch (CertificateException e) {
    		e.printStackTrace();
    	} catch (NoSuchAlgorithmException e) {
    		e.printStackTrace();
    	} catch (UnrecoverableEntryException e) {
    		e.printStackTrace();
    	} catch (KeyStoreException e) {
    		e.printStackTrace();
    	} catch (NoSuchPaddingException e) {
    		e.printStackTrace();
    	} catch (InvalidKeyException e) {
    		e.printStackTrace();
    	} catch (BadPaddingException e) {
    		e.printStackTrace();
    	} catch (IllegalBlockSizeException e) {
    		e.printStackTrace();
    	}
    	return null;
    }
    
    
    // 解密方法
    public byte[] decrypt(Context context, String keyAlias, byte[] data) {
    	if(!hasKey(keyAlias)) {
    		buildKey(context, keyAlias);
    	}
    	try {
    		// 获取"AndroidKeyStore"类型的KeyStore,加载
    		KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
    		keyStore.load(null);
    		// 拿到密钥别名对应的Entry
    		KeyStore.Entry entry = keyStore.getEntry(keyAlias, null);
    		if (entry instanceof KeyStore.PrivateKeyEntry) {
    			// 通过Entry拿到私钥对象(并不是真实的私钥,仅供解密方法使用)
    			PrivateKey privateKey = ((KeyStore.PrivateKeyEntry)entry).getPrivateKey();
    			// 使用"RSA/ECB/PKCS1Padding"模式进行解密
    			Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
    			cipher.init(Cipher.DECRYPT_MODE, privateKey);
    			return cipher.doFinal(data);
    		}
    	} catch (IOException e) {
    		e.printStackTrace();
    	} catch (CertificateException e) {
    		e.printStackTrace();
    	} catch (NoSuchAlgorithmException e) {
    		e.printStackTrace();
    	} catch (UnrecoverableEntryException e) {
    		e.printStackTrace();
    	} catch (KeyStoreException e) {
    		e.printStackTrace();
    	} catch (NoSuchPaddingException e) {
    		e.printStackTrace();
    	} catch (InvalidKeyException e) {
    		e.printStackTrace();
    	} catch (BadPaddingException e) {
    		e.printStackTrace();
    	} catch (IllegalBlockSizeException e) {
    		e.printStackTrace();
    	}
    	return null;
    }
    

    谜题揭晓,令人颤抖的异常!!这是我至今为止,捕获的最多的异常了,果真是头发短见识短,疫情让我的头发长长了,见识的增长也接踵而来。

    4、常规用法

    通常只把AndroidKeystore用于对密钥的加密,也就是说,建议将需要加密的数据使用对称加密算法(如AES)进行加密,而对称加密算法的密钥则由AndroidKeystore进行加密保护。通常用法如下:

    // saveKey()方法用于将加密后的aesKey持久化,parseByte2Hex()方法用于将二进制转为十六进制,防止byte[]转String导致格式出错的问题。 
    saveKey(parseByte2Hex(encrypt(context, keyAlias, aesKey)));
    
    // readKey()方法用于读取持久化的加密的aesKey,parseHex2Byte()方法用于将十六进制转为二进制,防止String转byte[]导致格式出错的问题。 
    aesKey = decrypt(context, key, parseHex2Byte(readKey()));
    
  • 相关阅读:
    android ListView加载不同布局
    实例演示如何在spring4.2.2中集成hibernate5.0.2并创建sessionFactory
    【翻译】Ext JS最新技巧——2015-10-21
    Android Studio下使用NDK的流程
    Android Studio JNI javah遇到的问题
    题解报告:hdu 1062 Text Reverse
    题解报告:hdu 1039 Easier Done Than Said?
    ACM_逆序数(归并排序)
    hdu 1556 Color the ball(区间修改,单点查询)
    hdu 1754 I Hate It(线段树)
  • 原文地址:https://www.cnblogs.com/hwb04160011/p/13960526.html
Copyright © 2011-2022 走看看