2017-07-04 3 views
0

Ich erstelle derzeit eine Form der Challenge-Response-Authentifizierung für ein Projekt, das ich für meine Master-Arbeit in der Informatik benötige.Android - Fingerabdruck-Authentifizierung für den Zugriff auf (RSA/PSS) Signaturschlüssel erforderlich

Zu diesem Zweck muss ich eine RSA-PSS-Signatur mit einem privaten Schlüssel erstellen, der durch einen Fingerabdruck authentifiziert wird, sodass er nur zum Erstellen einer Signatur verwendet werden kann, wenn der Besitzer des Geräts physisch anwesend ist.

Um dies zu erreichen, verwende ich die Android Keystor (unterstützt von Keymaster/Pförtner in ARM Trustzone) ein RSA-Schlüsselpaar (KEY_ALGORITHM_RSA) zur Verwendung mit dem RSA-PSS Signaturalgorithmus (SIGNATURE_PADDING_RSA_PSS) für die Erstellung zu erzeugen und Überprüfen von Signaturen (PURPOSE_SIGN | PURPOSE_VERIFY). Ich benötige auch eine Benutzerauthentifizierung, indem ich die entsprechende Eigenschaft auf true setze.

später wird die Signatur über einen Puffer zu schaffen final byte[] message, I ...

  1. eine Instanz des FingerprintManager Dienst erhalten
  2. eine Instanz des SHA512withRSA/PSS Signaturalgorithmus erstellen (Signature Objekt)
  3. initialisieren Sie den Signature Algorithmus zum Signieren mit dem privaten Schlüssel (initSign(...))
  4. wickeln Sie das Signature Objekt in eine CryptoObject
  5. (führen einige zusätzliche Kontrollen)
  6. authenticate(...) die CryptoObject die Instanz von FingerprintManager mit, vorbei an (unter anderem) ein FingerprintManager.AuthenticationCallback genannt werden, nachdem der Schlüssel durch den Benutzer authentifiziert wurde (durch seine/den Fingerabdrucksensor zu berühren ihr Gerät)

im Inneren der Rückruf, Verwendung des Schlüssels authentifiziert, so dass ich ...

  1. extrahieren sie die Signature Objekt aus die CryptoObject wrapper wieder
  2. die update(...) Methode auf das Signature Objekt verwenden, um die Daten zu streamen der Signatur
  3. kodieren, die Unterschrift zu signierenden (message) in den Signaturalgorithmus
  4. verwenden, um die sign() Methode auf dem Signature Objekt zu erhalten wie Base64 und es println(...) stdErr zu aus so scheint es, in adb logcat

ich einen Beispielcode erstellt, die ich Es ist so minimal wie es nur geht.

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); 
    } 

} 

(Es ist immer noch ~ 200 LOC lang, aber Fingerabdruck authentifizierte Kryptographie ein Stück Code muss, damit es funktioniert, so kann ich nicht diese jede kleine/einfacher zu bekommen scheinen.)

zu Testen Sie es, erstellen Sie einfach ein Projekt mit einer einzigen Aktivität in Android Studio.Fügen Sie zwei Schaltflächen in diese Aktivität ein, eine zum Generieren eines Schlüssels (d. H. Generate) und eine zum Erstellen einer Signatur (d. H. Sign).

Setzen Sie dann den Beispielcode in Ihre Haupttätigkeit und verbinden die onclick Ereignisse aus den Taste zur public void generate(final View view) Methode generieren und aus der Sign Taste zur public void sign(final View view) Methode.

Schließlich fügen Sie die folgenden in Ihre AndroidManifest.xml, in der obersten Ebene <manifest ...> ... </manifest> Tag.

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

Führen Sie das Projekt und lassen adb logcat laufen daneben.

Nachdem Sie die Schaltfläche Erzeuge gedrückt haben, sollten Sie eine Ausgabe wie diese in den Protokollen sehen.

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

Dies ist der öffentliche Schlüssel des Schlüsselpaares, das erzeugt wurde.

(Sie werden auch einige Beschwerden über die Schlüsselerzeugung stattfindet in dem Haupt-Thread, aber sehen, das nur den Beispielcode einfach zu halten ist. Die eigentliche Anwendung in einem eigenen Thread Schlüsselerzeugung führt.)

