Android - 需要指纹认证才能访问 (RSA/PSS) 签名密钥
Android - require fingerprint authentication to access (RSA/PSS) signing key
我目前正在为我的计算机科学硕士论文所需的项目创建一种质询-响应身份验证形式。
为此,我需要使用通过指纹验证的私钥创建 RSA-PSS 签名,以便它只能在设备所有者实际存在时用于创建签名。
为此,我使用 Android KeyStore(由 ARM TrustZone 中的 Keymaster/Gatekeeper 支持)生成用于创建和验证签名 (PURPOSE_SIGN | PURPOSE_VERIFY
) 的 RSA-PSS 签名算法 (SIGNATURE_PADDING_RSA_PSS
) 的 RSA 密钥对 (KEY_ALGORITHM_RSA
)。我还通过将相应的 属性 设置为 true
.
来要求用户身份验证
稍后,为了在缓冲区 final byte[] message
上创建签名,我 ...
- 获取
FingerprintManager
服务的一个实例
- 创建
SHA512withRSA/PSS
签名算法的实例(Signature
对象)
- 初始化
Signature
算法以使用私钥签名 (initSign(...)
)
- 将
Signature
对象包装成 CryptoObject
- (执行一些额外的检查)
authenticate(...)
CryptoObject
使用 FingerprintManager
的实例,传递(除其他外)一个 FingerprintManager.AuthenticationCallback
在用户验证密钥后调用(通过触摸 his/her 设备上的指纹传感器)
在回调中,密钥的使用是经过验证的,所以我...
- 再次从
CryptoObject
包装器中提取 Signature
对象
- 在
Signature
对象上使用 update(...)
方法将要签名的数据 (message
) 流式传输到签名算法中
- 在
Signature
对象上使用sign()
方法获取签名
- 将该签名编码为
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 长,但是指纹验证加密需要一些代码才能使其工作,所以我似乎无法得到它 smaller/simpler。)
要测试它,只需在 Android Studio 中创建一个包含单个 activity 的项目。在此 activity 中插入两个按钮,一个用于生成密钥(即标记为 Generate),另一个用于创建签名(即标记为 Sign).
然后将示例代码插入到 activity 和 link onclick
事件中,从 Generate 按钮到 public void generate(final View view)
方法和从 Sign 按钮到 public void sign(final View view)
方法。
最后,将以下内容插入您的 AndroidManifest.xml
顶级 <manifest ...> ... </manifest>
标签内。
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
运行 项目并让 adb logcat
运行 与它并排。
点击 Generate 按钮后,您应该会在日志中看到类似这样的输出。
07-04 14:46:18.475 6759 6759 D MinimalSignatureTest: Public key: MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICC...
这是生成的密钥对的public密钥。
(您还会看到一些关于密钥生成发生在主线程中的抱怨,然而,这只是为了使示例代码简单。实际应用程序在其自己的线程中执行密钥生成。)
然后,先点击签名,然后触摸传感器。会出现以下错误。
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.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
)和不同的(up -to-date) LineageOS nightlies 并且它们在这一点上都失败了。但是,当我考虑 API 文档时,似乎我在做正确的事情,而且 - 老实说 - 实际上你可以做很多不同的事情。它实际上似乎很明显它是如何工作的。
请注意,只要我这样做 就不会 需要用户身份验证 - 因此不要在回调方法中创建签名,而是在 [=23 之后的外部创建签名=] - 它工作正常 - 即使 Keymaster/Gatekeeper 在 TrustZone 中使用硬件支持的密钥存储。但是,一旦我需要身份验证,- 因此在回调中对 Signature
对象执行 update(...)
和 sign()
调用 - 一切都会崩溃。
我试图追查 OpenSSL 库中的错误或找出 -30
响应代码的含义,但都无济于事。
有什么建议吗?我已经走了很长一段路,在服务器端和 Android 上实现了大量的东西来推进这个项目,但现在我被困住了似乎无法执行加密可靠的用户身份验证。
我尝试用 KeyProperties.SIGNATURE_PADDING_RSA_PKCS1
替换 KeyProperties.SIGNATURE_PADDING_RSA_PSS
,用 SHA512withRSA
替换 SHA512withRSA/PSS
,然后用 KeyProperties.DIGEST_SHA256
和 SHA512withRSA
替换 KeyProperties.DIGEST_SHA512
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()
的调用放在一起是有意义的。 =79=]
非常感谢任何帮助。
将您的 getPrivateKey
方法更改为:
private PrivateKey getPrivateKey() {
KeyStore store = KeyStore.getInstance("AndroidKeyStore");
store.load(null);
return (PrivateKey) keyStore.getKey("authKey", null));
}
在您的代码中,您遍历所有键并 grep 最后一个,这不一定是您想要的 - 或者更糟:return null
如果该键不是没有私钥...
如果要检查key是否存在:
if (store.containsAlias(keyName)) {
...
}
我终于找到了解决办法。
这里实际上有 两个 个问题。
我尝试使用 SHA-512 作为 RSA/PSS 的掩码生成函数,"probably" 不受Android 使用的密码库。
我试图签署一个空的(0 字节)消息,不知何故似乎是 "problematic"。
当我both 将 MGF 更改为 SHA-256 时 and 生成了消息 64字节长,签名生成成功。
现在,"requirements" 看起来有点 "weird"。
首先,您可以确实使用SHA-512作为RSA/PSS的MGF,只要您setUserAuthenticationRequired(false),因此它必须 得到密码库的支持。只有当您启用身份验证时,它突然失败并且您 必须 退回到 SHA-256。我没有执行广泛的测试,哪些散列函数可以作为 RSA/PSS 的 MGF 使用身份验证,哪些不能。我刚刚发现 SHA-512 不起作用,但 SHA-256 起作用,所以 MGF 的选择在某种程度上是 "restricted" 身份验证时已启用。
其次,您的邮件需要有一定的最小大小,以便在启用身份验证的情况下对其进行签名。例如,您不能签署空缓冲区。这对我来说毫无意义,因为 RSA/PSS 的第一步是对消息应用加密哈希函数,其输出是固定长度的,所以签名方案真的不应该关心多长或多短消息是,但显然是这样。和以前一样,我没有执行广泛的测试来找到消息变为 "long enough" 以进行签名的确切截止点。但是,我发现可以对 64 字节的消息进行签名,而不能对空(0 字节)消息进行签名,因此最小长度在 [1; 64] 字节,包括两个限制。
请注意,截至目前,这似乎无处记录,而且抛出的异常也没有用。它只是说 "signature verification failed"(是的,它说“验证”,即使我们实际上是 生成 签名),所以你有不知道您必须更改 MGF 和要签名的消息的长度。
因此,可能还有更多我没有找到的内容。我刚刚通过 "trial and error" 发现了这个参数化,因此不知道密码库的实际约束是什么样的。
我目前正在为我的计算机科学硕士论文所需的项目创建一种质询-响应身份验证形式。
为此,我需要使用通过指纹验证的私钥创建 RSA-PSS 签名,以便它只能在设备所有者实际存在时用于创建签名。
为此,我使用 Android KeyStore(由 ARM TrustZone 中的 Keymaster/Gatekeeper 支持)生成用于创建和验证签名 (PURPOSE_SIGN | PURPOSE_VERIFY
) 的 RSA-PSS 签名算法 (SIGNATURE_PADDING_RSA_PSS
) 的 RSA 密钥对 (KEY_ALGORITHM_RSA
)。我还通过将相应的 属性 设置为 true
.
稍后,为了在缓冲区 final byte[] message
上创建签名,我 ...
- 获取
FingerprintManager
服务的一个实例 - 创建
SHA512withRSA/PSS
签名算法的实例(Signature
对象) - 初始化
Signature
算法以使用私钥签名 (initSign(...)
) - 将
Signature
对象包装成CryptoObject
- (执行一些额外的检查)
authenticate(...)
CryptoObject
使用FingerprintManager
的实例,传递(除其他外)一个FingerprintManager.AuthenticationCallback
在用户验证密钥后调用(通过触摸 his/her 设备上的指纹传感器)
在回调中,密钥的使用是经过验证的,所以我...
- 再次从
CryptoObject
包装器中提取Signature
对象 - 在
Signature
对象上使用update(...)
方法将要签名的数据 (message
) 流式传输到签名算法中 - 在
Signature
对象上使用sign()
方法获取签名 - 将该签名编码为
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 长,但是指纹验证加密需要一些代码才能使其工作,所以我似乎无法得到它 smaller/simpler。)
要测试它,只需在 Android Studio 中创建一个包含单个 activity 的项目。在此 activity 中插入两个按钮,一个用于生成密钥(即标记为 Generate),另一个用于创建签名(即标记为 Sign).
然后将示例代码插入到 activity 和 link onclick
事件中,从 Generate 按钮到 public void generate(final View view)
方法和从 Sign 按钮到 public void sign(final View view)
方法。
最后,将以下内容插入您的 AndroidManifest.xml
顶级 <manifest ...> ... </manifest>
标签内。
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
运行 项目并让 adb logcat
运行 与它并排。
点击 Generate 按钮后,您应该会在日志中看到类似这样的输出。
07-04 14:46:18.475 6759 6759 D MinimalSignatureTest: Public key: MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICC...
这是生成的密钥对的public密钥。
(您还会看到一些关于密钥生成发生在主线程中的抱怨,然而,这只是为了使示例代码简单。实际应用程序在其自己的线程中执行密钥生成。)
然后,先点击签名,然后触摸传感器。会出现以下错误。
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.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
)和不同的(up -to-date) LineageOS nightlies 并且它们在这一点上都失败了。但是,当我考虑 API 文档时,似乎我在做正确的事情,而且 - 老实说 - 实际上你可以做很多不同的事情。它实际上似乎很明显它是如何工作的。
请注意,只要我这样做 就不会 需要用户身份验证 - 因此不要在回调方法中创建签名,而是在 [=23 之后的外部创建签名=] - 它工作正常 - 即使 Keymaster/Gatekeeper 在 TrustZone 中使用硬件支持的密钥存储。但是,一旦我需要身份验证,- 因此在回调中对 Signature
对象执行 update(...)
和 sign()
调用 - 一切都会崩溃。
我试图追查 OpenSSL 库中的错误或找出 -30
响应代码的含义,但都无济于事。
有什么建议吗?我已经走了很长一段路,在服务器端和 Android 上实现了大量的东西来推进这个项目,但现在我被困住了似乎无法执行加密可靠的用户身份验证。
我尝试用 KeyProperties.SIGNATURE_PADDING_RSA_PKCS1
替换 KeyProperties.SIGNATURE_PADDING_RSA_PSS
,用 SHA512withRSA
替换 SHA512withRSA/PSS
,然后用 KeyProperties.DIGEST_SHA256
和 SHA512withRSA
替换 KeyProperties.DIGEST_SHA512
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()
的调用放在一起是有意义的。 =79=]
非常感谢任何帮助。
将您的 getPrivateKey
方法更改为:
private PrivateKey getPrivateKey() {
KeyStore store = KeyStore.getInstance("AndroidKeyStore");
store.load(null);
return (PrivateKey) keyStore.getKey("authKey", null));
}
在您的代码中,您遍历所有键并 grep 最后一个,这不一定是您想要的 - 或者更糟:return null
如果该键不是没有私钥...
如果要检查key是否存在:
if (store.containsAlias(keyName)) {
...
}
我终于找到了解决办法。
这里实际上有 两个 个问题。
我尝试使用 SHA-512 作为 RSA/PSS 的掩码生成函数,"probably" 不受Android 使用的密码库。
我试图签署一个空的(0 字节)消息,不知何故似乎是 "problematic"。
当我both 将 MGF 更改为 SHA-256 时 and 生成了消息 64字节长,签名生成成功。
现在,"requirements" 看起来有点 "weird"。
首先,您可以确实使用SHA-512作为RSA/PSS的MGF,只要您setUserAuthenticationRequired(false),因此它必须 得到密码库的支持。只有当您启用身份验证时,它突然失败并且您 必须 退回到 SHA-256。我没有执行广泛的测试,哪些散列函数可以作为 RSA/PSS 的 MGF 使用身份验证,哪些不能。我刚刚发现 SHA-512 不起作用,但 SHA-256 起作用,所以 MGF 的选择在某种程度上是 "restricted" 身份验证时已启用。
其次,您的邮件需要有一定的最小大小,以便在启用身份验证的情况下对其进行签名。例如,您不能签署空缓冲区。这对我来说毫无意义,因为 RSA/PSS 的第一步是对消息应用加密哈希函数,其输出是固定长度的,所以签名方案真的不应该关心多长或多短消息是,但显然是这样。和以前一样,我没有执行广泛的测试来找到消息变为 "long enough" 以进行签名的确切截止点。但是,我发现可以对 64 字节的消息进行签名,而不能对空(0 字节)消息进行签名,因此最小长度在 [1; 64] 字节,包括两个限制。
请注意,截至目前,这似乎无处记录,而且抛出的异常也没有用。它只是说 "signature verification failed"(是的,它说“验证”,即使我们实际上是 生成 签名),所以你有不知道您必须更改 MGF 和要签名的消息的长度。
因此,可能还有更多我没有找到的内容。我刚刚通过 "trial and error" 发现了这个参数化,因此不知道密码库的实际约束是什么样的。