10

Ich habe eine DialogFragment, die Anmeldung und Fingerabdruck-Authentifizierung für meine Anwendung behandelt. Dieses Fragment verwendet zwei Klassen, die für API 23, KeyGenParameterSpec und KeyPermanentlyInvalidatedException exklusiv sind. Ich hatte den Eindruck, dass ich diese Klassen verwenden könnte, solange ich die Build-Version überprüfen, bevor ich versuche, die Klassen zu initialisieren (here umrandet):Verwendung der nicht unterstützten Ausnahme für die niedrigere Plattform Version

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 
    ... 
} else { 
    ... 
} 

Aber es scheint, dass dies nicht der Fall ist. Wenn ich versuche, diesen Code auf einer Version vor API 20 auszuführen, weist die Dalvik-VM die gesamte Klasse zurück und wirft eine VerifyError. Der Code funktioniert jedoch für API 20 und höher. Wie kann ich diese Methoden in meinem Code verwenden, während der Code weiterhin für frühere API-Ebenen verwendet werden kann?

Der vollständige Stack-Trace ist wie folgt:

05-31 14:35:50.924 11941-11941/com.example.app E/dalvikvm: Could not find class 'android.security.keystore.KeyGenParameterSpec$Builder', referenced from method com.example.app.ui.fragment.util.LoginFragment.createKeyPair 
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: VFY: unable to resolve new-instance 263 (Landroid/security/keystore/KeyGenParameterSpec$Builder;) in Lcom/example/app/ui/fragment/util/LoginFragment; 
05-31 14:35:50.924 11941-11941/com.example.app D/dalvikvm: VFY: replacing opcode 0x22 at 0x000c 
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: VFY: unable to resolve exception class 265 (Landroid/security/keystore/KeyPermanentlyInvalidatedException;) 
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: VFY: unable to find exception handler at addr 0x3f 
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: VFY: rejected Lcom/example/app/ui/fragment/util/LoginFragment;.initializeCipher (I)Z 
05-31 14:35:50.924 11941-11941/cp W/dalvikvm: VFY: rejecting opcode 0x0d at 0x003f 
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: VFY: rejected Lcom/example/app/ui/fragment/util/LoginFragment;.initializeCipher (I)Z 
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: Verifier rejected class Lcom/example/app/ui/fragment/util/LoginFragment; 
05-31 14:35:50.924 11941-11941/com.example.app D/AndroidRuntime: Shutting down VM 
05-31 14:35:50.924 11941-11941/com.example.app W/dalvikvm: threadid=1: thread exiting with uncaught exception (group=0x9cca9b20) 
05-31 14:35:50.934 11941-11941/com.example.app E/AndroidRuntime: FATAL EXCEPTION: main 
     Process: com.example.app, PID: 11941 java.lang.VerifyError: com/example/app/ui/fragment/util/LoginFragment 
      at com.example.app.util.NetworkUtility.login(NetworkUtility.java:41) 
      at com.example.app.ui.activity.AbstractNavActivity.onOptionsItemSelected(AbstractNavActivity.java:68) 
      at android.app.Activity.onMenuItemSelected(Activity.java:2600) 
      at android.support.v4.app.FragmentActivity.onMenuItemSelected(FragmentActivity.java:403) 
      at android.support.v7.app.AppCompatActivity.onMenuItemSelected(AppCompatActivity.java:189) 
      at android.support.v7.view.WindowCallbackWrapper.onMenuItemSelected(WindowCallbackWrapper.java:100) 
      at android.support.v7.view.WindowCallbackWrapper.onMenuItemSelected(WindowCallbackWrapper.java:100) 
      at android.support.v7.app.ToolbarActionBar$2.onMenuItemClick(ToolbarActionBar.java:69) 
      at android.support.v7.widget.Toolbar$1.onMenuItemClick(Toolbar.java:169) 
      at android.support.v7.widget.ActionMenuView$MenuBuilderCallback.onMenuItemSelected(ActionMenuView.java:760) 
      at android.support.v7.view.menu.MenuBuilder.dispatchMenuItemSelected(MenuBuilder.java:811) 
      at android.support.v7.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:152) 
      at android.support.v7.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:958) 
      at android.support.v7.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:948) 
      at android.support.v7.view.menu.MenuPopupHelper.onItemClick(MenuPopupHelper.java:191) 
      at android.widget.AdapterView.performItemClick(AdapterView.java:299) 
      at android.widget.AbsListView.performItemClick(AbsListView.java:1113) 
      at android.widget.AbsListView$PerformClick.run(AbsListView.java:2904) 
      at android.widget.AbsListView$3.run(AbsListView.java:3638) 
      at android.os.Handler.handleCallback(Handler.java:733) 
      at android.os.Handler.dispatchMessage(Handler.java:95) 
      at android.os.Looper.loop(Looper.java:136) 
      at android.app.ActivityThread.main(ActivityThread.java:5017) 
      at java.lang.reflect.Method.invokeNative(Native Method) 
      at java.lang.reflect.Method.invoke(Method.java:515) 
      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779) 
      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595) 
      at dalvik.system.NativeStart.main(Native Method) 

