【问题标题】:Using RSAPrivateKey (and not RSAPrivateCrtKey) to save an RSA private key to Android KeyStore使用 RSAPrivateKey(而不是 RSAPrivateCrtKey)将 RSA 私钥保存到 Android KeyStore
【发布时间】:2021-05-20 03:21:17
【问题描述】:

我正在开发一个在服务器和 Android 应用之间实现 RSA threshold signature 的应用程序。

服务器和应用程序各有一份 RSA 私钥。在 Android/Java 中,有两个类用于保存 RSA 私钥:

  1. RSAPrivateKey
  2. RSAPrivateCrtKey

后者持有额外的信息(如模数分解、CRT coefficients等)以提高签名效率。但是,我无法存储额外的信息,因为它们会泄露主私钥。

我尝试在 Android KeyStore 中存储前一种类型的对象 (RSAPrivateKey),但似乎密钥库只接受后一种类型 (RSAPrivateCrtKey)。

这是有效的代码摘录(DummyCert 只是 java.security.cert.X509Certificate 的一个实现,其所有方法都返回默认值):

BigInteger n = new BigInteger("...");
BigInteger d = new BigInteger("...");
BigInteger e = new BigInteger("...");
BigInteger primeP = new BigInteger("...");
BigInteger primeQ = new BigInteger("...");
BigInteger primeExponentP = new BigInteger("...");
BigInteger primeExponentQ = new BigInteger("...");
BigInteger crt = new BigInteger("...");

KeySpec keySpec = new RSAPrivateCrtKeySpec(n, e, d, primeP, primeQ, primeExponentP, primeExponentQ, crt);

KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey sk = kf.generatePrivate(keySpec);

Certificate[] dummy = new Certificate[]{new DummyCert()};

KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
keyStore.setKeyEntry(KEY_ALIAS, sk, null, dummy);

但是,如果我们将keySpec 的定义更改为:

KeySpec keySpec = new RSAPrivateKeySpec(n, d);

抛出以下异常:

java.lang.RuntimeException: error:04000090:RSA routines:OPENSSL_internal:VALUE_MISSING
    at com.android.org.conscrypt.NativeCrypto.EVP_marshal_private_key(Native Method)
    at com.android.org.conscrypt.OpenSSLRSAPrivateKey.getEncoded(OpenSSLRSAPrivateKey.java:207)
    at android.security.keystore.AndroidKeyStoreSpi.setPrivateKeyEntry(AndroidKeyStoreSpi.java:480)
    at android.security.keystore.AndroidKeyStoreSpi.engineSetKeyEntry(AndroidKeyStoreSpi.java:294)
    at java.security.KeyStore.setKeyEntry(KeyStore.java:1179)
    at com.example.keystoretester.MainActivity.createKeyStoreEntry(MainActivity.java:68)
    at com.example.keystoretester.MainActivity.onCreate(MainActivity.java:31)
    at android.app.Activity.performCreate(Activity.java:8000)
    at android.app.Activity.performCreate(Activity.java:7984)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3422)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
    at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
    at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loop(Looper.java:223)
    at android.app.ActivityThread.main(ActivityThread.java:7656)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

PS:

我逐行跟踪setKeyEntry 的代码。似乎AndroidKeyStoreSpi 的这一部分抛出了异常,可能调用了 native 函数:

int errorCode = mKeyStore.importKey(
        Credentials.USER_PRIVATE_KEY + alias,
        importArgs,
        KeymasterDefs.KM_KEY_FORMAT_PKCS8,
        pkcs8EncodedPrivateKeyBytes,
        mUid,
        flags,
        resultingKeyCharacteristics);
if (errorCode != KeyStore.NO_ERROR) {
    throw new KeyStoreException("Failed to store private key",
            KeyStore.getKeyStoreException(errorCode));
}

可以通过直接访问密钥存储服务来规避上述检查。 here 解释了一个这样的例子。但代码适用于 Android 4.x,它可能太旧了。我只是想在进一步挖掘之前获得专家意见。

【问题讨论】:

  • 您基本上是想对所有内容进行分解。 previousPrime()previousProbablePrime() 是您需要的一种方法。我只能这么说。
  • ...但是,我无法存储额外的信息,因为它们会泄露主私钥...。我不知道这到底是什么意思,但给定只需私有指数(以及公共指数和模数),您就可以快速导出私钥的 CRT 形式中存在的所有剩余参数。因此,实际上,RSAPrivateCrtKey 中泄露的任何内容也会在RSAPrivateKey 中泄露。
  • @PresidentJamesK.Polk:这正是我不将公共指数与私钥一起存储并使用 DummyCert 类的原因。准确地说,设 (e,n) 为公钥,(d,n) 为私钥。提取两个私钥共享 (d1, n) 和 (d2, n),其中 d1*d2 = d (mod phi(n))。该应用仅存储 (d1, n),但存储其“对应的”公钥 (e1, n),其中 e1 = (d1)^-1 mod phi(n)。

标签: java android cryptography rsa keystore


【解决方案1】:

KeyProtection 提供了更现代的界面,您可能想尝试一下。

EVP_marshal_private_key 采用 PrivateKeyInfo 结构,因此从技术上讲,它应该可以使用不同格式的密钥。看OpenSSLRSAPrivateKey的代码,好像是n,应该够了,可能是你的key格式有问题?

低级API变化很大,官方不支持,所以不太推荐使用。

【讨论】:

  • 感谢您的回答。我尝试将 KeyProtection 类合并到我的代码中。 Here 是结果。不幸的是,如果您将crtKey 更改为false in this line of code,您将收到问题中提到的错误。
  • 对我来说似乎是一个错误,您是否尝试过不同的 Android 版本和设备/模拟器?
  • 我也遇到了@M.S.Dousti 提到的是他的问题,并在 Nikolay 建议的不同 api 级别(都在物理设备/模拟器上)的不同设备上尝试了他的解决方案,但我得到了错误。关于该错误可能会给您提供线索的一件事是,当我在具有 api 级别 29 的设备上运行示例时,我得到 OPENSSL_internal:VALUE_MISSING ... 与错误 M.S. 相同。 Dousti 报告了,但是当我在 api 级别 23 的设备上运行示例时,我得到 java.security.KeyStoreException: Failed to store private key android.security.KeyStoreException: Invalid key blob error
【解决方案2】:

AndroidKeyStore 似乎只接受易于编码RFC 3447 中定义的ASN.1 格式的RSA 私钥。这个限制是从底层的Keymaster 继承而来的,从目前为止 AOSP 源代码中的公共测试用例来看,它保证接受已经以这种方式编码的 RSA 私钥。

这意味着如果您将 RSA 密钥导入密钥库,则需要披露主要因素。

【讨论】:

  • 在使用 Java API 时确实如此。但问题是,我可以直接与 keystore 服务对话吗?请参阅问题末尾的示例链接。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-04
  • 2013-12-06
  • 2012-06-26
  • 2017-07-02
相关资源
最近更新 更多