将加密的AES密钥导入Android密钥库,并将其存储在新别名下

nul*_*der 5 java security android cryptography keystore

我只是熟悉Android Keystore API。我发现可以使用以下功能:

  • 至少在某些设备上,Android密钥库受硬件支持,这意味着加密操作在安全环境(TEE)中运行。
  • 当密钥库由硬件支持时,可以将私钥RSA密钥以及在密钥库中创建的对称对称密钥配置为永不离开密钥库,并且即使具有root访问权限也无法读出原始密钥。

我现在想知道以下情况是否可能:

  1. 生成公钥/私钥对,其中私钥永远不会离开密钥库
  2. 将此对的公钥上传到服务器
  3. 在服务器上:创建随机对称AES密钥,并使用用户上传的公共RSA密钥对其进行加密
  4. 在设备上:下载此加密的AES密钥
  5. 将其导入到硬件支持的密钥库中,以便使用该对的私钥在其中解密并存储在新别名下
  6. 使用此新的密钥别名执行对称加密和解密

1-4应该是可能的,对于我来说,现在缺少的链接是此列表中的第5点。有人可以帮我一下,告诉我是否可行,和/或为我提供正确的API参考吗?

我发现了这一点:https : //android.googlesource.com/platform/development/+/master/samples/Vault/src/com/example/android/vault/SecretKeyWrapper.java

但是在我看来,秘密密钥的解开发生在正常环境中,并且解密的AES密钥将在应用程序中可用,这不能满足我的安全要求。

更新:

我使用链接创建了一个小型测试项目SecretKeyWrapper,这是两个代码段:

第一个执行以下操作:

  1. 创建一个随机的AES密钥(不在密钥库中,这将在以后的服务器上发生)。显然,可以从生成的SecretKey对象中检索原始密钥,这没问题,因为服务器可以知道密钥。
  2. 使用在客户端的Android密钥存储区中创建的RSA公共密钥对密钥进行加密/包装(这也会在服务器上发生)。
  3. 使用RSA私钥再次解密该密钥(这将在客户端上发生,并且实际上在示例中的TEE中发生)。

片段1:

SecretKeyWrapper secretKeyWrapper = new SecretKeyWrapper(this,"testKeyRsa");

// Generate a random AES key (not in the keystore) [1]
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128);
SecretKey secretKeyGenerated = keyGen.generateKey();
byte[] secretKeyGeneratedRaw = secretKeyGenerated.getEncoded();

// wrap this key with the RSA key from the keystore [2]
byte[] wrappedKey = secretKeyWrapper.wrap(secretKeyGenerated);

// unwrap it again with the RSA key from the keystore [3]
SecretKey unwrappedKey = secretKeyWrapper.unwrap(wrappedKey);

// the raw key can be read again [4]
byte[] unwrappedKeyRaw = secretKeyGenerated.getEncoded();
Run Code Online (Sandbox Code Playgroud)

我想要实现的是,[3]中的未包装密钥以新的别名存储在密钥库中,而没有返回原始密钥。当然,我可以SecretKey在此处轻松地将对象导入到密钥库中,但是问题在于,此时可以使用语句[4]从对象中检索原始密钥,这会引发安全漏洞。显然,在密钥库/ TEE中已经发生了解包/解密,因为用于解密的专用RSA密钥位于密钥库中并且无法检索。

如果将此与在密钥库中创建随机秘密AES密钥的情况进行比较,则会注意到返回了不同的类型(实现SecretKey接口)。在上面的示例中,类型为SecretKeySpec,而对于从Android密钥库返回的键(请参见下面的代码段2),在getEncoded()方法始终返回null的情况下使用“不透明”类型。在以下示例中,类型keyAesKeystoreAndroidKeyStoreSecretKey

片段2:

// create a new AES key in the keystore
KeyGenerator keyGenAndroid =  KeyGenerator.getInstance("AES","AndroidKeyStore");
keyGenAndroid.init(
    new KeyGenParameterSpec.Builder("testKeyAes",
         KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)                             
        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)                             
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
        .build());
        SecretKey keyAesKeystore = keyGenAndroid.generateKey();

