android.security.KeyStoreException: несовместимый дайджест при подписании с помощью 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 я вижу:

Для операций подписи и проверки дайджест должен быть указан в аргументе Additional_params команды begin. Если указанный дайджест отсутствует в дайджестах, связанных с ключом, операция должна завершиться с ошибкой KM_ERROR_INCOMPATIBLE_DIGEST.

Но, как видите, я создаю пару ключей с помощью KeyProperties.DIGEST_SHA256 и устанавливаю для DigestInfo тот же алгоритм. Поэтому я не понимаю, почему я получаю сообщение об ошибке «Несовместимый дайджест». Кто-нибудь может пролить свет на это?

О, для тех, кто задается вопросом, почему я не использую Signature.sign(): для этого потребуется открытый текст для подписи, затем создается хеш, DigestInfo, а затем шифруется с помощью закрытого ключа. Я уже получаю хеш извне, и это то, что я не могу изменить.


person Frank    schedule 16.02.2017    source источник


Ответы (2)


Шифрование с помощью закрытого ключа с использованием 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 байта)

person pedrofb    schedule 16.02.2017
comment
Я изменил назначение KeyGenParameterSpec на KeyProperties.PURPOSE_SIGN|KeyProperties.PURPOSE_ENCRYPT|KeyProperties.PURPOSE_DECRYPT и добавил EncrpytionPaddings, но это все равно не работает. Исключение вызывается cipher.init(), когда шифр ничего не знает о хэше для шифрования (это 32 байта). - person Frank; 16.02.2017

Ответ pedrofbs помог мне в конце концов все исправить. Я уже изменил назначение ключа на значение, указанное в моем комментарии к его ответу: KeyProperties.PURPOSE_SIGN|KeyProperties.PURPOSE_ENCRYPT|Key‌​Properties.PURPOSE_D‌​ECRYPT, но забыл позвонить .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1). Так что большое спасибо, pedrofs, за то, что заметили это!

К сожалению, это все еще не сработало. Повозившись с разными настройками ключа, я понял, что не защитил устройство, которое использую для тестирования, паролем, пин-кодом или чем-то еще. Таким образом, добавление .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();
        }
person Frank    schedule 16.02.2017