将加密的AES密钥导入Android密钥库,并将其存储在新别名下
我只是在熟悉 Android Keystore API。我发现以下功能可用:
- 至少在某些设备上,Android密钥库是硬件支持的,这意味着加密操作在安全环境(TEE)中运行。
- 当密钥库得到硬件支持时,可以将私有 RSA 密钥以及在密钥库中创建的秘密对称密钥配置为从不离开密钥库,并且即使具有 root 访问权限也无法读出原始密钥。
我现在想知道以下是否可能:
- 生成公钥/私钥对,其中私钥永远不会离开密钥库
- 将此对的公钥上传到服务器
- 在服务器上:创建一个随机对称的 AES 密钥,并使用用户上传的公共 RSA 密钥对其进行加密
- 在设备上:下载此加密的 AES 密钥
- 将其导入硬件支持的密钥库,以便使用该对的私钥在其中对其进行解密,并存储在新的别名下
- 使用此新密钥别名执行对称加密和解密
1-4应该是可能的,我现在缺少的环节是第5点。在此列表中。有人可以帮助我并告诉我这是否可能和/或指向我正确的API参考吗?
但在我看来,密钥的解包似乎发生在正常环境中,解密的AES密钥将在应用程序中可用,这不符合我的安全要求。
更新:
我使用链接创建了一个小型测试项目,下面是两个代码片段:SecretKeyWrapper
第一个执行以下操作:
- 创建一个随机的AES密钥(不在密钥库中,这是稍后在服务器上发生的情况)。显然,可以从生成的对象中检索原始密钥,这不是问题,因为服务器可以知道密钥。
SecretKey
- 使用在客户端的 Android 密钥库中创建的 RSA 公钥加密/包装密钥(这也会发生在服务器上)。
- 使用 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();
我想实现的是,来自 [3] 的未包装密钥存储在密钥库中,具有新的别名,而不返回原始密钥。当然,我可以很容易地将对象导入到这里的密钥库中,但问题是,此时可以从对象中检索原始密钥,并使用导致安全漏洞的语句[4]。很明显,解包/解密已经发生在密钥库/TEE 中,因为用于解密的私有 RSA 密钥存在于密钥库中,无法检索。SecretKey
如果我将此与在密钥库中创建随机秘密AES密钥的情况进行比较,我注意到返回了不同的类型(实现接口)。在上面的示例中,类型为 ,而对于从 Android 密钥库返回的密钥(请参阅下面的代码段 2),使用“不透明”类型,其中该方法始终返回 null。在下面的示例中,的类型为 。SecretKey
SecretKeySpec
getEncoded()
keyAesKeystore
AndroidKeyStoreSecretKey
片段 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();
因此,重新表述一个问题:是否有可能以某种方式将RSA包装的AES密钥安全地导入Android密钥库,而不会泄露应用程序的密钥?
更新 2:
@Robert在下面的答案中绝对有效,即无论在TEE中还是在Rich OS(应用程序)中进行解包实际上并不重要,因为应用程序(或篡改的版本)总是可以在以后(在拦截包装的密钥后)只是“使用”密钥库中的私有RSA密钥来解开AES密钥(根本不需要访问原始私钥)。
这是另一个想法:我发现可以为Android密钥库中的密钥设置密钥保护参数(请参阅此处)。
的链接实现不使用此类保护参数。按如下方式更改方法并添加和属性后,一切仍然有效。SecretKeyWrapper
generateKeyPair
PURPOSE_DECRYPT
PURPOSE_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();
}
我现在可以通过删除该属性来保护RSA密钥,使其不能用于解密。正如预期的那样,该方法停止工作并引发异常。PURPOSE_DECRYPT
Cipher.unwrap
Incompatible purpose
因此,我需要的是一个保护属性,其中普通解密功能被阻止,但允许我正在寻找的这种“安全导入功能”。像“”这样的东西显然不存在。PURPOSE_IMPORT