it-swarm.com.de

Android Fingerprint API Verschlüsselung und Entschlüsselung

Ich verwende die Android M Fingerprint API, um Benutzern die Anmeldung bei der Anwendung zu ermöglichen. Dazu müsste ich den Benutzernamen und das Passwort auf dem Gerät speichern. Momentan habe ich das Login und die Fingerprint API, aber der Benutzername und das Passwort werden beide als Klartext gespeichert. Ich möchte das Kennwort verschlüsseln, bevor ich es speichere, und es abrufen können, nachdem sich der Benutzer mit seinem Fingerabdruck authentifiziert hat.

Ich habe große Schwierigkeiten, das zum Laufen zu bringen. Ich habe versucht, das, was ich kann, aus den Android Security-Beispielen anzuwenden, aber jedes Beispiel scheint nur mit Verschlüsselung oder Signierung und niemals mit Entschlüsselung umzugehen.

Was ich bisher habe, ist, dass ich eine Instanz von AndroidKeyStore, KeyPairGenerator und Cipher mit asymmetrischer Kryptographie erhalten muss, um die Verwendung von Android KeyGenParameterSpec.Builder().setUserAuthenticationRequired(true) . Der Grund für die asymmetrische Kryptografie ist, dass die Methode setUserAuthenticationRequiredanydie Verwendung des Schlüssels blockiert, wenn der Benutzer nicht authentifiziert ist, aber:

Diese Berechtigung gilt nur für Operationen mit geheimen Schlüsseln und privaten Schlüsseln. Operationen mit öffentlichen Schlüsseln sind nicht eingeschränkt.

Dadurch sollte es mir möglich sein, das Kennwort mit dem öffentlichen Schlüssel zu verschlüsseln, bevor sich der Benutzer mit seinem Fingerabdruck authentifiziert, und es dann erst nach der Authentifizierung des Benutzers mit dem privaten Schlüssel zu entschlüsseln.

public KeyStore getKeyStore() {
    try {
        return KeyStore.getInstance("AndroidKeyStore");
    } catch (KeyStoreException exception) {
        throw new RuntimeException("Failed to get an instance of KeyStore", exception);
    }
}

public KeyPairGenerator getKeyPairGenerator() {
    try {
        return KeyPairGenerator.getInstance("EC", "AndroidKeyStore");
    } catch(NoSuchAlgorithmException | NoSuchProviderException exception) {
        throw new RuntimeException("Failed to get an instance of KeyPairGenerator", exception);
    }
}

public Cipher getCipher() {
    try {
        return Cipher.getInstance("EC");
    } catch(NoSuchAlgorithmException | NoSuchPaddingException exception) {
        throw new RuntimeException("Failed to get an instance of Cipher", exception);
    }
}

