/   今日科技快讯   /

近日,据外媒援引知情人士透露,零售巨头亚马逊计划最早从本周开始裁员约1万人,这将是该公司历史上规模最大的裁员行动。知情人士表示,裁员将集中在亚马逊的设备部门(包括语音助手Alexa)、零售部门以及人力资源部门。目前裁员人数仍然不稳定,可能会逐个团队进行,而不是在全公司范围内同时裁员。

/   作者简介   /

本篇文章转自我的猫叫冰彬的博客,文章主要分享了Android中KeyStore的使用以及相关原理,相信会对大家有所帮助!

原文地址:

https://blog.csdn.net/qq_43478882/article/details/127392947

/   前言   /

在我们App开发过程中,可能会涉及到一些敏感和安全数据需要加密的情况,比如登录token的存储。我们往往会使用一些加密算法将这些敏感数据加密之后再保存起来,需要取出来的时候再进行解密。

此时就会有一个问题:用于加解密的Key该如何存储?

  • 如果把Key和加密后的数据存到一起,那有一定的安全风险。

  • 对Key再进行一次加密,这就陷入了死循环。

为了保证安全性,Android提供了KeyStore系统来保存Key,本文就浅探一下KeyStore及其使用方法。

/   什么是KeyStore?如何保证安全性?   /

什么是KeyStore?

先来看看官方对他的定义:

This class represents a storage facility for cryptographic keys and certificates.

这个类代表加密密钥和证书的存储设施。定义其实很简单,他就是用来保存加解密的Key和证书的。

如何保证安全性?

安全性保护措施在官方文档里有写,我就按自己的理解总结一下:

  • Key是保存在手机系统里的,应用进程在获取Key的时候是通过系统进程获取到内存里的。也就是说只能在本应用进程里才能拿到Key,想要把Key提取出来是不可能的。

  • KeyStore还可以指定密钥的授权使用方式(比如用户安全锁验证),可保证必须在授权的情况下才能获取到Key。

总的来说就是使用者只能在应用程序运行时使用KeyStore里存放的Key,除非攻击者拿到源码打断点调试,否则无法知道Key到底是什么。这样就保证了存储Key的安全性。

/   KeyStore的使用   /

KeyStore支持的加密算法有很多,其中对部分算法有API Level的要求,具体可以查看官网。

本文就以AES加密算法为例,简单说明一下KeyStore的使用方式。注意,本文涉及到的代码需要minSdk>=23。

先简单实现一下AES算法的加解密。

数据加密

