Из-за этого может быть что-то еще, чего я не нашел. Я просто нашел эту параметризацию методом «проб и ошибок» и, таким образом, понятия не имею, как выглядят фактические ограничения криптографической библиотеки.

тоящее время я создаю форму аутентификации «вызов-ответ» для проекта, который мне нужен для моей магистерской диссертации по информатике.

Для этого мне нужно создать подпись RSA-PSS с закрытым ключом, который аутентифицируется по отпечатку пальца, чтобы его можно было использовать только для создания подписи, когда физически присутствует владелец устройства.

Для этого я используюAndroid KeyStore (при поддержке Keymaster / Gatekeeper вARM TrustZone) для генерации пары ключей RSA (KEY_ALGORITHM_RSA) для использования с алгоритмом подписи RSA-PSS (SIGNATURE_PADDING_RSA_PSS) для создания и проверки подписей (PURPOSE_SIGN | PURPOSE_VERIFY). Я также требую аутентификацию пользователя, установив соответствующее свойство вtrue.

Позже, чтобы создать подпись над буферомfinal byte[] messageЯ ...

получить экземплярFingerprintManager оказание услугсоздать экземплярSHA512withRSA/PSS алгоритм подписи (Signature объект)инициализироватьSignature алгоритм подписи закрытым ключом (initSign(...))обернутьSignature возражать вCryptoObject(выполнить некоторые дополнительные проверки)authenticate(...) CryptoObject используя экземплярFingerprintManagerпрохождение (среди прочего)FingerprintManager.AuthenticationCallback вызывается после того, как ключ был аутентифицирован пользователем (касаясь датчика отпечатка пальца на его / ее устройстве)

Внутри обратного вызова использование ключа проходит проверку подлинности, поэтому я ...

извлечьSignature объект изCryptoObject снова оберткаиспользоватьupdate(...) метод наSignature объект для потоковой передачи данных для подписи (message) в алгоритм подписииспользоватьsign() метод наSignature объект для получения подписизакодировать эту подпись какBase64 а такжеprintln(...) это кStderr так что появляется вadb logcat

Я создал пример кода, который настолько минимален, насколько это возможно.

package com.example.andre.minimalsignaturetest;

import android.content.Context;
import android.hardware.fingerprint.FingerprintManager;
import android.os.CancellationSignal;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Base64;
import android.util.Log;
import android.view.View;

import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Enumeration;

/*
 * Sample code to test generation of RSA signature authenticated by fingerprint.
 */
public final class MainActivity extends AppCompatActivity {
    private final String tag;

    /*
     * Creates a new main activity.
     */
    public MainActivity() {
        this.tag = "MinimalSignatureTest";
    }