Dann , zuerst hit Zeichen, dann den Sensor berühren. Der folgende Fehler wird auftreten.

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 

Dies ist, wo ich feststecke.

Die komische Sache ist, dass ich Signature/MAC verification failed als die Nachricht der SignatureException bekomme. Beachten Sie, dass es verification failed heißt, während ich eigentlich signieren (nicht Überprüfung) und die gesamte Stack-Trace zeigt, dass nur signSomething (...) Funktionen aufgerufen werden.

Ich habe schon versucht, diese auf das LG Nexus 5X sowohl mit der offiziellen Firmware (Android 7.1.1, N2G47W) und andere (up-to-date) LineageOS Nightlies und sie nicht alle an diesem Punkt. Wenn ich jedoch die API-Dokumentation in Betracht ziehe, scheint es, als würde ich die richtigen Sachen machen und - um ehrlich zu sein - es gibt nicht viele Sachen, die man eigentlich anders machen könnte. Es scheint tatsächlich ziemlich offensichtlich zu sein, wie es funktioniert.

Beachten Sie, dass, solange ich nicht Benutzerauthentifizierung erforderlich - und damit nicht die Signatur in der Callback-Methode erstellen, sondern außerhalb, direkt nach den initSign(...) - es funktioniert gut - auch mit Hardware-unterstützter Schlüssel Speicher von Keymaster/Gatekeeper in TrustZone. Aber sobald ich eine Authentifizierung benötige, - und damit die update(...) und sign() Anrufe auf das Signature Objekt innerhalb des Callbacks machen - bricht alles auseinander.

Ich habe versucht, den Fehler in der OpenSSL Bibliothek zu verfolgen oder um herauszufinden, was der -30 Antwortcode bedeutet, aber ohne Erfolg beides.

Irgendwelche Vorschläge? Ich habe einen langen Weg gegangen und eine Menge Zeug, sowohl serverseitig als auch auf Android implementiert, um dieses Projekt voranzutreiben, aber jetzt bin ich fest und anscheinend nicht in der Lage, eine kryptografisch einwandfreie Benutzerauthentifizierung durchzuführen.

Ich versuchte KeyProperties.SIGNATURE_PADDING_RSA_PSS mit KeyProperties.SIGNATURE_PADDING_RSA_PKCS1 und SHA512withRSA/PSS mit SHA512withRSA, dann KeyProperties.DIGEST_SHA512 mit KeyProperties.DIGEST_SHA256 und SHA512withRSA mit SHA256withRSA ersetzen. Ich versuchte auch eine kleinere Schlüsselgröße - 2048 Bit statt 4096 Bit - alles vergebens.

Ich habe auch versucht, Befehle von den initSign(...), update(...), sign() Verfahren von der Außenseite des Rückrufs nach innen oder umgekehrt, aber zu verschieben, ist dies die einzige Kombination ist, dass die Arbeit angenommen hat. Wenn ich initSign(...) innerhalb des Rückrufs verschiebe, schlägt der Anruf zu authenticate(...) mit java.lang.IllegalStateException: Crypto primitive not initialized fehl. Wenn ich update(...) und sign() außerhalb des Rückrufs verschiebe, schlägt der Anruf an sign() mit java.security.SignatureException: Key user not authenticated fehl. So initSign(...)hat draußen zu sein und sign()hat innen sein. Wo update(...) passiert, scheint unkritisch zu sein, jedoch ist es aus semantischer Sicht sinnvoll, es zusammen mit dem Aufruf an sign() zu halten.

Jede Hilfe wird wirklich geschätzt.

+0

Bitte geben Sie den Code ein, anstatt den Code, den Sie verwenden, zu referenzieren. – pedrofb

+0

Fertig. (Code ist jetzt in Post enthalten.) – andrep

+1

Ich habe ein wenig Ihren Code überprüft und ich konnte keinen wesentlichen Fehler finden. Ich habe ein ähnliches Beispiel mit Nexus, also vermute ich, dass es sich um eine nicht unterstützte Parameterkombination handelt. Ich schlage vor, Sie vereinfachen Ihren Code und wenden einen Versuch-und-Irrtum-Prozess an. Zum Beispiel verwenden Sie nicht PSS Padding – pedrofb