object AESUtil {private const val IV_BLOCK_SIZE = 16fun encryptAES(encryptBytes: ByteArray, encryptKey: SecretKey): ByteArray?{try {//创建密码器val cipher = Cipher.getInstance("AES/CBC/PKCS7PADDING")//用密钥初始化Cipher对象cipher.init(Cipher.ENCRYPT_MODE, encryptKey)val final = cipher.doFinal(encryptBytes)// iv占前16位,加密后的数据占后面return cipher.iv + final} catch (e: NoSuchPaddingException) {e.printStackTrace()} catch (e: NoSuchAlgorithmException) {e.printStackTrace()} catch (e: InvalidAlgorithmParameterException) {e.printStackTrace()} catch (e: InvalidKeyException) {e.printStackTrace()} catch (e: BadPaddingException) {e.printStackTrace()} catch (e: IllegalBlockSizeException) {e.printStackTrace()}return null}fun decryptAES(decryptBytes: ByteArray, decryptKey: SecretKey): ByteArray? {try {// 先取出IVval iv = decryptBytes.copyOfRange(0, IV_BLOCK_SIZE)// 取出加密后的数据val decryptData = decryptBytes.copyOfRange(IV_BLOCK_SIZE, decryptBytes.size)val cipher = Cipher.getInstance("AES/CBC/PKCS7PADDING")cipher.init(Cipher.DECRYPT_MODE, decryptKey, IvParameterSpec(iv))return cipher.doFinal(decryptData)} catch (e: NoSuchPaddingException) {e.printStackTrace()} catch (e: NoSuchAlgorithmException) {e.printStackTrace()} catch (e: InvalidAlgorithmParameterException) {e.printStackTrace()} catch (e: InvalidKeyException) {e.printStackTrace()} catch (e: BadPaddingException) {e.printStackTrace()} catch (e: IllegalBlockSizeException) {e.printStackTrace()}return null}}

然后我们需要为加密生成一个Key,通过KeyGenerator来实现,先生成一个KeyGenerator。

private fun getKeyGenerator(alias: String): KeyGenerator {// 第一个参数指定加密算法,第二个参数指定Providerval keyGenerator = KeyGenerator.getInstance("AES", "AndroidKeyStore")val parameterSpec = KeyGenParameterSpec.Builder(alias,KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT  //用于加密和解密).setBlockModes(KeyProperties.BLOCK_MODE_CBC)  // AEC_CBC.setUserAuthenticationRequired(false)   // 是否需要用户认证.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)  //AES算法的PADDING, 和前面的AESUtil里保持统一.build()keyGenerator.init(parameterSpec)return keyGenerator}

这个方法里接受一个alias的参数,是生成Key的别名,这个会在之后从KeyStore里取的时候用到。生成KeyGenerator之后,就可以生成出加解密需要的Key了:

val key: SecretKey = getKeyGenerator("myKey").generateKey()

那紧接着我们就可以对需要保护的数据进行加密然后存储。

val srcData = "hello world"val encryptData = AESUtil.encryptAES(srcData.toByteArray(), key)// 存储加密后的数据...

从KeyStore中获取Key解密

前面我们已经把数据加密存储好了,接下来就是拿出数据解密后使用了。我们从KeyStore中取出我们解密的Key。

fun getKeyFromKeyStore(alias: String): SecretKey? {// 参数为Providerval keyStore = KeyStore.getInstance("AndroidKeyStore")// 一定要先初始化keyStore.load(null)// 获取KeyStore中的所有Key的别名val aliases = keyStore.aliases()// KeyStore里没有keyif (!aliases.hasMoreElements()) {return null}// Key的保护参数,这里为不需要密码val protParam: KeyStore.ProtectionParameter =KeyStore.PasswordProtection(null)// 通过别名获取Keyval entry = keyStore.getEntry(alias, protParam) as KeyStore.SecretKeyEntryreturn entry.secretKey}

每一步的注释都写在了代码里,这里方法的alias参数就是我们之前通过KeyGenerator生成Key时生成的参数。

接着就可以拿到Key去解密了。

val decryptKey = KeyStoreUtil.getKeyFromKeyStore(KEY_ALIAS)decryptKey?.let {// 解密数据val decryptAES = AESUtil.decryptAES(encryptData, decryptKey)}

到这里,KeyStore的简单使用就结束了。

/   源码分析   /

细心的读者可能会发现问题,在前面的使用中,并没有把Key存入到KeyStore里的操作,为什么后面就可以直接取出来?想要搞清楚这个问题,就必须得通过源码去解决了。

先拟定一下分析问题的思路:

  1. KeyStore是从哪里取的?

  2. KeyGenerator生成Key的时候是怎么存的?

KeyStore是从哪里取的

KeyStore取Key的方法主要是getEntry,这个方法的源码很清晰简单。

public final Entry getEntry(String alias, ProtectionParameter protParam)throws NoSuchAlgorithmException, UnrecoverableEntryException,KeyStoreException {if (alias == null) {throw new NullPointerException("invalid null input");}if (!initialized) {throw new KeyStoreException("Uninitialized keystore");}return keyStoreSpi.engineGetEntry(alias, protParam);}

首先取的时候alias不能为空,这是取Key的别名,如果为空自然就不知道你要哪一个Key了。其次会判断KeyStore是否初始化。那核心的代码就是最后一行了。这里的KeyStoreSpi是一个abstract类,里面实现了engineGetEntry方法。

public KeyStore.Entry engineGetEntry(String alias,KeyStore.ProtectionParameter protParam)throws KeyStoreException, NoSuchAlgorithmException,UnrecoverableEntryException {...if ((protParam == null) || protParam instanceof KeyStore.PasswordProtection) {if (engineIsCertificateEntry(alias)) {throw new UnsupportedOperationException("trusted certificate entries are not password-protected");} else if (engineIsKeyEntry(alias)) {char[] password = null;if (protParam != null) {KeyStore.PasswordProtection pp =(KeyStore.PasswordProtection)protParam;password = pp.getPassword();}Key key = engineGetKey(alias, password);....}}....
}

顺着源码走就会发现,真正拿Key的实现是通过engineGetKey()方法拿的,而这个方法是个abstract方法,也就是要找到具体的实现类。

我们使用的Provider是AndroidKeyStore,对应是实现类是AndroidKeyStoreSpi。那就到里面取看一下engineGetKey()的实现。

public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException,UnrecoverableKeyException {try {return AndroidKeyStoreProvider.loadAndroidKeyStoreKeyFromKeystore(mKeyStore,alias,mNamespace);}....}

里面最核心的代码也就一句话,继续深挖到AndroidKeyStoreProvider里。

public static AndroidKeyStoreKey loadAndroidKeyStoreKeyFromKeystore(@NonNull KeyStore2 keyStore, @NonNull String alias, int namespace)throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException {....final AndroidKeyStoreKey key = loadAndroidKeyStoreKeyFromKeystore(keyStore, descriptor);if (key instanceof AndroidKeyStorePublicKey) {return ((AndroidKeyStorePublicKey) key).getPrivateKey();} else {return key;}}

核心代码是loadAndroidKeyStoreKeyFromKeystore()方法。

private static AndroidKeyStoreKey loadAndroidKeyStoreKeyFromKeystore(@NonNull KeyStore2 keyStore, @NonNull KeyDescriptor descriptor)throws UnrecoverableKeyException, KeyPermanentlyInvalidatedException {KeyEntryResponse response = null;try {// 核心代码response = keyStore.getKeyEntry(descriptor);} catch (android.security.KeyStoreException e) {....}}...}

终于快能看到最终拿Key的地方了,不过这里的keyStore要注意以下,是Android下的KeyStore2这个类。

/*** Retrieves a key entry from the keystore backend.* @see IKeystoreService#getKeyEntry(KeyDescriptor) for more details.* @param descriptor* @return* @throws KeyStoreException* @hide*/public KeyEntryResponse getKeyEntry(@NonNull KeyDescriptor descriptor)throws KeyStoreException {return handleRemoteExceptionWithRetry((service) -> service.getKeyEntry(descriptor));}

从注释里可以看到,KeyStore获取Key的方式是通过IKeystoreService这个服务取获取的,也就是通过系统进程获取的。这里我们主要是查看从哪里取的,更多如何取的细节读者可以看一下IKeystoreService。

怎么存的?

前面我们弄清楚了是从哪里取的,接下来就要看一看是怎么存进去的。KeyStore里存Key的方法是setEntry(),我们就从这里下手看看。

public final void setEntry(String alias, Entry entry,ProtectionParameter protParam)throws KeyStoreException {if (alias == null || entry == null) {throw new NullPointerException("invalid null input");}if (!initialized) {throw new KeyStoreException("Uninitialized keystore");}keyStoreSpi.engineSetEntry(alias, entry, protParam);}

可以看到,存的时候KeyStore还是交给了KeyStoreSpi。而KeyStoreSpi的核心方法是engineSetKeyEntry(),我们直接到AndroidKeyStoreSpi里去看具体的实现。

@Overridepublic void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain)throws KeyStoreException {if ((password != null) && (password.length > 0)) {throw new KeyStoreException("entries cannot be protected with passwords");}if (key instanceof PrivateKey) {setPrivateKeyEntry(alias, (PrivateKey) key, chain, null);} else if (key instanceof SecretKey) {setSecretKeyEntry(alias, (SecretKey) key, null);} else {throw new KeyStoreException("Only PrivateKey and SecretKey are supported");}}

这里简单说一下:

  • PrivateKey通常是非对称加密算法的私钥,公钥用于加密,私钥用于解密,比如RSA算法。

  • SecretKey通常是对称加密算法的密钥,加密解密都用他,比如AES算法。

接着看一下setSecretKeyEntry()方法。

private void setSecretKeyEntry(String alias, SecretKey key,java.security.KeyStore.ProtectionParameter param)throws KeyStoreException {...try {KeyStoreSecurityLevel securityLevelInterface = mKeyStore.getSecurityLevel(securityLevel);KeyDescriptor descriptor = makeKeyDescriptor(alias);securityLevelInterface.importKey(descriptor, null /* TODO attestationKey */,importArgs, flags, keyMaterial);} catch (android.security.KeyStoreException e) {throw new KeyStoreException("Failed to import secret key.", e);}}

方法很长,但是最终存入的方法是最后这里。可以看到,最终是KeyStoreSecurityLevel这个类通过importKey()方法去保存的。

/*** Imports a key into Keystore.* @see IKeystoreSecurityLevel#importKey(KeyDescriptor, KeyDescriptor, KeyParameter[], int,*/public KeyMetadata importKey(KeyDescriptor descriptor, KeyDescriptor attestationKey,Collection<KeyParameter> args, int flags, byte[] keyData)throws KeyStoreException {return handleExceptions(() -> mSecurityLevel.importKey(descriptor, attestationKey,args.toArray(new KeyParameter[args.size()]), flags, keyData));}

从注释里就能看懂了,往KeyStore里导入Key。

KeyGenerator是不是帮我们存了?

搞清楚了怎么存的,就可以去KeyGenerator的源码看看他是不是确实帮我们直接保存了。

public final SecretKey generateKey() {if (serviceIterator == null) {return spi.engineGenerateKey();}....}

KeyGeneratorSpi也是个abstact类,我们这里的具体实现类是AndroidKeyStoreKeyGeneratorSpi。

@Overrideprotected SecretKey engineGenerateKey() {....KeyStoreSecurityLevel iSecurityLevel = null;try {iSecurityLevel = mKeyStore.getSecurityLevel(securityLevel);metadata = iSecurityLevel.generateKey(descriptor,null, /* Attestation key not applicable to symmetric keys. */params,flags,additionalEntropy);} catch (android.security.KeyStoreException e) {switch (e.getErrorCode()) {// TODO replace with ErrorCode.HARDWARE_TYPE_UNAVAILABLE when KeyMint spec//      becomes available.case KeymasterDefs.KM_ERROR_HARDWARE_TYPE_UNAVAILABLE:throw new StrongBoxUnavailableException("Failed to generate key");default:throw new ProviderException("Keystore key generation failed", e);}}....SecretKey result = new AndroidKeyStoreSecretKey(descriptor, metadata, keyAlgorithmJCA,iSecurityLevel);return result;}

这个方法也特别长,但是在最后能看到一个老朋友:KeyStoreSecurityLevel。原来最终生成Key的方法是调用了他的generateKey()方法。

/*** Generates a new key in Keystore.* @see IKeystoreSecurityLevel#generateKey(KeyDescriptor, KeyDescriptor, KeyParameter[], int,*/public KeyMetadata generateKey(@NonNull KeyDescriptor descriptor, KeyDescriptor attestationKey,Collection<KeyParameter> args, int flags, byte[] entropy)throws KeyStoreException {return handleExceptions(() -> mSecurityLevel.generateKey(descriptor, attestationKey, args.toArray(new KeyParameter[args.size()]),flags, entropy));}

在KeyStore里生成一个新的Key,这里就很明显了。

KeyGenerator最终在生成Key的时候,会直接生成在KeyStore里,所以我们才可以直接取到。

/   总结   /

本篇文章简单介绍了什么是KeyStore,如果使用KeyGenerator和KeyStore,并对KeyStore的存取方式做了源码分析。

推荐阅读:

我的新书,《第一行代码 第3版》已出版!

Android开发中的WMS详细解析

Android开发中关于内存的那些事,一篇全搞懂

欢迎关注我的公众号

学习技术或投稿

长按上图,识别图中二维码即可关注

用Android KeyStore对数据进行加解密相关推荐

  1. 使用JDK中的安全包对数据进行加解密

    本文以使用DES对称加密算法为例使用jdk对数据进行加密解密. 首先需要了解Provider类,它是jdk引入的密码服务提供者概念,实现了Java安全性的一部分或者全部.Provider 可能实现的服 ...

  2. SpringBoot中如何灵活的实现接口数据的加解密功能?

    数据是企业的第四张名片,企业级开发中少不了数据的加密传输,所以本文介绍下SpringBoot中接口数据加密.解密的方式. 本文目录 一.加密方案介绍二.实现原理三.实战四.测试五.踩到的坑 一.加密方 ...

  3. 文件传输-对数据进行加解密的方法!

    由于项目安全要求,需要使用RSA算法对部分关键数据进行加密,并使用OAEPWithSHA-256AndMGF1对数据进行填充.通过搜索最终选择较为通用OpenSSL库,但OpenSSL的RSA算法默认 ...

  4. Python-使用U盾完成数据的加解密(使用国密算法SKF接口)

    Python-使用U盾完成数据的加解密(使用国密算法SKF接口) 1-涉及的内容 2-动态库涉及的函数,及结构体 2.1 相关结构体 2.2 相关函数 3-Python实现 4-测试结果 5-UI可视 ...

  5. SpringBoot 基于RequestBodyAdvice 和 ResponseBodyAdvice 实现数据的加/解密(采用 RSA 算法 ),“船新版本”!

    一.前言: 数据是企业的第四张名片,企业级开发中少不了数据的加密传输.为了预防请求数据被劫持篡改,一般都会对传输的数据进行加密操作,如果每个接口都由我们自己去手动加密和解密,那么工作量太大而且代码冗余 ...

  6. boot数据加解密 spring_SpringBoot实现接口数据的加解密功能

    一.加密方案介绍 对接口的加密解密操作主要有下面两种方式: 自定义消息转换器 优势:仅需实现接口,配置简单. 劣势:仅能对同一类型的MediaType进行加解密操作,不灵活. 使用spring提供的接 ...

  7. java tar压缩工具类_分享apache的commons-compress的TarUtils压缩工具类对文件数据进行加解密、解析及格式化校验等操作...

    一.前言 基于apache的commons-compress包中的org.apache.commons.compress.archivers.tar.TarUtils打包工具类对文件进行加解密.并对加 ...

  8. 数据加密-国密SM2对数据进行加解密

    1 什么是SM2 RSA算法的危机在于其存在亚指数算法,对ECC算法而言一般没有亚指数攻击算法. SM2椭圆曲线公钥密码算法:我国自主知识产权的商用密码算法,是ECC(Elliptic Curve C ...

  9. android cocoscreator jsc js 间加解密(六)

    前言 前面 学了 aandroid cocoscreator 热更新 超详细篇(五) 这章 主要学习 cocoscreator 构建后 jsc 与js 文件 之间相互转化(加解密)并实际测试. 可以配 ...

  10. 前端CryptoJS和Java后端数据互相加解密(AES)

    目录 一.序言 二.关于前端CryptoJS 1.CryptoJS简单介绍 2.加密和填充模式选择 3.前端AES加解密示例 (1) cryptoutils工具类 (2) 测试用例 (3) 加解密后输 ...

最新文章

  1. 自动驾驶多目视觉感知
  2. wordpress-4.7.2-zh_CN页面加载慢
  3. python教程是用什么博客写的-用Python和Pygame写游戏-从入门到精通(目录)
  4. jQuery学习笔记--JqGrid相关操作 方法列表 备忘 重点讲解(超重要)
  5. mysql中int(16)_MySQL中int(M)和tinyint(M)数值类型中M值的意义
  6. 基于django的视频点播网站开发-step11-后台用户管理功能...
  7. BZOJ-3226 校门外的区间 线段数+拆点(类似的思想)
  8. php函数find的用法,fleaphp crud操作之findByField函数的用法
  9. python连数据库课程设计报告_sql数据库课程设计报告书
  10. 人工智能吹来的是失业的寒风还是发展的春风?
  11. 如何将零碎信息结构化并做到有序安放,以实现知识积累?
  12. 3.2.1 LinearLayout(线性布局)
  13. 莽撞小子终到迟暮中年 弗朗西斯择队目标转换(转)
  14. 幸福,是一种有节制的满足,冷暖自知。
  15. MySQL: 垂直分片
  16. 软考信息系统监理师:2016年4月1日(冬青子)作业
  17. 英语读书笔记-Book Lovers Day01
  18. 非制冷式红外探测器原理研究(课题总结论文)
  19. 主题 06:如何高效地排查 Java 系统异常
  20. [AWT] GridLayout

热门文章

  1. VIN码/车架号的详解,车架号识别,VIN码识别,OCR车架号识别能带来什么
  2. STM32学习心得三十三:FLASH闪存编程原理与实验
  3. 404两人互殴css3搞笑代码
  4. Word 中自动设置匹配章、节序号的标题
  5. vi/vim操作手册
  6. Tomcat的作用(自用)
  7. 注册页面获取手机验证码
  8. 金融知识图谱的现状与展望
  9. 电脑照片太大怎么压缩?照片怎么缩小kb?
  10. BZOJ 2339 [HNOI2011]卡农