    /*
     * Generate a 4096-bit key pair for use with the RSA-PSS signature scheme and store it in Android key store.
     *
     * (This is normally done asynchronously, in its own Thread (AsyncTask), with proper parametrization and error handling.)
     */
    public void generate(final View view) {

        /*
         * Generate RSA key pair.
         */
        try {
            KeyPairGenerator generator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
            KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder("authKey", KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY);
            builder.setKeySize(4096);
            builder.setDigests(KeyProperties.DIGEST_SHA512);
            builder.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS);
            builder.setUserAuthenticationRequired(true);
            KeyGenParameterSpec spec = builder.build();
            generator.initialize(spec);
            KeyPair pair = generator.generateKeyPair();
            PublicKey publicKey = pair.getPublic();
            byte[] publicKeyBytes = publicKey.getEncoded();
            String publicKeyString = Base64.encodeToString(publicKeyBytes, Base64.NO_WRAP);
            Log.d(this.tag, "Public key: " + publicKeyString);
        } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
            Log.d(this.tag, "Key generation failed!", e);
        }

    }

    /*
     * Returns the private key stored in the Android key store.
     */
    private PrivateKey getPrivateKey() {

        /*
         * Fetch private key from key store.
         */
        try {
            KeyStore store = KeyStore.getInstance("AndroidKeyStore");
            store.load(null);
            Enumeration<String> enumeration = store.aliases();
            String alias = null;

            /*
             * Find the last key in the key store.
             */
            while (enumeration.hasMoreElements())
                alias = enumeration.nextElement();

            /*
             * Check if we got a key.
             */
            if (alias == null)
                return null;
            else {
                Key key = store.getKey(alias, null);

                /*
                 * Check if it has a private part associated.
                 */
                if (key instanceof PrivateKey)
                    return (PrivateKey) key;
                else
             ,       return null;

            }

        } catch (IOException | NoSuchAlgorithmException | CertificateException | KeyStoreException | UnrecoverableKeyException e) {
            Log.d(this.tag, "Obtaining private key failed!", e);
            return null;
        }

    }

    /*
     * Create an RSA-PSS signature using a key from the Android key store.
     */
    public void sign(final View view) {
        final byte[] message = new byte[0];
        final PrivateKey privateKey = this.getPrivateKey();
        Context context = this.getApplicationContext();
        FingerprintManager manager = (FingerprintManager)context.getSystemService(Context.FINGERPRINT_SERVICE);

        /*
         * Create RSA signature.
         */
        try {
            Signature rsa = Signature.getInstance("SHA512withRSA/PSS");
            rsa.initSign(privateKey);

            /*
             * Check if we have a fingerprint manager.
             */
            if (manager == null)
                Log.d(this.tag, "The fingerprint service is unavailable.");
            else {
                FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(rsa);
                CancellationSignal signal = new CancellationSignal();

                /*
                 * Create callback for fingerprint authentication.
                 */
                FingerprintManager.AuthenticationCallback callback = new FingerprintManager.AuthenticationCallback() {

                    /*
                     * This is called when access to the private key is granted.
                     */
                    @Override public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
                        FingerprintManager.CryptoObject cryptoObject = result.getCryptoObject();
                        Signature rsa = cryptoObject.getSignature();

                        /*
                         * Sign the message.
                         */
                        try {
                            rsa.update(message);
                            byte[] signature = rsa.sign();
                            String signatureString = Base64.encodeToString(signature, Base64.NO_WRAP);
                            Log.d(tag, "Signature: " + signatureString);
                        } catch (SignatureException e) {
                            Log.d(tag, "Signature creation failed!", e);
                        }

                    }

                };

                /*
                 * Check if we have a fingerprint reader.
                 */
                if (!manager.isHardwareDetected())
                    Log.d(this.tag, "Your device does not have a fingerprint reader.");
                else {

                    /*
                     * Check if fingerprints are enrolled.
                     */
                    if (!manager.hasEnrolledFingerprints())
                        Log.d(this.tag, "Your device does not have fingerprints enrolled.");
                    else
                        manager.authenticate(cryptoObject, signal, 0, callback, null);

                }

            }

        } catch (NoSuchAlgorithmException | InvalidKeyException | SecurityException e) {
            Log.d(this.tag, "Signature creation failed!", e);
        }

    }

    /*
     * This is called when the user interface initializes.
     */
    @Override protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(R.layout.activity_main);
    }

}

(Это все еще ~ 200 LOC длиной, но для криптографии с аутентификацией по отпечатку пальца требуется немного кода, чтобы заставить его работать, поэтому я не могу сделать это немного меньше / проще.)

Чтобы проверить это, просто создайте проект с одним действием вAndroid Studio, Вставьте в это действие две кнопки, одну для генерации ключа (т.е. помеченнуюгенерировать) и один для создания подписи (т.е. с пометкойПодписать).

Затем вставьте образец кода в основной вид деятельности и свяжитеonclick события изгенерировать кнопка кpublic void generate(final View view) метод и изПодписать кнопка кpublic void sign(final View view) метод.

Наконец, вставьте следующее в вашAndroidManifest.xmlвнутри на высшем уровне<manifest ...> ... </manifest> тег.

<uses-permission android:name="android.permission.USE_FINGERPRINT" />

Запустите проект и дайтеadb logcat бежать рядом с ним.

После того, как вы нажалигенерировать Кнопка, вы должны увидеть такой вывод в журналах.

07-04 14:46:18.475 6759 6759 D MinimalSignatureTest: Public key: MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICC...

Это открытый ключ пары ключей, которая была сгенерирована.

(Вы также увидите некоторые жалобы по поводу генерации ключей в основном потоке, однако это просто для упрощения примера кода. Реальное приложение выполняет генерацию ключей в своем собственном потоке.)

Затем первый ударПодписать, затем коснитесь датчика. Произойдет следующая ошибка.

