android.security.KeyStoreException:使用 RSA 签名时不兼容的摘要
android.security.KeyStoreException: Incompatible digest when signing with RSA
当我尝试在我从外部获取的 android 应用程序中签署哈希值时,出现上述异常。
生成密钥对的代码是:
public static KeyPair generateKeyPair(Context context, String username) throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEY_STORE);
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
end.add(Calendar.YEAR, 1);
AlgorithmParameterSpec parameterSpec;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
parameterSpec = new KeyGenParameterSpec.Builder(MY_KEY_ALIAS, KeyProperties.PURPOSE_SIGN)
.setKeySize(KEY_SIZE)
.setCertificateSubject(usernameToSubject(username))
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA1)
.setCertificateNotBefore(start.getTime())
.setCertificateNotAfter(end.getTime())
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
.build();
} else {
// Here I build the keys for older versions. This is not part of my problem
}
keyPairGenerator.initialize(parameterSpec);
return keyPairGenerator.generateKeyPair();
}
稍后我对从外部获得的哈希进行签名:
public static byte[] signHash(byte[] hashToSign) {
try {
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
keyStore.load(null);
KeyStore.PrivateKeyEntry keyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(MY_KEY_ALIAS, null);
PrivateKey privateKey = keyEntry.getPrivateKey();
DigestAlgorithmIdentifierFinder hashAlgorithmFinder = new DefaultDigestAlgorithmIdentifierFinder();
AlgorithmIdentifier hashingAlgorithmIdentifier = hashAlgorithmFinder.find(KeyProperties.DIGEST_SHA256);
DigestInfo digestInfo = new DigestInfo(hashingAlgorithmIdentifier, hashToSign);
byte[] hashToEncrypt = digestInfo.getEncoded();
Cipher cipher = Cipher.getInstance("RSA/ECB/Pkcs1Padding");
cipher.init(Cipher.ENCRYPT_MODE, privateKey); // <= the exception is thrown here
return cipher.doFinal(hashToEncrypt);
} catch (Throwable e) {
Log.e("KeyStoreWrapper", "Error while signing: ", e);
}
return "Could not sign the message.".getBytes(StandardCharsets.UTF_16LE);
}
我得到的错误是:
java.security.InvalidKeyException: Keystore operation failed
at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1004)
at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1024)
at android.security.keystore.KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(KeyStoreCryptoOperationUtils.java:53)
at android.security.keystore.KeyStoreCryptoOperationUtils.getExceptionForCipherInit(KeyStoreCryptoOperationUtils.java:89)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.ensureKeystoreOperationInitialized(AndroidKeyStoreCipherSpiBase.java:263)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineInit(AndroidKeyStoreCipherSpiBase.java:108)
at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:612)
at javax.crypto.Cipher.tryCombinations(Cipher.java:532)
at javax.crypto.Cipher.getSpi(Cipher.java:437)
at javax.crypto.Cipher.init(Cipher.java:815)
at javax.crypto.Cipher.init(Cipher.java:774)
at de.new_frontiers.m2fa.security.KeyStoreWrapper.signHash(KeyStoreWrapper.java:186)
at de.new_frontiers.m2fa.bluetooth.BluetoothHandler.handleMessage(BluetoothHandler.java:93)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:158)
at android.app.ActivityThread.main(ActivityThread.java:7229)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
Caused by: android.security.KeyStoreException: Incompatible digest
at android.security.KeyStore.getKeyStoreException(KeyStore.java:944)
at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1024)
at android.security.keystore.KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(KeyStoreCryptoOperationUtils.java:53)
at android.security.keystore.KeyStoreCryptoOperationUtils.getExceptionForCipherInit(KeyStoreCryptoOperationUtils.java:89)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.ensureKeystoreOperationInitialized(AndroidKeyStoreCipherSpiBase.java:263)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineInit(AndroidKeyStoreCipherSpiBase.java:108)
at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:612)
at javax.crypto.Cipher.tryCombinations(Cipher.java:532)
at javax.crypto.Cipher.getSpi(Cipher.java:437)
at javax.crypto.Cipher.init(Cipher.java:815)
at javax.crypto.Cipher.init(Cipher.java:774)
at com.mycompany.security.KeyStoreWrapper.signHash(KeyStoreWrapper.java:186)
在 android documentation 我看到:
For signing and verification operations a digest must be specified in
the additional_params argument of begin. If the specified digest is
not in the digests associated with the key, the operation must fail
with KM_ERROR_INCOMPATIBLE_DIGEST.
但是如您所见,我使用 KeyProperties.DIGEST_SHA256
创建了密钥对并将 DigestInfo 设置为相同的算法。所以我不明白为什么我会得到错误"Incompatible digest"。任何人都可以阐明这一点吗?
哦,对于任何想知道为什么我不使用 Signature.sign()
的人:这需要明文进行签名,然后创建一个散列、一个 DigestInfo,然后使用私钥对其进行加密。我已经从外面得到了哈希值,这是我无法改变的。
使用 RSA/ECB/PKCS1Padding
的私钥加密可在 AndroidKeyStore 上从 Android 18 获得,因此您应该能够使用收到的哈希执行有效的数字签名。
我想问题是将密钥用途设置为签名而不是加密(这正是您想要密钥的目的)。试试这个:
parameterSpec = new KeyGenParameterSpec.Builder(MY_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | | KeyProperties.PURPOSE_DECRYPT)
.setKeySize(KEY_SIZE)
.setCertificateSubject(usernameToSubject(username))
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA1)
.setCertificateNotBefore(start.getTime())
.setCertificateNotAfter(end.getTime())
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.build();
还要检查 hashToSign
确实是 SHA-256
(32 字节)
pedrofbs 回答帮助我最终把事情做好了。我已经将密钥的用途更改为我对他的回答的评论中提到的值:KeyProperties.PURPOSE_SIGN|KeyProperties.PURPOSE_ENCRYPT|KeyProperties.PURPOSE_DECRYPT
,但忘记调用 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
。非常感谢 pedrofs 发现了这一点!
不幸的是,它仍然没有用。在密钥上摆弄不同的设置,我意识到我没有用密码、pin 或其他东西保护我用于测试的设备。因此,将 .setUserAuthenticationRequired(false)
添加到 KeyGenParameterSpec 就可以了。我知道这不安全,必须更改,但目前我只是在做概念验证,所以没关系。 :-)
以防其他人偶然发现这个问题:这里是密钥生成的完整工作代码:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
parameterSpec = new KeyGenParameterSpec.Builder(LOGON_KEY_ALIAS, KeyProperties.PURPOSE_SIGN|KeyProperties.PURPOSE_ENCRYPT|KeyProperties.PURPOSE_DECRYPT)
.setKeySize(KEY_SIZE)
.setUserAuthenticationRequired(false)
.setCertificateSubject(usernameToSubject(username))
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA1)//, KeyProperties.DIGEST_NONE, KeyProperties.DIGEST_SHA224, KeyProperties.DIGEST_SHA384, KeyProperties.DIGEST_SHA512, KeyProperties.DIGEST_MD5)
.setCertificateNotBefore(start.getTime())
.setCertificateNotAfter(end.getTime())
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.build();
}
当我尝试在我从外部获取的 android 应用程序中签署哈希值时,出现上述异常。
生成密钥对的代码是:
public static KeyPair generateKeyPair(Context context, String username) throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, ANDROID_KEY_STORE);
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
end.add(Calendar.YEAR, 1);
AlgorithmParameterSpec parameterSpec;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
parameterSpec = new KeyGenParameterSpec.Builder(MY_KEY_ALIAS, KeyProperties.PURPOSE_SIGN)
.setKeySize(KEY_SIZE)
.setCertificateSubject(usernameToSubject(username))
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA1)
.setCertificateNotBefore(start.getTime())
.setCertificateNotAfter(end.getTime())
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
.build();
} else {
// Here I build the keys for older versions. This is not part of my problem
}
keyPairGenerator.initialize(parameterSpec);
return keyPairGenerator.generateKeyPair();
}
稍后我对从外部获得的哈希进行签名:
public static byte[] signHash(byte[] hashToSign) {
try {
KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
keyStore.load(null);
KeyStore.PrivateKeyEntry keyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(MY_KEY_ALIAS, null);
PrivateKey privateKey = keyEntry.getPrivateKey();
DigestAlgorithmIdentifierFinder hashAlgorithmFinder = new DefaultDigestAlgorithmIdentifierFinder();
AlgorithmIdentifier hashingAlgorithmIdentifier = hashAlgorithmFinder.find(KeyProperties.DIGEST_SHA256);
DigestInfo digestInfo = new DigestInfo(hashingAlgorithmIdentifier, hashToSign);
byte[] hashToEncrypt = digestInfo.getEncoded();
Cipher cipher = Cipher.getInstance("RSA/ECB/Pkcs1Padding");
cipher.init(Cipher.ENCRYPT_MODE, privateKey); // <= the exception is thrown here
return cipher.doFinal(hashToEncrypt);
} catch (Throwable e) {
Log.e("KeyStoreWrapper", "Error while signing: ", e);
}
return "Could not sign the message.".getBytes(StandardCharsets.UTF_16LE);
}
我得到的错误是:
java.security.InvalidKeyException: Keystore operation failed
at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1004)
at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1024)
at android.security.keystore.KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(KeyStoreCryptoOperationUtils.java:53)
at android.security.keystore.KeyStoreCryptoOperationUtils.getExceptionForCipherInit(KeyStoreCryptoOperationUtils.java:89)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.ensureKeystoreOperationInitialized(AndroidKeyStoreCipherSpiBase.java:263)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineInit(AndroidKeyStoreCipherSpiBase.java:108)
at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:612)
at javax.crypto.Cipher.tryCombinations(Cipher.java:532)
at javax.crypto.Cipher.getSpi(Cipher.java:437)
at javax.crypto.Cipher.init(Cipher.java:815)
at javax.crypto.Cipher.init(Cipher.java:774)
at de.new_frontiers.m2fa.security.KeyStoreWrapper.signHash(KeyStoreWrapper.java:186)
at de.new_frontiers.m2fa.bluetooth.BluetoothHandler.handleMessage(BluetoothHandler.java:93)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:158)
at android.app.ActivityThread.main(ActivityThread.java:7229)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
Caused by: android.security.KeyStoreException: Incompatible digest
at android.security.KeyStore.getKeyStoreException(KeyStore.java:944)
at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1024)
at android.security.keystore.KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(KeyStoreCryptoOperationUtils.java:53)
at android.security.keystore.KeyStoreCryptoOperationUtils.getExceptionForCipherInit(KeyStoreCryptoOperationUtils.java:89)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.ensureKeystoreOperationInitialized(AndroidKeyStoreCipherSpiBase.java:263)
at android.security.keystore.AndroidKeyStoreCipherSpiBase.engineInit(AndroidKeyStoreCipherSpiBase.java:108)
at javax.crypto.Cipher.tryTransformWithProvider(Cipher.java:612)
at javax.crypto.Cipher.tryCombinations(Cipher.java:532)
at javax.crypto.Cipher.getSpi(Cipher.java:437)
at javax.crypto.Cipher.init(Cipher.java:815)
at javax.crypto.Cipher.init(Cipher.java:774)
at com.mycompany.security.KeyStoreWrapper.signHash(KeyStoreWrapper.java:186)
在 android documentation 我看到:
For signing and verification operations a digest must be specified in the additional_params argument of begin. If the specified digest is not in the digests associated with the key, the operation must fail with KM_ERROR_INCOMPATIBLE_DIGEST.
但是如您所见,我使用 KeyProperties.DIGEST_SHA256
创建了密钥对并将 DigestInfo 设置为相同的算法。所以我不明白为什么我会得到错误"Incompatible digest"。任何人都可以阐明这一点吗?
哦,对于任何想知道为什么我不使用 Signature.sign()
的人:这需要明文进行签名,然后创建一个散列、一个 DigestInfo,然后使用私钥对其进行加密。我已经从外面得到了哈希值,这是我无法改变的。
使用 RSA/ECB/PKCS1Padding
的私钥加密可在 AndroidKeyStore 上从 Android 18 获得,因此您应该能够使用收到的哈希执行有效的数字签名。
我想问题是将密钥用途设置为签名而不是加密(这正是您想要密钥的目的)。试试这个:
parameterSpec = new KeyGenParameterSpec.Builder(MY_KEY_ALIAS, KeyProperties.PURPOSE_ENCRYPT | | KeyProperties.PURPOSE_DECRYPT)
.setKeySize(KEY_SIZE)
.setCertificateSubject(usernameToSubject(username))
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA1)
.setCertificateNotBefore(start.getTime())
.setCertificateNotAfter(end.getTime())
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.build();
还要检查 hashToSign
确实是 SHA-256
(32 字节)
pedrofbs 回答帮助我最终把事情做好了。我已经将密钥的用途更改为我对他的回答的评论中提到的值:KeyProperties.PURPOSE_SIGN|KeyProperties.PURPOSE_ENCRYPT|KeyProperties.PURPOSE_DECRYPT
,但忘记调用 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
。非常感谢 pedrofs 发现了这一点!
不幸的是,它仍然没有用。在密钥上摆弄不同的设置,我意识到我没有用密码、pin 或其他东西保护我用于测试的设备。因此,将 .setUserAuthenticationRequired(false)
添加到 KeyGenParameterSpec 就可以了。我知道这不安全,必须更改,但目前我只是在做概念验证,所以没关系。 :-)
以防其他人偶然发现这个问题:这里是密钥生成的完整工作代码:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
parameterSpec = new KeyGenParameterSpec.Builder(LOGON_KEY_ALIAS, KeyProperties.PURPOSE_SIGN|KeyProperties.PURPOSE_ENCRYPT|KeyProperties.PURPOSE_DECRYPT)
.setKeySize(KEY_SIZE)
.setUserAuthenticationRequired(false)
.setCertificateSubject(usernameToSubject(username))
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA1)//, KeyProperties.DIGEST_NONE, KeyProperties.DIGEST_SHA224, KeyProperties.DIGEST_SHA384, KeyProperties.DIGEST_SHA512, KeyProperties.DIGEST_MD5)
.setCertificateNotBefore(start.getTime())
.setCertificateNotAfter(end.getTime())
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
.build();
}