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 ...
- eine Instanz des
FingerprintManager
Dienst erhalten - eine Instanz des
SHA512withRSA/PSS
Signaturalgorithmus erstellen (Signature
Objekt) - initialisieren Sie den
Signature
Algorithmus zum Signieren mit dem privaten Schlüssel (initSign(...)
) - wickeln Sie das
Signature
Objekt in eineCryptoObject
- (führen einige zusätzliche Kontrollen)
authenticate(...)
dieCryptoObject
die Instanz vonFingerprintManager
mit, vorbei an (unter anderem) einFingerprintManager.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 ...
- extrahieren sie die
Signature
Objekt aus dieCryptoObject
wrapper wieder - die
update(...)
Methode auf dasSignature
Objekt verwenden, um die Daten zu streamen der Signatur - kodieren, die Unterschrift zu signierenden (
message
) in den Signaturalgorithmus - verwenden, um die
sign()
Methode auf demSignature
Objekt zu erhalten wieBase64
und esprintln(...)
stdErr zu aus so scheint es, inadb 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.
Bitte geben Sie den Code ein, anstatt den Code, den Sie verwenden, zu referenzieren. – pedrofb
Fertig. (Code ist jetzt in Post enthalten.) – andrep
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