aktualisiert, Code

Die login() Methode ist nur eine bequeme Methode der LoginFragment zu starten:

public static void login(FragmentManager manager) { 
    manager.beginTransAction().add(LoginFragment.newInstance(), null).commit(); 
} 

Die r Code ist in der LoginFragment selbst. Insbesondere die createKeyPair()initializeCipher und Methoden:

public class LoginFragment extends DialogFragment 
     implements TextView.OnEditorActionListener, FingerprintCallback.Callback { 

    ... 

    public static LoginFragment newInstance() { 
     return newInstance(null); 
    } 

    public static LoginFragment newInstance(Intent intent) { 
     LoginFragment fragment = new LoginFragment(); 

     Bundle args = new Bundle(); 
     args.putParcelable(EXTRA_INTENT, intent); 
     fragment.setArguments(args); 

     return fragment; 
    } 

    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     Injector.getContextComponent().inject(this); 
     setStyle(STYLE_NO_TITLE, R.style.DialogTheme); 
     setRetainInstance(true); 
     setCancelable(false); 

     mSaveUsernamePreference = mPreferences.getBoolean(getString(R.string.key_auth_username_retain)); 
     mUseFingerprintPreference = mPreferences.getBoolean(getString(R.string.key_auth_fingerprint)); 
     mUsernamePreference = mPreferences.getString(getString(R.string.key_auth_username)); 
     mPasswordPreference = mPreferences.getString(getString(R.string.key_auth_password)); 
    } 

    @Override 
    public View onCreateView(LayoutInflater inflater, ViewGroup container, 
          Bundle savedInstanceState) { 
     View view = inflater.inflate(R.layout.dialog_login_container, container, false); 
     ButterKnife.bind(this, view); 

     mPasswordView.setOnEditorActionListener(this); 

     if(!mFingerprintManager.isHardwareDetected()) { 
      mUseFingerprintToggle.setVisibility(View.GONE); 
     } else { 
      mGenerated = initializeKeyPair(false); 
     } 

     if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 
      setStage(isFingerprintAvailable() ? Stage.FINGERPRINT : Stage.CREDENTIALS); 
     } else { 
      setStage(Stage.CREDENTIALS); 
     } 

     return view; 
    } 

    @Override 
    public void onResume() { 
     super.onResume(); 

     ... 

     if(mStage == Stage.FINGERPRINT && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 
      startListening(initializeCipher(Cipher.DECRYPT_MODE)); 
     } 
    } 

    @Override 
    public void onPause() { 
     super.onPause(); 
     stopListening(); 
    } 

    ... 

    @Override 
    public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) { 
     Timber.i("Fingerprint succeeded"); 
     showFingerprintSuccess(); 

     mSubscriptions.add(
      mGenerated.subscribeOn(Schedulers.newThread()) 
        .observeOn(AndroidSchedulers.mainThread()) 
        .doOnCompleted(() -> { 
         try { 
          mUsername = mUsernamePreference.get(); 
          mPassword = decryptPassword(result.getCryptoObject().getCipher()); 
          initLoginAttempt(); 
         } catch (IllegalBlockSizeException | BadPaddingException exception) { 
          Timber.e(exception, "Failed to decrypt password"); 
         } 
        }).subscribe()); 
    } 

    @Override 
    public void onAuthenticationHelp(int messageId, CharSequence message) { 
     Timber.i("Fingerprint help id: " + messageId + " message: " + message); 
     showFingerprintError(message); 
    } 

    @Override 
    public void onAuthenticationError(int messageId, CharSequence message) { 
     Timber.i("Fingerprint error id: " + messageId + " message: " + message); 
     if(messageId != 5) { 
      showFingerprintError(message); 
     } 
    } 

    @Override 
    public void onAuthenticationFailed() { 
     Timber.i("Fingerprint failed"); 
     showFingerprintError(getResources().getString(R.string.msg_fingerprint_error_unknown)); 
    } 

    @OnClick(R.id.button_cancel) 
    public void onCancel() { 
     dismiss(); 
    } 

    @OnClick(R.id.button_continue) 
    public void onContinue() { 
     switch (mStage) { 
      case CREDENTIALS: 
       mUsername = mUsernameView.getText().toString(); 
       mPassword = mPasswordView.getText().toString(); 
       initLoginAttempt(); 
       break; 
      case FINGERPRINT: 
       setStage(Stage.CREDENTIALS); 
       break; 
     } 
    } 

    private void showFingerprintSuccess() { 
     int colorAccent = ThemeUtil.getColorAttribute(getContext(), android.R.attr.colorAccent); 
     mFingerprintIcon.setImageResource(R.drawable.ic_done_white_24dp); 
     mFingerprintIcon.setCircleColor(colorAccent); 
     mFingerprintStatus.setText(R.string.msg_fingerprint_success); 
     mFingerprintStatus.setTextColor(colorAccent); 
    } 

    private void showFingerprintError(CharSequence message) { 
     int colorError = ContextCompat.getColor(getContext(), R.color.material_deep_orange_600); 
     mFingerprintIcon.setImageResource(R.drawable.ic_priority_high_white_24dp); 
     mFingerprintIcon.setCircleColor(colorError); 
     mFingerprintStatus.setText(message); 
     mFingerprintStatus.setTextColor(colorError); 
     resetFingerprintStatus(); 
    } 

    private void resetFingerprintStatus() { 
     mSubscriptions.add(Observable.timer(1600, TimeUnit.MILLISECONDS) 
       .subscribeOn(Schedulers.newThread()) 
       .observeOn(AndroidSchedulers.mainThread()) 
       .subscribe(finished -> { 
        mFingerprintIcon.setImageResource(R.drawable.ic_fingerprint_white_24dp); 
        mFingerprintIcon.setCircleColor(ContextCompat 
          .getColor(getContext(), R.color.material_blue_gray_500)); 
        mFingerprintStatus.setText(R.string.msg_fingerprint_input); 
        mFingerprintStatus.setTextColor(ThemeUtil 
          .getColorAttribute(getContext(), android.R.attr.textColorHint)); 
       })); 
    } 

    private void onSaveUsernameChanged(boolean checked) { 
     if(!checked) { 
      mUseFingerprintToggle.setChecked(false); 
     } 
    } 

    private void onUseFingerprintChanged(boolean checked) { 
     if(checked) { 
      mSaveUsernameToggle.setChecked(true); 

      if(!mFingerprintManager.hasEnrolledFingerprints()) { 
       displaySettingsDialog(); 
       mUseFingerprintToggle.setChecked(false); 
      } 
     } 
    } 

    public void setStage(Stage stage) { 
     switch (stage) { 
      case CREDENTIALS: 
       Timber.d("Set stage Credentials"); 
       mPositiveButton.setText(R.string.btn_login); 
       mFingerprintContent.setVisibility(View.GONE); 
       mCredentialContent.setVisibility(View.VISIBLE); 
       setForm(); 
       break; 
      case FINGERPRINT: 
       mPositiveButton.setText(R.string.btn_password); 
       mCredentialContent.setVisibility(View.GONE); 
       mFingerprintContent.setVisibility(View.VISIBLE); 
       break; 
     } mStage = stage; 
    } 

    private void startListening(boolean cipher) { 
     Timber.v("Start listening for fingerprint input"); 
     mCancellationSignal = new CancellationSignal(); 
     if(cipher) { 
      mFingerprintManager.authenticate(new FingerprintManagerCompat.CryptoObject(mCipher), 
        0, mCancellationSignal, new FingerprintCallback(this), null); 
     } else { 
      setStage(Stage.CREDENTIALS); 
     } 
    } 

    private void stopListening() { 
     if(mCancellationSignal != null) { 
      mCancellationSignal.cancel(); 
      mCancellationSignal = null; 
     } 
    } 

    private void setForm() { 
     if(mSaveUsernamePreference.isSet() && mSaveUsernamePreference.get() 
       && mUsernamePreference.isSet()) { 
      mUsernameView.setText(mUsernamePreference.get()); 
      mUsernameView.setSelectAllOnFocus(true); 
      mPasswordView.requestFocus(); 
     } else { 
      mUsernameView.requestFocus(); 
     } 
    } 

    public void initLoginAttempt() { 
     mProgressBar.setVisibility(View.VISIBLE); 
     mAuthenticationService.getLoginForm().subscribeOn(Schedulers.newThread()) 
       .observeOn(AndroidSchedulers.mainThread()) 
       .subscribe(this::onLoginFormResponse, this::onError); 
    } 

    private void onLoginFormResponse(ResponseBody response) { 
     try { 
      attemptLogin(LoginForm.parse(response.string())); 
     } catch (IOException exception) { 
      Timber.w(exception, "Failed to parse login form"); 
     } 
    } 

    private void attemptLogin(LoginForm loginForm) { 
     mAuthenticationService 
       .login(loginForm.getLoginTicket(), loginForm.getExecution(), loginForm.getEventIdentifier(), 
         mUsername, mPassword, loginForm.getSubmitValue()) 
       .subscribeOn(Schedulers.newThread()) 
       .observeOn(AndroidSchedulers.mainThread()) 
       .subscribe(this::onLoginResponse, this::onError); 
    } 

    public void onLoginResponse(ResponseBody response) { 
     Timber.d("LOGIN RESPONSE"); 
     try { 
      Timber.d(response.string()); 
     } catch (IOException exception) { 
      Timber.w(exception, "Failed to retrieve attemptLogin response"); 
     } 

     mSubscriptions.add(NetworkUtility.getAuthentication() 
       .subscribeOn(Schedulers.newThread()) 
       .observeOn(AndroidSchedulers.mainThread()) 
       .subscribe(this::onAuthenticationChanged, this::onError)); 
    } 

    public void onAuthenticationChanged(Boolean authenticated) { 
     if(authenticated) { 
      Timber.d("Authentication success"); 

      if(mStage == Stage.CREDENTIALS) { 
       if (mSaveUsernameToggle.isChecked()) { 
        storeUsername(); 
       } else { 
        clearUsername(); 
       } 

       if (mUseFingerprintToggle.isChecked()) { 
        mGenerated = initializeKeyPair(true); 
        storePassword(); 
       } else { 
        clearPassword(); 
        finishIntent(); 
       } 
      } else { 
       finishIntent(); 
      } 
     } else { 
      Timber.d("Authentication failed"); 
      setStage(Stage.CREDENTIALS); 
      mCaptionView.setTextColor(ContextCompat.getColor(getContext(), R.color.material_deep_orange_600)); 
      mCaptionView.setText(getString(R.string.msg_login_failed)); 
      mPasswordView.setText(""); 
     } 
    } 

    private void finishIntent() { 
     mProgressBar.setVisibility(View.INVISIBLE); 
     Intent intent = getArguments().getParcelable(EXTRA_INTENT); 
     if(intent != null) { 
      startActivity(intent); 
     } dismiss(); 
    } 

    private void onError(Throwable throwable) { 
     Timber.w(throwable, "Login attempt failed"); 
     mProgressBar.setVisibility(View.INVISIBLE); 
     mCaptionView.setTextColor(ContextCompat.getColor(getContext(), R.color.material_deep_orange_600)); 
     mCaptionView.setText("Login attempt failed\nPlease check your internet connection and try again"); 
     mPasswordView.setText(""); 
    } 

    private void storeUsername() { 
     String username = mUsernameView.getText().toString(); 
     mUsernamePreference.set(username); 
     if(mPreferences.getBoolean(getString(R.string.key_auth_push), false).get()) { 
      UAirship.shared().getPushManager().getNamedUser().setId(username); 
     } 
    } 

    private void clearUsername() { 
     UAirship.shared().getPushManager().getNamedUser().setId(null); 
     mUsernamePreference.delete(); 
    } 

    private void storePassword() { 
     Timber.d("STORE PASSWORD"); 
     mSubscriptions.add(mGenerated.subscribeOn(Schedulers.newThread()) 
       .observeOn(AndroidSchedulers.mainThread()) 
       .doOnCompleted(() -> { 
        try { 
         Timber.d("Store password"); 
         initializeCipher(Cipher.ENCRYPT_MODE); 

         String password = mPasswordView.getText().toString(); 
         byte[] bytes = password.getBytes(); 
         byte[] encrypted = mCipher.doFinal(bytes); 
         String encoded = Base64.encodeToString(encrypted, Base64.NO_WRAP); 

         mPasswordPreference.set(encoded); 

         finishIntent(); 

        } catch (IllegalBlockSizeException | BadPaddingException exception) { 
         Timber.e(exception, "Failed to encrypt password"); 
        } 
       }).subscribe()); 
    } 

    private String decryptPassword(Cipher cipher) throws IllegalBlockSizeException, BadPaddingException { 
     String encoded = mPasswordPreference.get(); 

     Timber.d("ENCODED STRING " + encoded); 

     byte[] encrypted = Base64.decode(encoded, Base64.NO_WRAP); 

     byte[] bytes = cipher.doFinal(encrypted); 

     return new String(bytes); 
    } 

    private void clearPassword() { 
     mPasswordPreference.delete(); 
    } 

    private boolean isFingerprintAvailable() { 
     return mUseFingerprintPreference.isSet() && mUseFingerprintPreference.get() 
       && mFingerprintManager.hasEnrolledFingerprints() 
       && mSaveUsernamePreference.isSet() 
       && mPasswordPreference.isSet(); 
    } 

    private void displaySettingsDialog() { 
     new AlertDialog.Builder(getContext()) 
       .setTitle(R.string.title_dialog_secure_lock) 
       .setMessage(R.string.msg_fingerprint_unavailable) 
       .setPositiveButton(R.string.btn_settings, (dialog, which) -> { 
        startActivity(new Intent(android.provider.Settings.ACTION_SECURITY_SETTINGS)); 
        dialog.dismiss(); 
       }).setNegativeButton(R.string.btn_cancel, (dialog, which) -> { 
      dialog.dismiss(); 
     }).create().show(); 
    } 

    @TargetApi(Build.VERSION_CODES.M) 
    private boolean initializeCipher(int opmode) { 
     try { 
      mKeyStore.load(null); 

      /** 
      * A known bug in the Android 6.0 (API Level 23) implementation of Bouncy Castle 
      * RSA OAEP causes the cipher to default to an SHA-1 certificate, making the SHA-256 
      * certificate of the public key incompatible 
      * To work around this issue, explicitly provide a new OAEP specification upon 
      * initialization 
      * @see <a href="https://code.google.com/p/android/issues/detail?id=197719">Issue 197719</a> 
      */ 
      AlgorithmParameterSpec spec = generateOAEPParameterSpec(); 
      Key key; 

      if(opmode == Cipher.ENCRYPT_MODE) { 
       Key publicKey = mKeyStore.getCertificate(CIPHER_KEY_ALIAS).getPublicKey(); 

       /** 
       * A known bug in Android 6.0 (API Level 23) causes user authentication-related 
       * authorizations to be enforced even for public keys 
       * To work around this issue, extract the public key material to use outside of 
       * the Android Keystore 
       * @see <a href="http://developer.android.com/reference/android/security/keystore/KeyGenParameterSpec.html">KeyGenParameterSpec Known Issues</a> 
       */ 
       key = KeyFactory.getInstance(publicKey.getAlgorithm()) 
         .generatePublic(new X509EncodedKeySpec(publicKey.getEncoded())); 
      } else { 
       key = mKeyStore.getKey(CIPHER_KEY_ALIAS, null); 
      } 

      mCipher.init(opmode, key, spec); 
      return true; 
     } catch (KeyPermanentlyInvalidatedException exception) { 
      Timber.w(exception, "Failed to initialize Cipher"); 
      handleKeyPermanentlyInvalidated(); 
      return false; 
     } catch (IOException | KeyStoreException | UnrecoverableEntryException 
       | InvalidKeySpecException | CertificateException | InvalidKeyException 
       | NoSuchAlgorithmException | InvalidAlgorithmParameterException exception) { 
      throw new RuntimeException("Failed to initialize Cipher", exception); 
     } 
    } 

    private OAEPParameterSpec generateOAEPParameterSpec() { 
     return new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT); 
    } 

    private void handleKeyPermanentlyInvalidated() { 
     mCaptionView.setText(getString(R.string.msg_fingerprint_invalidated)); 
     mGenerated = initializeKeyPair(true); 
     clearPassword(); 
    } 

    private Observable<KeyPair> initializeKeyPair(boolean generate) { 
     return Observable.create(subscriber -> { 
      try { 
       mKeyStore.load(null); 

       if(!generate || mKeyStore.containsAlias(CIPHER_KEY_ALIAS)) { 
        PublicKey publicKey = mKeyStore.getCertificate(CIPHER_KEY_ALIAS).getPublicKey(); 
        PrivateKey privateKey = (PrivateKey) mKeyStore.getKey(CIPHER_KEY_ALIAS, null); 
        subscriber.onNext(new KeyPair(publicKey, privateKey)); 
       } else { 
        subscriber.onNext(createKeyPair()); 
       } 

       subscriber.onCompleted(); 
      } catch (IOException | KeyStoreException | UnrecoverableKeyException 
        | CertificateException | NoSuchAlgorithmException 
        | InvalidAlgorithmParameterException exception) { 
       Timber.e(exception, "Failed to generate key pair"); 
       subscriber.onError(exception); 
      } 
     }); 
    } 

    @TargetApi(Build.VERSION_CODES.M) 
    private KeyPair createKeyPair() throws InvalidAlgorithmParameterException { 
     // Set the alias of the entry in Android KeyStore where the key will appear 
     // and the constrains (purposes) in the constructor of the Builder 
     Timber.d("Initialize key pair"); 
     mKeyPairGenerator.initialize(
       new KeyGenParameterSpec.Builder(CIPHER_KEY_ALIAS, KeyProperties.PURPOSE_DECRYPT) 
        .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512) 
        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP) 
         .setUserAuthenticationRequired(true) 
         .build()); 

     return mKeyPairGenerator.generateKeyPair(); 
    } 

} 