keymaster1_device: Update send cmd failed
keymaster1_device: ret: 0
keymaster1_device: resp->status: -30
SoftKeymaster: system/keymaster/openssl_err.cpp, Line 47: error:00000000:invalid library (0):OPENSSL_internal:invalid library (0)
SoftKeymaster: system/keymaster/openssl_err.cpp, Line 88: Openssl error 0, 0
MinimalSignatureTest: java.security.SignatureException: android.security.KeyStoreException: Signature/MAC verification failed
MinimalSignatureTest:   at android.security.keystore.AndroidKeyStoreSignatureSpiBase.engineSign(AndroidKeyStoreSignatureSpiBase.java:333)
MinimalSignatureTest:   at java.security.Signature$Delegate.engineSign(Signature.java:1263)
MinimalSignatureTest:   at java.security.Signature.sign(Signature.java:649)
MinimalSignatureTest:   at com.example.andre.minimalsignaturetest.MainActivity$1.onAuthenticationSucceeded(MainActivity.java:148)
MinimalSignatureTest:   at android.hardware.fingerprint.FingerprintManager$MyHandler.sendAuthenticatedSucceeded(FingerprintManager.java:855)
MinimalSignatureTest:   at android.hardware.fingerprint.FingerprintManager$MyHandler.handleMessage(FingerprintManager.java:803)
MinimalSignatureTest:   at android.os.Handler.dispatchMessage(Handler.java:102)
MinimalSignatureTest:   at android.os.Looper.loop(Looper.java:154)
MinimalSignatureTest:   at android.app.ActivityThread.main(ActivityThread.java:6186)
MinimalSignatureTest:   at java.lang.reflect.Method.invoke(Native Method)
MinimalSignatureTest:   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889)
MinimalSignatureTest:   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779)
MinimalSignatureTest: Caused by: android.security.KeyStoreException: Signature/MAC verification failed
MinimalSignatureTest:   at android.security.KeyStore.getKeyStoreException(KeyStore.java:676)
MinimalSignatureTest:   at android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.java:224)
MinimalSignatureTest:   at android.security.keystore.AndroidKeyStoreSignatureSpiBase.engineSign(AndroidKeyStoreSignatureSpiBase.java:328)
System.err:     ... 11 more

Вот где я застрял.

Странная вещь в том, что я получаюSignature/MAC verification failed как сообщениеSignatureException, Обратите внимание, что это говоритverification failedпока я на самом делеподписание (не проверка) и весь стек показывает, что толькоsignSomething (...) функции называются.

Я попробовал это наLG Nexus 5X с обеих официальных прошивок (Android 7.1.1,N2G47W) и разные (актуальные)LineageOS ночные и все они терпят неудачу в этой точке. Однако, когда я рассматриваю документацию по API, мне кажется, что я делаю правильные вещи, и, честно говоря, не так много вещей, которые вы могли бы сделать по-другому. Это на самом деле кажется довольно очевидным, как это работает.

Обратите внимание, что пока яне требует аутентификации пользователя - и, следовательно, не создавать подпись в методе обратного вызова, но снаружи, сразу послеinitSign(...) - он работает нормально - даже с аппаратной поддержкой ключей Keymaster / Gatekeeper вTrustZone, Но как только я требую аутентификации, - и поэтому делаюupdate(...) а такжеsign() призываетSignature объект внутри обратного вызова - все это разбивается на части.

Я попытался отследить ошибку вOpenSSL библиотека или узнать, что это-30 Код ответа означает, но оба безрезультатно.

Какие-либо предложения? Я прошел долгий путь и реализовал массу вещей, как на стороне сервера, так и наAndroid, чтобы продвинуть этот проект вперед, но теперь я застрял и, кажется, не могу выполнить аутентификацию пользователя, которая криптографически обоснована.

Я пытался заменитьKeyProperties.SIGNATURE_PADDING_RSA_PSS сKeyProperties.SIGNATURE_PADDING_RSA_PKCS1 а такжеSHA512withRSA/PSS сSHA512withRSA, тогдаKeyProperties.DIGEST_SHA512 сKeyProperties.DIGEST_SHA256 а такжеSHA512withRSA сSHA256withRSA, Я также попробовал меньший размер ключа - 2048 бит вместо 4096 бит - все безрезультатно.

Я также пытался сдвинуть команды сinitSign(...), update(...), sign() Процедура снаружи от обратного вызова внутрь или наоборот, однако, это единственная комбинация, которая должна работать. Когда я двигаюсьinitSign(...) внутри обратного вызова, а также вызовauthenticate(...) не удается сjava.lang.IllegalStateException: Crypto primitive not initialized, Когда я двигаюсьupdate(...) а такжеsign() вне обратного вызова, вызовsign() не удается сjava.security.SignatureException: Key user not authenticated, ТакinitSign(...) имеет быть снаружи иsign() имеет быть внутри. кудаupdate(...) случается,появляется чтобы быть некритическим, однако, с семантической точки зрения, имеет смысл держать это вместе с призывом кsign().

Любая помощь очень ценится.

Ответы на вопрос(0)

Ваш ответ на вопрос