Skip to content

Latest commit

 

History

History
326 lines (258 loc) · 11.2 KB

0009-android-keystore-system.asc

File metadata and controls

326 lines (258 loc) · 11.2 KB

Android Keystore System

Keystore是存储和访问密钥的一种重要管理方式,也是安全相关API的重要基础。 本文介绍Android中系统Keystore的使用。

📎
反馈与建议,请移步: #8

文章更新历史:

  • 2015/11/19 文章发布


1. Android Keystore System

早在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来使用。

1.1. 安全特性

1.1.1. 防止提取密钥

Android Keystore中的密钥信息受到提取保护,有如下特点:

  • 密钥信息永远不会进入应用进程。即使应用进程被入侵了, 攻击者也无法在应用进程中提取密钥信息。

  • 密钥信息可存储在安全硬件中(Trusted Execution Environment, Secure Element)。如果生成key时启用了这个特性, 那么key的密钥信息永远不会被提取到安全硬件外部。即使OS被入侵了, 攻击者也无法把密钥信息从安全硬件中提取出来。 但不是所有Android设备都支持安全硬件,可通过KeyInfo.isInsideSecurityHardware()来确认。

1.1.2. Key使用认证

为了防止Android设备中的key被非授权使用,Android Keystore 允许应用在生成或导入key时指定认证方式。一旦key被生成或导入,其认证方式无法更改。

1.2. KeyChain还是Android Keystore Provider?如何选择

通过KeyChain安装的凭据,是全系统可共享访问的(所有应用都可以访问)。 当应用请求使用凭据时,系统会弹出一个选择确认界面, 由用户选择决定访问哪个已安装的凭据。

使用Android Keystore Provider,应用可以存储自己私有的凭据, 只有它自己可以访问。当然,也不需要用户介入来选择凭据。

2. Keychain

KeyChain允许用户授权应用去访问系统keystore,来存储和使用凭据(credentials)。

具体而言,KeyChain提供了两方面的能力:

  • 允许应用在系统keystore中安装凭据

  • 允许应用使用系统keystore中的凭据

2.1. 安装凭据

在用户介入并确认的情况下,应用可以把自己的凭据安装到系统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();
        }
    }

2.2. 使用凭据

只要系统中安装有凭据,应用就可以请求使用凭据的授权。

使用凭据的步骤如下:

  • 调用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();
        }
    }

3. Android Keystore Provider

要使用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发生变化导致的)。

3.1. 枚举所有key

枚举Android Keystore Provider中所有的key,示例代码:

KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
Enumeration<String> allAliases = ks.aliases();

3.2. 生成公私密钥

示例代码:

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();

3.3. 生成对称密钥

示例代码:

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();

3.4. 签名及验证

示例代码:

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);

3.5. 使用对称密钥加密数据

示例代码:

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);

3.6. 用户认证

在创建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);
}

除了上面的基于时间的认证,还可以使用指纹认证。

4. 示例代码

在示例代码中,展示了如何使用 KeyChain 和 Android Keystore Provider 相关API。