// this returns null
byte[] keyAesKeystoreRaw = keyAesKeystore.getEncoded();
Run Code Online (Sandbox Code Playgroud)

因此,请改写这个问题:是否可以通过某种方式将RSA包装的AES密钥安全地导入Android密钥存储区,而无需向应用程序公开密钥?

更新2:

@Robert在下面的答案中给出绝对有效的信息,即在TEE或Rich OS(App)中发生解包实际上并不重要,因为该应用程序(或被篡改的版本)总是可以稍后(在截取包装好的密钥之后)只需“使用”密钥库中的RSA私钥来解开AES密钥(根本不需要访问原始私钥)。

不过,这是另外一种想法:我发现可以为Android密钥库中的密钥设置密钥保护参数(请参见此处)。

的链接实现SecretKeyWrapper不使用此类保护参数。在generateKeyPair按如下所示更改方法并添加PURPOSE_DECRYPTPURPOSE_ENCRYPT属性后,一切仍然有效。

private static void generateKeyPair(Context context, String alias)
        throws GeneralSecurityException {
    final Calendar start = new GregorianCalendar();
    final Calendar end = new GregorianCalendar();
    end.add(Calendar.YEAR, 100);
    final KeyGenParameterSpec keyGenParameterSpec = new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT)
            .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)

            .build();
    final KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
    gen.initialize(keyGenParameterSpec);
    gen.generateKeyPair();
}
Run Code Online (Sandbox Code Playgroud)

现在,我可以保护RSA密钥,使其通过删除该PURPOSE_DECRYPT属性而不能用于解密。如预期的那样,该Cipher.unwrap方法停止工作,Incompatible purpose然后引发异常。

因此,我需要的是一个保护属性,其中普通的解密功能被阻止了,但允许我正在寻找的这种“安全导入功能”。诸如“ PURPOSE_IMPORT”之类的东西显然不存在。

div*_*eek 10

从 API 级别 28 (Android Pie) 开始,您要查找的内容现在已经存在。要使用您,您需要:

  • 创建一个包装密钥对,一个用途为 PURPOSE_WRAP_KEY 的 RSA 密钥对。您还应该为公钥生成证明,以验证私钥是安全硬件中的密钥库密钥。
  • 将公钥(和证明)从您的应用程序发送到将提供包装的对称密钥的服务器。
  • 在服务器上,您需要包装对称密钥。这不仅仅涉及对其进行加密,因为包装器不仅需要包含密钥材料,还需要包含定义如何使用密钥的授权列表。这是通过将密钥和授权信息封装在 ASN.1 DER 编码结构中来完成的,根据此处记录的架构。CTS 测试中有一些示例包装代码。请注意,如果此格式看起来过于复杂(例如可选的“掩码密钥”),那是因为在未来的 Android 版本中将有相应的安全导出功能,并且其用例需要额外的复杂性。安全导出功能没有进入 Q,但可能会进入 R。
  • 将包装好的密钥发送到应用程序,应用程序必须创建一个WrappedKeyEntry并使用Keystore.setEntry()来存储它。

这应该适用于 API 级别 28 的任何设备。 但是,如果设备的 Keymaster 版本 < 4(请参阅证明证书以了解存在的 Keymaster 版本),则解包操作会将包装好的密钥材料返回给 Android用户空间。Keymaster 版本 4(或更高版本)会将未包装的材料保存在安全硬件中,但由于较低版本不支持已包装的密钥功能,因此必须对其进行模拟。

如果您的 Keymaster 版本较低,那么当您创建 PURPOSE_WRAP_KEY 密钥对时,安全硬件实际请求的是 PURPOSE_DECRYPT 密钥对。然后,当您执行导入时,密钥库守护程序使用此 PURPOSE_DECRYPT 私钥从包装器解密机密,然后将机密导入安全硬件并擦除保存它的用户空间内存。因此,密钥材料在密钥库守护进程的内存中只存在几分之一毫秒。同样,如果设备具有 Keymaster 版本 4+,它只会在安全硬件中展开,永远不会离开。