private void createKey() {
    try {
        mKeyPairGenerator.initialize(
                new KeyGenParameterSpec.Builder(KEY_ALIAS,
                        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                        .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1")
                        .setUserAuthenticationRequired(true)
                        .build());
        mKeyPairGenerator.generateKeyPair();
    } catch(InvalidAlgorithmParameterException exception) {
        throw new RuntimeException(exception);
    }
}

private boolean initCipher(int opmode) {
    try {
        mKeyStore.load(null);

        if(opmode == Cipher.ENCRYPT_MODE) {
            PublicKey key = mKeyStore.getCertificate(KEY_ALIAS).getPublicKey();
            mCipher.init(opmode, key);
        } else {
            PrivateKey key = (PrivateKey) mKeyStore.getKey(KEY_ALIAS, null);
            mCipher.init(opmode, key);
        }

        return true;
    } catch (KeyPermanentlyInvalidatedException exception) {
        return false;
    } catch(KeyStoreException | CertificateException | UnrecoverableKeyException
            | IOException | NoSuchAlgorithmException | InvalidKeyException
            | InvalidAlgorithmParameterException exception) {
        throw new RuntimeException("Failed to initialize Cipher", exception);
    }
}

private void encrypt(String password) {
    try {
        initCipher(Cipher.ENCRYPT_MODE);
        byte[] bytes = mCipher.doFinal(password.getBytes());
        String encryptedPassword = Base64.encodeToString(bytes, Base64.NO_WRAP);
        mPreferences.getString("password").set(encryptedPassword);
    } catch(IllegalBlockSizeException | BadPaddingException exception) {
        throw new RuntimeException("Failed to encrypt password", exception);
    }
}

private String decryptPassword(Cipher cipher) {
    try {
        String encryptedPassword = mPreferences.getString("password").get();
        byte[] bytes = Base64.decode(encryptedPassword, Base64.NO_WRAP);
        return new String(cipher.doFinal(bytes));
    } catch (IllegalBlockSizeException | BadPaddingException exception) {
        throw new RuntimeException("Failed to decrypt password", exception);
    }
}

Um ehrlich zu sein, ich bin mir nicht sicher, ob irgendetwas davon richtig ist. Es sind Kleinigkeiten von allem, was ich zu diesem Thema finden konnte. Alles, was ich ändere, löst eine andere Ausnahme aus, und dieser spezielle Build wird nicht ausgeführt, da ich die Cipher nicht instanziieren kann. Es wird ein NoSuchAlgorithmException: No provider found for EC Ausgelöst. Ich habe auch versucht, zu RSA zu wechseln, aber ich erhalte ähnliche Fehler.

Meine Frage lautet also im Grunde: Wie kann ich Klartext unter Android verschlüsseln und zur Entschlüsselung bereitstellen, nachdem der Benutzer durch die Fingerabdruck-API authentifiziert wurde?


Ich habe einige Fortschritte erzielt, hauptsächlich aufgrund der Entdeckung der Informationen auf der Dokumentationsseite KeyGenParameterSpec .

Ich habe getKeyStore, encryptePassword, decryptPassword, getKeyPairGenerator und getCipher größtenteils gleich gehalten, aber ich habe den KeyPairGenerator.getInstance und Cipher.getInstance bis "RSA" bzw. "RSA/ECB/OAEPWithSHA-256AndMGF1Padding".

Ich habe auch den Rest des Codes in RSA anstatt in Elliptic Curve geändert, da nach meinem Verständnis Java 1.7 (und daher Android) die Ver- und Entschlüsselung mit EC nicht unterstützt. Ich habe meine createKeyPair -Methode basierend auf dem Beispiel "RSA-Schlüsselpaar für die Verschlüsselung/Entschlüsselung mit RSA OAEP" auf der Dokumentationsseite geändert:

private void createKeyPair() {
    try {
        mKeyPairGenerator.initialize(
                new KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_DECRYPT)
                        .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
                        .setUserAuthenticationRequired(true)
                        .build());
        mKeyPairGenerator.generateKeyPair();
    } catch(InvalidAlgorithmParameterException exception) {
        throw new RuntimeException(exception);
    }
}

Ich habe auch meine initCipher - Methode geändert, basierend auf dem bekannten Problem in der KeyGenParameterSpec - Dokumentation:

Ein bekannter Fehler in Android 6.0 (API-Stufe 23) führt dazu, dass Berechtigungen im Zusammenhang mit der Benutzerauthentifizierung auch für öffentliche Schlüssel erzwungen werden. Um dieses Problem zu umgehen, extrahieren Sie das Public-Key-Material, das außerhalb von Android Keystore verwendet werden soll.