Antwort

0

I schließlich fand eine Lösung.

Es gab tatsächlich zwei Probleme bei der Hand hier.

  1. Ich versuchte SHA-512 als Maske Generation Funktion für RSA/PSS zu verwenden, die "wahrscheinlich" nicht unterstützt durch die Verschlüsselungsbibliothek, die Android Anwendungen ist.

  2. Ich habe versucht, eine leere (0-Byte-) Nachricht zu signieren, die irgendwie "problematisch" erscheint.

Wenn I beide die MGF nach SHA-256und gemacht gelang es, die Nachricht 64 Bytes lang, Unterschrift verändert Generation.

Nun scheinen beide "Anforderungen" ein bisschen "komisch" zu sein.

Zuerst Sie können tatsächlich SHA-512 als MGF für RSA/PSS, verwenden, solange Sie setUserAuthenticationRequired (false), so dass es hat durch die Krypto-Bibliothek unterstützt werden. Nur wenn Sie die Authentifizierung aktivieren, schlägt sie plötzlich fehl und Sie können auf SHA-256 zurückfallen lassen. Ich habe nicht ausführlich getestet, welche Hash-Funktionen als MGFs für RSA/PSS mit Authentifizierung funktionieren und welche nicht. Ich habe gerade festgestellt, dass SHA-512 nicht funktioniert, aber SHA-256 tut, so ist die Wahl der MGF irgendwie "eingeschränkt", wenn die Authentifizierung aktiviert ist.

Zweitens muss Ihre Nachricht eine bestimmte Mindestgröße haben, damit sie mit aktivierter Authentifizierung signiert werden kann. Sie können beispielsweise keinen leeren Puffer signieren. Dies macht für mich überhaupt keinen Sinn, da der erste Schritt in RSA/PSS darin besteht, eine kryptografische Hash-Funktion auf die Nachricht anzuwenden, deren Ausgabe eine feste Länge hat, so dass es dem Signaturschema wirklich egal ist, wie lang oder kurz die Nachricht ist ist, aber anscheinend tut es das. Wie zuvor habe ich keine umfangreichen Tests durchgeführt, um den genauen Abschneidepunkt zu finden, an dem die Nachricht für das Signieren "lang genug" wird. Ich habe jedoch festgestellt, dass eine 64-Byte-Nachricht signiert werden kann, während eine leere (0 Byte) Nachricht nicht signiert werden kann, sodass die minimale Länge irgendwo innerhalb von [1; 64] Bytes, beide Grenzwerte inklusive.

Beachten Sie, dass dies ab sofort nirgendwo dokumentiert zu sein scheint und auch die ausgelöste Ausnahme nützt nichts. Es sagt nur "Signaturprüfung fehlgeschlagen" (ja, es heißt "Verifikation" obwohl wir eigentlich generieren eine Signatur), so dass Sie keine Ahnung haben, dass Sie die MGF und die Länge der Nachricht zu ändern unterschrieben sein.

Aus diesem Grund könnte es mehr, dass ich nicht gefunden habe. Ich habe diese Parametrisierung nur durch "Versuch und Irrtum" gefunden und habe daher keine Ahnung, wie die tatsächlichen Einschränkungen der kryptographischen Bibliothek aussehen.

0

Ihre getPrivateKey Methode ändern:

private PrivateKey getPrivateKey() { 

    KeyStore store = KeyStore.getInstance("AndroidKeyStore"); 
    store.load(null); 

    return (PrivateKey) keyStore.getKey("authKey", null)); 
} 

in Ihrem Code Sie iterieren alle Schlüssel durch und grep die letzte, die nicht unbedingt die ist, die Sie wollen - oder noch schlimmer: return null wenn nicht, dass Schlüssel nicht einen privaten Schlüssel ...

Wenn Sie, ob ein Schlüssel überprüfen möchten existieren:

if (store.containsAlias(keyName)) { 
    ... 
} 
Verwandte Themen