背景
最近需要解决用户使用手机克隆进行手机备份,某些设备相关的数据,比如设备ID的的存储文件也被克隆,导致用户更换设备之后,从本地读取到的设备ID信息依旧是上个手机的(Android Q以及以上的设备,有时候没办法获取设备ID,只能给一个随机数,因此只能存储在应用本地),这样会导致安全问题。
KeyStore简介
利用 Android KeyStore System,您可以在容器中存储加密密钥,从而提高从设备中提取密钥的难度。在密钥进入密钥库后,可以将它们用于加密操作,而密钥材料仍不可导出。此外,它提供了密钥使用的时间和方式限制措施,例如要求进行用户身份验证才能使用密钥,或者限制为只能在某些加密模式中使用。
密钥库系统由 KeyChain API 以及在 Android 4.3(API 级别 18)中引入的 Android 密钥库提供程序功能使用。本文说明了何时以及如何使用 Android 密钥库提供程序。
注意:Android Keystore的API非线程安全,对于API的调用需要进行互斥操作。线程不安全的原因很简单,那就是不管上层创建多少个对象,底层都只对应同一个加解密硬件,硬件没有完成操作之前给出另一个操作命令会导致硬件工作异常。这也是官方文档 EncryptedFile 类和 EncryptedSharedPreferences 类中的方法不是线程安全的根本原因。
我们从 Google Tink 项目的讨论 Bug: using EncryptedSharedPreferences, it can cause crashes to users right when initializing it #535中可以了解到,目前三星Samsung的部分机型的底层实现就是线程不安全的。
更多的机型信息参考 Bug: using EncryptedSharedPreferences, it can cause crashes to users right when initializing it 崩溃率排行中,三星,小米遥遥领先!
应用场景
1、 存储密匙:Android提供的这个KeyStore最大的作用就是不需要开发者去维护这个密匙的存储问题,相比起存储在用户的数据空间或者是外部存储器都更加安全。需要注意:这个密匙随着用户清除数据或者卸载应用都会被清除掉。
2、得益于Android独立的一套密匙库系统,可以提高安全性
安全功能
Android 密钥库系统可以保护密钥材料免遭未经授权的使用。首先,Android 密钥库可以防止从应用进程和 Android 设备中整体提取密钥材料,从而避免了在 Android 设备之外以未经授权的方式使用密钥材料。其次,Android 密钥库可以让应用指定密钥的授权使用方式,并在应用进程之外强制实施这些限制,从而避免了在 Android 设备上以未经授权的方式使用密钥材料。
提取防范
Android 密钥库密钥使用两项安全措施来避免密钥材料被提取:
- 密钥材料永不进入应用进程。通过 Android 密钥库密钥执行加密操作时,应用会将待签署或验证的明文、密文和消息馈送到执行加密操作的系统进程。如果应用进程受攻击,攻击者也许能使用应用密钥,但无法提取密钥材料(例如,在 Android 设备以外使用)。
- 您可以将密钥材料绑定至 Android 设备的安全硬件,例如可信执行环境 (TEE) 和安全元素 (SE)。为密钥启用此功能时,其密钥材料永远不会暴露于安全硬件之外。如果 Android 操作系统受到攻击或者攻击者可以读取设备内部存储空间,攻击者也许能在 Android 设备上使用应用的 Android 密钥库,但无法从设备上提取这些数据。只有设备的安全硬件支持密钥算法、区块模式、填充方案和密钥有权使用的摘要的特定组合时,才可启用此功能。要检查是否为密钥启用了此功能,请获取密钥的 KeyInfo 并检查 KeyInfo.isInsideSecurityHardware() 的返回值。
密钥使用授权
为了避免在 Android 设备上以未经授权的方式使用密钥材料,在生成或导入密钥时 Android 密钥库会让应用指定密钥的授权使用方式。一旦生成或导入密钥,其授权将无法更改。然后,每次使用密钥时,都会由 Android 密钥库强制执行授权。这是一项高级安全功能,通常仅用于有以下要求的情形:在生成/导入密钥后(而不是之前或当中),应用进程受到攻击不会导致密钥以未经授权的方式使用。
支持的密钥使用授权可归为以下几个类别:
- 加密:授权密钥算法、运算或目的(加密、解密、签署、验证)、填充方案、区块模式以及可与密钥搭配使用的摘要;
- 时间有效性间隔:密钥获得使用授权的时间间隔;
- 用户身份验证:密钥只能在用户最近进行身份验证时使用。请参阅要求进行用户身份验证才能使用密钥。
作为一项额外的安全措施,对于密钥材料位于安全硬件内部的密钥(请参阅 KeyInfo.isInsideSecurityHardware()),某些密钥使用授权可能由安全硬件实施,具体取决于 Android 设备。加密和用户身份验证授权可能由安全硬件实施。由于安全硬件一般不具备独立的安全实时时钟,时间有效性间隔授权不可能由其实施。
您可以使用 KeyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware() 查询密钥的用户身份验证授权是否由安全硬件实施。
选择密钥链或 Android 密钥库提供程序
在需要系统级凭据时请使用 KeyChain API。在应用通过 KeyChain API 请求使用任何凭据时,用户需要通过系统提供的 UI 选择应用可以访问已安装的哪些凭据。因此,在用户同意的情况下多个应用可以使用同一套凭据。
使用 Android 密钥库提供程序让各个应用存储自己的凭据,并且只允许应用自身访问。这样,应用可以管理仅能由自己使用的凭据,同时又可以提供等同于 KeyChain API 为系统级凭据提供的安全优势。这一方法不需要用户选择凭据。
使用 Android 密钥库提供程序
要使用此功能,请使用标准的 KeyStore 和 KeyPairGenerator 或 KeyGenerator 类,以及在 Android 4.3(API 级别 18)中引入的 AndroidKeyStore 提供程序。
AndroidKeyStore 注册为 KeyStore 类型以用于 KeyStore.getInstance(type) 方法,而在用于 KeyPairGenerator.getInstance(algorithm, provider) 和 KeyGenerator.getInstance(algorithm, provider) 方法时则注册为提供程序。
生成新私钥
生成新的 PrivateKey 要求您同时指定自签署证书具备的初始 X.509 属性。之后,您可以使用 KeyStore.setKeyEntry 将证书替换为由证书颁发机构 (CA) 签署的证书。
要生成密钥,则使用 KeyPairGenerator 和 KeyPairGeneratorSpec:
生成新密钥
要生成密钥,请使用 KeyGenerator 和 KeyGenParameterSpec。
使用密钥库条目
AndroidKeyStore 提供程序的使用通过所有的标准 KeyStore API 加以实现。
列出条目
通过调用 aliases() 方法列出密钥库中的条目:
签署和验证数据
通过从密钥库提取 KeyStore.Entry 并使用 Signature API(例如 sign())签署数据:
类似地,请使用 verify(byte[]) 方法验证数据:
要求进行用户身份验证才能使用密钥
生成密钥或将密钥导入到 AndroidKeyStore 时,您可以指定密钥仅授权给经过身份验证的用户使用。用户使用安全锁定屏幕凭据(模式/PIN/密码、指纹)的子集进行身份验证。
这是一项高级安全功能,通常仅用于有以下要求的情形:在生成/导入密钥后(而不是之前或当中),应用进程受到攻击不会导致密钥被未经身份验证的用户使用。
如果密钥仅授权给经过身份验证的用户使用,可以将其配置为以下列两种模式之一运行:
- 经过身份验证的用户可以在一段时间内使用密钥。在用户解锁安全锁定屏幕或使用 KeyguardManager.createConfirmDeviceCredentialIntent 流程确认其安全锁定屏幕凭据后,即可使用此模式中的所有密钥。每个密钥的授权持续时间各不相同,并由 setUserAuthenticationValidityDurationSeconds 在密钥生成或导入时指定。此类密钥只能在启用安全锁定屏幕时生成或导入(请参阅 KeyguardManager.isDeviceSecure())。在安全锁定屏幕停用(重新配置为“无”、“滑动”或不验证用户身份的其他模式)或被强制重置(例如由设备管理员执行)时,这些密钥将永久失效。
- 用户身份验证会授权与某一密钥关联的特定加密操作。在此模式中,涉及此类密钥的每个操作都需要用户单独授权。目前,此类授权的唯一方式是指纹身份验证:FingerprintManager.authenticate。此类密钥只能在至少注册一个指纹时生成或导入(请参阅 FingerprintManager.hasEnrolledFingerprints)。一旦注册新指纹或取消注册所有指纹,这些密钥将永久失效。
Android数据加密:
Android 提供了 KeyStore 等可以长期存储和检索加密密钥的机制,Android KeyStore 系统特别适合于存储加密密钥。
“AndroidKeyStore” 是 KeyStore 的一个子集,存进 AndroidKeyStore 的 key 将受到签名保护,并且这些 key 是存在系统里的,而不是在 App 的 data 目录下,依托于硬件的 KeyChain 存储,可以做到 private key 一旦存入就无法取出,
每个 App 自己创建的 key,别的应用是访问不到的。
它提供了限制何时以何种方式使用密钥的方法,比如使用密钥时需要用户认证或限制密钥只能在加密模式下使用。
一个应用程式只能编辑、保存、取出自己的密钥。
App可以生成或者接收一个公私密钥对,并存储在Android的Keystore系统中。公钥可以用于在应用数据放置到特定文件夹前对数据进行加密,私钥可以在需要的时候解密相应的数据。
作用:
KeyStore 适用于生成和存储密钥,这些密钥可以用来加密运行时获取到的数据,比如运行时,用户输入的密码,或者服务端传下来的 token。
操作方式
- 存:使用 Keystore 加密信息后存入 SharedPreferences
- 取:从 SharedPreferences 取出信息並使用 Keystore 解密
建议做法
1. 使用对称式加解密,但只能在Api Level 23+使用
对称式加解密(AES)速度较快,但是对称式的Key若要存在 KeyStore 裡,Api Level一定要在23以上才支持,23以下是无法存入 KeyStore 的,非对称式的Key則不在此限。
2. 想兼容各Api版本(23以下也能用)
- 若要存取的東西不多、字串長度也不長:直接使用非对称式加解密即可
- 若要存取的東西很多或字串長度很長:由於非对称式加解密速度较慢,使用非对称式+对称式加解密可以解決此問題。
考慮到加解密效能、版本兼容,下面會介紹用非对称式+对称式來加解密。
KeyStore非对称+对称式加解密流程
- 使用 KeyStore 产生随机的 RSA Key;
- 产生 AES Key,并用 RSA Public Key 加密后存入 SharedPrefs;
- 从 SharedPrefs 取出 AES Key,並用 RSA Private Key 解密,用这把 AES Key 来加解密信息;
主流的加密方式有:(对称加密)AES、DES 、EC (非对称加密)RSA、DSA
工作模式:
DES一共有:
电子密码本模式(ECB)、加密分组链接模式(CBC)、加密反馈模式(CFB)、输出反馈模式(OFB);
AES一共有:
电子密码本模式(ECB)、加密分组链接模式(CBC)、加密反馈模式(CFB)、输出反馈模式(OFB)、计数器模式(CTR),伽罗瓦计数器模式(GCM)
PKCS5Padding是填充模式,还有其它的填充模式;
对于初始化向量 iv: 初始化向量参数,AES 为16bytes. DES 为 8bytes
(1)产生随机的 RSA Key
产生 RSA Key 会使用到 KeyPairGenerator:
其中 KeyPairGeneratorSpec 在Api 23以上已经 Deprecated 了;
Api level 23以上改使用 KeyGenParameterSpec
Api 23 以上使用 KeyGenParameterSpec
Api 23 以下使用 KeyPairGeneratorSpec
注意,已知,在某些低端设备上,RSA密钥对生成的时间可能超过1S以上。尝试过异步子线程初始化,但是由KeyStore内部的函数在实现的时候,线程不安全,导致子线程初始化的时候诱发了异常行为,因此只能在主线程中进行操作。
(2)产生AES Key后,并用RSA Public Key加密后存入SharedPrefs
1]加密存储:使用RSA Public Key 加密 AES Key,存入缓存中。
2] 解密使用:使用RSA Private Key 解密 得到 AES Key。
获取AES :
再使用AES 加解密内容:
对于:Cipher 初始化
具体使用:
iv 初始化向量
关于RSA:
使用RSA加解密时,在较低版本的手机上可能无法选择OAEP(最优非对称加密填充,RSA的加密解密是基于OAEP的)這個模式;
因此可以改使用RSA_PKCS1_PADDING模式,使用这个模式的話,输入必须比RSA的Key最大长度少11個字节,如果需要被加密的字串过长的话,可以在产生Key时指定Key Size长度,或是将字串分段加密。
以预设Key Size = 2048bit(256byte)來说,输入最长只能到256–11=245byte,我們可以透过setKeySize(int keySize)指定Key的长度,但是Key Size越大,加解密时速度就越慢。
需要注意,由于设备可能存储密钥到硬件设备(KeyInfo.isInsideSecurityHardware()),然而硬件设备不一定能保存我们手工指定的某些长度的密钥。导致如果我们设置了指定长度,可能由于硬件设备不支持,反而只能存储到系统中,造成密钥存储的安全性反而下降了。
我们希望厂家设置的默认值是硬件能支持的最大安全性,并且尽量存储到硬件中。尽管密钥安全性可能下降了,但是存储安全性反而上升了。
或者从高到低重试,选择当前设备支持的最高的硬件存储的长度。究竟是存储位置重要还是加密级别更重要,需要权衡。
判断生成的密钥是否由硬件存储,参考如下代码:
获取设备生成的密钥的长度,参考如下代码:
需要解决的一个疑惑就是,既然可以通过
的方式获得RSA私钥,那么,我们能不导出这个私钥呢?
答案显然是不能的,原因在于系统给出的私钥只是一个代理,并没有实际的私钥数据,私钥数据被存储在相关的硬件或者系统内核中,主要证据就是privateKey.getEncoded()
返回了null
,这样就实现了关键的私钥数据都无法获得。
参考如下测试代码:
需要注意的一个问题在于,由于RSA的大素数搜索机制,导致每次生成密钥的时间可能会超过预期,最长的可能会耗时1-2S以上。因此,如果设备支持,我们直接申请硬件AES密钥的方式来进行数据的加解密操作,达到更高的安全程度。
可用的参考代码如下:
参考链接
- Android Keystore 对称-非对称加密
- AndroidKeyStore的使用
- Android KeyStore密钥存储
- Android 密钥库系统
- BouncyCastle 密钥转换 - Java pkcs1格式,pkcs8格式互转
- C#与Java的RSA中的X509EncodedKeySpec、PKCS8EncodedKeySpec
- android安全存储,使用AndroidKeyStore的问题
- OPENSSL RSA加密与解密
- How to store and retrieve an RSA public key in Android keystore which is generated from server side application?
- 如何用已知的参数生成一个RSAPrivateKey
- 公钥,私钥和数字签名这样最好理解 (转载)
- MIUI安全与隐私白皮书
- android.security.KeyStoreException: Signature/MAC verification failed When trying to decrypt in a React Native Module
- 分组密码加密操作模式
- List of Android smart phones that support Android StrongBox on API 28(Android Pie)
- Android 纯本地安全存储方案
- 从AndroidKeystore加载私钥时出现问题
- Using AES with AndroidKeyStore
- Key attestation的几个关键点!
- Verifying hardware-backed key pairs with Key Attestation
- GrapheneOS/AttestationSamples
- google/android-key-attestation
- App security improvement program
- aes iv值_AES加密: GCM和CBC模式的区别
- 我对Padding Oracle攻击的分析和思考(详细)
- 使用 SafetyNet 抵御安全威胁
- Android Pie 引入 Keystore 新特性,安全防护再升级
- Import encrypted AES key into Android Keystore and store it under new alias
- Android Cryptographic APIs
- 聊聊密码学中的Padding
- Chosen ciphertext attacks against protocols based on the RSA encryption standard PKCS #1
- Import encrypted AES key into Android Keystore and store it under new alias
- 密钥认证和 ID 认证
- alphamu/KeyStoreHelper.java
- How to write thread-safe code for Android Keystore
- Bug: using EncryptedSharedPreferences, it can cause crashes to users right when initializing it #535
- [原创]Key Attestation原理理解
- Android Developers/Develop/Reference/Cipher
- Android Developers/Develop/Reference/AlgorithmParameters
- Android Developers/Security/加密
- Android Developers/App quality/安全准则
- Android Local Authentication