private boolean initCipher(int opmode) {
    try {
        mKeyStore.load(null);

        if(opmode == Cipher.ENCRYPT_MODE) {
            PublicKey key = mKeyStore.getCertificate(KEY_ALIAS).getPublicKey();

            PublicKey unrestricted = KeyFactory.getInstance(key.getAlgorithm())
                    .generatePublic(new X509EncodedKeySpec(key.getEncoded()));

            mCipher.init(opmode, unrestricted);
        } else {
            PrivateKey key = (PrivateKey) mKeyStore.getKey(KEY_ALIAS, null);
            mCipher.init(opmode, key);
        }

        return true;
    } catch (KeyPermanentlyInvalidatedException exception) {
        return false;
    } catch(KeyStoreException | CertificateException | UnrecoverableKeyException
            | IOException | NoSuchAlgorithmException | InvalidKeyException
            | InvalidAlgorithmParameterException exception) {
        throw new RuntimeException("Failed to initialize Cipher", exception);
    }
}

Jetzt kann ich das Passwort verschlüsseln und das verschlüsselte Passwort speichern. Aber wenn ich das verschlüsselte Passwort erhalte und versuche zu entschlüsseln, erhalte ich einen KeyStoreException Unbekannten Fehler ...

03-15 10:06:58.074 14702-14702/com.example.app E/LoginFragment: Failed to decrypt password
        javax.crypto.IllegalBlockSizeException
            at Android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.Java:486)
            at javax.crypto.Cipher.doFinal(Cipher.Java:1502)
            at com.example.app.ui.fragment.util.LoginFragment.onAuthenticationSucceeded(LoginFragment.Java:251)
            at com.example.app.ui.controller.FingerprintCallback.onAuthenticationSucceeded(FingerprintCallback.Java:21)
            at Android.support.v4.hardware.fingerprint.FingerprintManagerCompat$Api23FingerprintManagerCompatImpl$1.onAuthenticationSucceeded(FingerprintManagerCompat.Java:301)
            at Android.support.v4.hardware.fingerprint.FingerprintManagerCompatApi23$1.onAuthenticationSucceeded(FingerprintManagerCompatApi23.Java:96)
            at Android.hardware.fingerprint.FingerprintManager$MyHandler.sendAuthenticatedSucceeded(FingerprintManager.Java:805)
            at Android.hardware.fingerprint.FingerprintManager$MyHandler.handleMessage(FingerprintManager.Java:757)
            at Android.os.Handler.dispatchMessage(Handler.Java:102)
            at Android.os.Looper.loop(Looper.Java:148)
            at Android.app.ActivityThread.main(ActivityThread.Java:5417)
            at Java.lang.reflect.Method.invoke(Native Method)
            at com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.Java:726)
            at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:616)
        Caused by: Android.security.KeyStoreException: Unknown error
            at Android.security.KeyStore.getKeyStoreException(KeyStore.Java:632)
            at Android.security.keystore.KeyStoreCryptoOperationChunkedStreamer.doFinal(KeyStoreCryptoOperationChunkedStreamer.Java:224)
            at Android.security.keystore.AndroidKeyStoreCipherSpiBase.engineDoFinal(AndroidKeyStoreCipherSpiBase.Java:473)
            at javax.crypto.Cipher.doFinal(Cipher.Java:1502) 
            at com.example.app.ui.fragment.util.LoginFragment.onAuthenticationSucceeded(LoginFragment.Java:251) 
            at com.example.app.ui.controller.FingerprintCallback.onAuthenticationSucceeded(FingerprintCallback.Java:21) 
            at Android.support.v4.hardware.fingerprint.FingerprintManagerCompat$Api23FingerprintManagerCompatImpl$1.onAuthenticationSucceeded(FingerprintManagerCompat.Java:301) 
            at Android.support.v4.hardware.fingerprint.FingerprintManagerCompatApi23$1.onAuthenticationSucceeded(FingerprintManagerCompatApi23.Java:96) 
            at Android.hardware.fingerprint.FingerprintManager$MyHandler.sendAuthenticatedSucceeded(FingerprintManager.Java:805) 
            at Android.hardware.fingerprint.FingerprintManager$MyHandler.handleMessage(FingerprintManager.Java:757) 
            at Android.os.Handler.dispatchMessage(Handler.Java:102) 
            at Android.os.Looper.loop(Looper.Java:148) 
            at Android.app.ActivityThread.main(ActivityThread.Java:5417) 
            at Java.lang.reflect.Method.invoke(Native Method) 
            at com.Android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.Java:726) 
            at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:616)