aktualisieren

Okay, so habe ich heraus es ist die KeyPermanentlyInvalidatedException, die den Fehler verursacht. Wenn ich den Block catch, der diese Ausnahme behandelt, auskommentiere, funktioniert der Code auf jedem Gerät einwandfrei. Das Problem ist, dass ich diese Ausnahme auf Geräte-API in der Lage sein müssen, zu handhaben 23+:

catch (KeyPermanentlyInvalidatedException exception) { 
    Timber.w(exception, "A new fingerprint was added to the device"); 
    handleKeyPermanentlyInvalidated(); 
    return false; 
} 
+0

Wir können Ihnen mit redigiertem Code nicht wirklich helfen. Bitte poste ein [mcve], um dein Problem zu demonstrieren, wie zum Beispiel die tatsächliche Implementierung deiner 'login()' Methode, wo du abstürzt, und die 'initializeCipher()' Methode deines 'LoginFragment' (wo der unerkannte Kram zu sein scheint Lügen). – CommonsWare

+0

@CommonsWare Ich habe die Frage mit Code aktualisiert. – Bryan

Antwort

8

Meine Vermutung ist, dass entweder FingerprintCallback.Callback erstreckt sich eine API-Ebene 23+ Schnittstelle oder dass LoginFragment hat Felder, die API erenzstand 23 + Zeug.

