Keystore是存储和访问密钥的一种重要管理方式,也是安全相关API的重要基础。 本文介绍Android中系统Keystore的使用。
📎
|
反馈与建议,请移步: #8 |
文章更新历史:
-
2015/11/19 文章发布
早在Android 1.6,为了支持VPN,就已经添加了一个系统keystore。 后来,这个keystore也被用于支持WiFi认证。但是,应用却不被允许访问这个keystore。 如果应用想要使用keystore的功能,只能创建自己的keystore。
Android 4.0的到来,改变了这一现状。利用新添加的API KeyChain, 普通应用也可以访问这个系统keystore。应用可以利用KeyChain来安装自己的凭据 (credentials),也可以直接使用系统keystore中已有的凭据(公钥证书和私钥)。
随着Android 4.3的发布,应用可以在Android Keystore Provider 中创建和使用私有的密钥(仅自己可见)。这是一个定制的Java Security Provider (即“AndroidKeyStore”),可以通过现有加密API来使用。
Android Keystore中的密钥信息受到提取保护,有如下特点:
-
密钥信息永远不会进入应用进程。即使应用进程被入侵了, 攻击者也无法在应用进程中提取密钥信息。
-
密钥信息可存储在安全硬件中(Trusted Execution Environment, Secure Element)。如果生成key时启用了这个特性, 那么key的密钥信息永远不会被提取到安全硬件外部。即使OS被入侵了, 攻击者也无法把密钥信息从安全硬件中提取出来。 但不是所有Android设备都支持安全硬件,可通过KeyInfo.isInsideSecurityHardware()来确认。
KeyChain允许用户授权应用去访问系统keystore,来存储和使用凭据(credentials)。
具体而言,KeyChain提供了两方面的能力:
-
允许应用在系统keystore中安装凭据
-
允许应用使用系统keystore中的凭据
在用户介入并确认的情况下,应用可以把自己的凭据安装到系统keystore中。
目前,支持两种格式的凭据的导入:a) X.509证书; b) PKCS#12 keystore。
安装凭据的步骤如下:
-
通过 KeyChain#createInstallIntent() 创建一个Intent对象, 其extra数据中携带了要安装的凭据;
-
调用startActivityForResult来调起系统的安装确认界面 (PKCS#12 keystore需要输入密码);
-
在onActivityForResult中检查返回值,来确认安装是否成功。
示例代码如下:
public void installCredentials(Activity cxt, int requestCode) {
AssetManager assetMgr = cxt.getAssets();
try {
// password: android
InputStream in = assetMgr.open("apk.keystore.p12");
byte[] keystoreData = IoUtils.readAllBytes(in);
Intent installIntent = KeyChain.createInstallIntent();
installIntent.putExtra(KeyChain.EXTRA_PKCS12, keystoreData);
cxt.startActivityForResult(installIntent, requestCode);
} catch (IOException e) {
e.printStackTrace();
}
}
只要系统中安装有凭据,应用就可以请求使用凭据的授权。
使用凭据的步骤如下:
-
调用KeyChain#choosePrivateKeyAlias(),弹出系统的授权确认界面, 用户可以选择并授权应用访问某个key;
-
通过前面拿到的key alias和KeyChain#getPrivateKey(),就可以使用私钥;
-
通过前面拿到的key alias和KeyChain#getCertificateChain(),就可以拿到证书链。
示例代码如下:
public void requestCredentials(Activity cxt) {
final Context appContext = cxt.getApplicationContext();
KeyChain.choosePrivateKeyAlias(cxt,
new KeyChainAliasCallback() {
public void alias(String alias) {
AppLogger.d(TAG, "got key alias [" + alias + "]");
if (alias != null) {
mKeyAlias = alias;
new Thread() {
@Override
public void run() {
checkPrivateKey(appContext);
checkCertificateChain(appContext);
}
}.start();
}
}
},
new String[]{"RSA", "DSA"}, null, null, -1, null);
}
private void checkPrivateKey(Context cxt) {
PrivateKey privateKey = null;
try {
privateKey = KeyChain.getPrivateKey(cxt, mKeyAlias);
AppLogger.d(TAG, "private key: " + privateKey);
if (privateKey == null) {
return;
}
AppLogger.d(TAG, "format: " + privateKey.getFormat());
AppLogger.d(TAG, "alg: " + privateKey.getAlgorithm());
} catch (KeyChainException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void checkCertificateChain(Context cxt) {
try {
X509Certificate[] certificates = KeyChain.getCertificateChain(cxt, mKeyAlias);
if (certificates == null) {
return;
}
AppLogger.i(TAG, "cert count: " + certificates.length);
for (X509Certificate cert : certificates) {
AppLogger.i(TAG, "cert: " + cert);
}
} catch (KeyChainException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
要使用Android Keystore Provider,只需要在使用KeyStore、KeyPairGenerator、 KeyGenerator等API时,使用“AndroidKeyStore”这个provider即可。
从Android Keystore Provider所支持的 算法列表 可以看到,虽然Android 4.3就添加了“AndroidKeyProvider”, 但大多数算法都是在Android 6.0添加的。
如果应用卸载后再重新安装,那么应用之前在Android Keystore Provider 中创建的key都不再可以访问(没去分析实现细节,很可能是卸载时系统就清除了所有key, 也不排除是因为应用UID发生变化导致的)。
枚举Android Keystore Provider中所有的key,示例代码:
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
Enumeration<String> allAliases = ks.aliases();
示例代码:
KeyPairGenerator kpg = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
kpg.initialize(new KeyGenParameterSpec.Builder(
mPrivateKeyAlias,
KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
.setDigests(KeyProperties.DIGEST_SHA256,
KeyProperties.DIGEST_SHA512)
.build());
KeyPair kp = kpg.generateKeyPair();
示例代码:
KeyGenerator kg = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
kg.init(new KeyGenParameterSpec.Builder(
mSecretKeyAlias,
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
.setKeySize(256)
.build());
SecretKey key = kg.generateKey();
示例代码:
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
KeyStore.Entry entry = ks.getEntry(mPrivateKeyAlias, null);
if (!(entry instanceof KeyStore.PrivateKeyEntry)) {
AppLogger.w(TAG, "Not an instance of a PrivateKeyEntry");
return;
}
byte[] data = "hahahaha.....".getBytes();
// sign
Signature s = Signature.getInstance("SHA256withECDSA");
s.initSign(((KeyStore.PrivateKeyEntry) entry).getPrivateKey());
s.update(data);
byte[] signature = s.sign();
// verify
Signature s2 = Signature.getInstance("SHA256withECDSA");
s2.initVerify(((KeyStore.PrivateKeyEntry) entry).getCertificate());
s2.update(data);
boolean valid = s2.verify(signature);
示例代码:
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
KeyStore.Entry entry = ks.getEntry(mSecretKeyAlias, null);
if (!(entry instanceof KeyStore.SecretKeyEntry)) {
AppLogger.w(TAG, "Not an instance of a SecretKeyEntry");
return;
}
String data = "fofofo...fofofo...fofofo...";
KeyStore.SecretKeyEntry secretEntry = (KeyStore.SecretKeyEntry) entry;
// encrypt
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretEntry.getSecretKey());
byte[] encryptedData = cipher.doFinal(data.getBytes());
byte[] iv = cipher.getIV();
// decrypt
Cipher cipher2 = Cipher.getInstance("AES/CBC/PKCS7Padding");
cipher2.init(Cipher.DECRYPT_MODE, secretEntry.getSecretKey(),
new IvParameterSpec(iv));
byte[] decryptedData = cipher2.doFinal(encryptedData);
String plaintext = new String(decryptedData);
在创建key时,可以指定key在使用时需要用户认证。 这里,用户认证支持安全锁屏的部分方式(解锁图案、PIN码、密码和指纹)。
示例代码:
if (mUserAuth && mKeyguardManager.isKeyguardSecure()) {
builder.setUserAuthenticationRequired(true)
.setUserAuthenticationValidityDurationSeconds(30);
}
在使用key时,如果捕获到 UserNotAuthenticatedException 异常, 可以重新申请用户认证。示例代码:
KeyguardManager keyguardMgr = (KeyguardManager) cxt.getSystemService(Context.KEYGUARD_SERVICE);
Intent intent = keyguardMgr.createConfirmDeviceCredentialIntent(null, null);
if (intent != null) {
cxt.startActivityForResult(intent, requestCode);
}
除了上面的基于时间的认证,还可以使用指纹认证。
在示例代码中,展示了如何使用 KeyChain 和 Android Keystore Provider 相关API。