41
Bryan

Ich fand das letzte Puzzleteil im Android Issue Tracker , ein anderer bekannter Fehler führt dazu, dass das uneingeschränkte PublicKey bei Verwendung von OAEP nicht mit dem Cipher kompatibel ist. Die Lösung besteht darin, bei der Initialisierung ein neues OAEPParameterSpec zum Cipher hinzuzufügen:

OAEPParameterSpec spec = new OAEPParameterSpec(
        "SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT);

mCipher.init(opmode, unrestricted, spec);

Unten ist der endgültige Code:

public KeyStore getKeyStore() {
    try {
        return KeyStore.getInstance("AndroidKeyStore");
    } catch (KeyStoreException exception) {
        throw new RuntimeException("Failed to get an instance of KeyStore", exception);
    }
}

public KeyPairGenerator getKeyPairGenerator() {
    try {
        return KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
    } catch(NoSuchAlgorithmException | NoSuchProviderException exception) {
        throw new RuntimeException("Failed to get an instance of KeyPairGenerator", exception);
    }
}

public Cipher getCipher() {
    try {
        return Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
    } catch(NoSuchAlgorithmException | NoSuchPaddingException exception) {
        throw new RuntimeException("Failed to get an instance of Cipher", exception);
    }
}

private void createKeyPair() {
    try {
        mKeyPairGenerator.initialize(
                new KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_DECRYPT)
                        .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
                        .setUserAuthenticationRequired(true)
                        .build());
        mKeyPairGenerator.generateKeyPair();
    } catch(InvalidAlgorithmParameterException exception) {
        throw new RuntimeException("Failed to generate key pair", exception);
    }
}

private boolean initCipher(int opmode) {
    try {
        mKeyStore.load(null);

        if(opmode == Cipher.ENCRYPT_MODE) {
            PublicKey key = mKeyStore.getCertificate(KEY_ALIAS).getPublicKey();

            PublicKey unrestricted = KeyFactory.getInstance(key.getAlgorithm())
                    .generatePublic(new X509EncodedKeySpec(key.getEncoded()));

            OAEPParameterSpec spec = new OAEPParameterSpec(
                    "SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT);

            mCipher.init(opmode, unrestricted, spec);
        } else {
            PrivateKey key = (PrivateKey) mKeyStore.getKey(KEY_ALIAS, null);
            mCipher.init(opmode, key);
        }

        return true;
    } catch (KeyPermanentlyInvalidatedException exception) {
        return false;
    } catch(KeyStoreException | CertificateException | UnrecoverableKeyException
            | IOException | NoSuchAlgorithmException | InvalidKeyException
            | InvalidAlgorithmParameterException exception) {
        throw new RuntimeException("Failed to initialize Cipher", exception);
    }
}

private void encrypt(String password) {
    try {
        initCipher(Cipher.ENCRYPT_MODE);
        byte[] bytes = mCipher.doFinal(password.getBytes());
        String encrypted = Base64.encodeToString(bytes, Base64.NO_WRAP);
        mPreferences.getString("password").set(encrypted);
    } catch(IllegalBlockSizeException | BadPaddingException exception) {
        throw new RuntimeException("Failed to encrypt password", exception);
    }
}

private String decrypt(Cipher cipher) {
    try {
        String encoded = mPreferences.getString("password").get();
        byte[] bytes = Base64.decode(encoded, Base64.NO_WRAP);
        return new String(cipher.doFinal(bytes));
    } catch (IllegalBlockSizeException | BadPaddingException exception) {
        throw new RuntimeException("Failed to decrypt password", exception);
    }
}
36
Bryan