Ihre Regel, ob Sie API Level 23+ Methoden sicher innerhalb des Version-Guard-Blocks aufrufen können, ist korrekt. Sie können jedoch nicht:

  • erben von Klassen, die auf dem Gerät nicht existieren
  • Schnittstellen implementieren, die auf dem Gerät nicht
  • existieren haben Felder, deren Typen existieren nicht auf dem Gerät
  • akzeptieren Konstruktor- oder Methodenparameter, deren Typen nicht auf dem Gerät existieren (wo wir diese tatsächlich aufrufen)
  • haben Methodenrückgabewerte, deren Typen nicht auf dem Gerät existieren (wo wir diese tatsächlich aufrufen)

In vielen Fällen brauchen wir nichts davon. In diesem Fall reicht es aus, nur Build.VERSION.SDK_INT zu überprüfen, bevor API Level 23+ aufgerufen wird.

Wenn Sie einige der Dinge in der Aufzählung Liste tun müssen, ist das in Ordnung, aber dann müssen Sie diejenigen in Klassen isolieren, die Sie nur auf API Level 23 + Geräten verwenden.

Also, zum Beispiel, lassen Sie uns so tun, dass das Problem ist, dass FingerprintCallback.Callback einige API Level 23+ Schnittstelle erweitert. Anstatt FingerprintCallback.Callback unter LoginFragment zu implementieren, können Sie dies als anonyme innere Klasse implementieren und nur den Code ausführen, der diese anonyme innere Klasseninstanz erstellt, wenn Build.VERSION.SDK_INT hoch genug ist. Dann referenzieren Sie nur FingerprintCallback.Callback auf den neueren Geräten, und Sie sollten sicher sein.

+0

Ich sehe, was Sie sagen, obwohl es nicht "FingerprintCallback" sein sollte, denn das ist eine Klasse, die 'FingerprintManagerCompat.AuthenticationCallback' erweitert, die ein Teil der [Fingerprint Support Library] ist (https://developer.android .com/Referenz/android/support/v4/Hardware/Fingerabdruck/Paket-Zusammenfassung.html). Ich sehe auch keine Felder, die Klassen von API 23+ verweisen. Ich werde den Code durchsehen müssen, um zu sehen, ob ich eine der anderen Anforderungen nicht erfülle. – Bryan

+1

@Bryan: Wenn du rumpelt bist, spalte 'LoginFragment' in 2-3 Klassen. Entweder haben sie eine "LoginFragment" und eine "FingerprintLoginFragment" Unterklasse oder eine "LoginFragmentBase" mit "LoginFragment" und "FingerprintLoginFragment" Unterklassen. Legen Sie alle Fingerabdrücke in "FingerprintLoginFragment". Dann, wenn es Zeit ist, das Fragment hinzuzufügen(), wählen Sie die Fragmentklasse basierend auf der Geräte-API-Ebene. Dies ist auch praktisch in Fällen, in denen Sie sonst mit einer Zillion 'Build.VERSION.SDK_INT'-Prüfung enden würden, da' FingerprintLoginFragment' alle API-Level-23-Objekte verwenden kann, die es möchte. – CommonsWare

+0

Ich habe die Quelle des Problems gefunden, nämlich die 'KeyPermanentlyInvalidatedException' im' initializeCipher() 'try/catch-Block. Aber diese Methode wird nie auf Geräten vor API 23 aufgerufen (sonst würde ich nicht glauben, dass der Code auf Geräten API 20+ funktionieren würde). Ich denke, ich mag am Ende nur zwei Klassen machen, wie Sie es vorgeschlagen haben, aber irgendwelche Ideen, warum das passieren würde? – Bryan

1

Wie Sie sagt, das Problem mit dem catch-Block ist

catch (KeyPermanentlyInvalidatedException exception) { 
    Timber.w(exception, "A new fingerprint was added to the device"); 
    handleKeyPermanentlyInvalidated(); 
    return false; 
} 

Da diese Ausnahme auf API LEVEL 23 hinzugefügt wird, aber ich weiß nicht, warum der Verifizierungsfehler bei der Initialisierung ausgelöst wird, selbst.

Auf jeden Fall können Sie die Ausnahme abfangen mit

catch (InvalidKeyExceptionexception) { 
    .... 
    return false; 
} 

seit KeyPermanentlyInvalidatedExceptionInvalidKeyExceptionexception

+1

Ja, ich habe dies getan, aber ich muss speziell die 'KeyPermanentlyInvalidatedException' abfangen. Der Grund dafür (https://developer.android.com/reference/android/security/keystore/KeyPermanentlyInvalidatedException.html) ist, dass diese Ausnahme verwendet wird, um jedes Mal abzufangen, wenn sich der Benutzer erneut mit einem Passwort authentifizieren sollte. Eine "InvalidKeyException" könnte in einer Reihe anderer Fälle ausgelöst werden. Daher überprüfe ich, wie @CommonsWare erwähnt, ob die Ausnahme eine "instanceof" der 'KeyPermanentlyInvalidatedException' im catch-Block ist, nachdem zuerst geprüft wurde, ob die API 23+ ist. – Bryan

4

erstreckt ich den gleichen Fehler hatte und löste es die folgende Art und Weise:

catch (Exception e) { 
    if (e instanceof KeyPermanentlyInvalidatedException) { 
     //your error handling goes here 
    } 

Es isn‘ t vey nice, aber es funktioniert

+0

Yup, es funktioniert. Bestätigt. Kein Fan davon, aber ich habe größere Fische zum Frittieren. Dies war nur ein Problem für mich am 4.x. Also in ein paar Jahren kann ich diesen hässlichen Code entfernen. – KickingLettuce