【发布时间】:2017-07-04 14:22:04
【问题描述】:
我目前正在为我的计算机科学硕士论文所需的项目创建一种质询-响应身份验证形式。
为此,我需要使用通过指纹进行身份验证的私钥创建 RSA-PSS 签名,以便仅当设备所有者实际在场时才能使用它来创建签名。
为了实现这一点,我使用 Android KeyStore(由 ARM TrustZone 中的 Keymaster/Gatekeeper 支持)生成 RSA 密钥对 (KEY_ALGORITHM_RSA) 以用于用于创建和验证签名 (PURPOSE_SIGN | PURPOSE_VERIFY) 的 RSA-PSS 签名算法 (SIGNATURE_PADDING_RSA_PSS)。我还要求通过将相应的属性设置为true来进行用户身份验证。
稍后,要在缓冲区final byte[] message 上创建签名,我...
- 获取
FingerprintManager服务的实例 - 创建
SHA512withRSA/PSS签名算法的实例(Signature对象) - 初始化
Signature算法以使用私钥 (initSign(...)) 进行签名 - 将
Signature对象包装成CryptoObject - (执行一些额外的检查)
-
authenticate(...)CryptoObject使用FingerprintManager的实例,传递(除其他外)一个FingerprintManager.AuthenticationCallback在用户验证密钥后调用(通过触摸他/她设备上的指纹传感器)李>
在回调内部,密钥的使用是经过验证的,所以我...
- 再次从
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,但指纹认证加密需要一些代码才能使其工作,所以我似乎无法让它变得更小/更简单。)
要对其进行测试,只需在 Android Studio 中创建一个包含单个 Activity 的项目。在此活动中插入两个按钮,一个用于生成密钥(即标记为 Generate),另一个用于创建签名(即标记为 Sign)。
然后将示例代码插入到您的主活动中,并将 Generate 按钮中的 onclick 事件链接到 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 与它一起运行。
点击生成按钮后,您应该会在日志中看到类似这样的输出。
07-04 14:46:18.475 6759 6759 D MinimalSignatureTest: Public key: MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICC...
这是已生成的密钥对的公钥。
(您还会看到一些关于在主线程中生成密钥的抱怨,但这只是为了保持示例代码简单。实际的应用程序在其自己的线程中执行密钥生成。)
然后,首先点击Sign,然后触摸传感器。会出现以下错误。
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
这就是我卡住的地方。
奇怪的是我得到了Signature/MAC verification failed 作为SignatureException 的消息。请注意,它说的是verification failed,而我实际上是在签名(不是验证)并且整个堆栈跟踪显示,只有 signSomething(...) 函数被调用。
我已经在 LG Nexus 5X 上尝试过这个,官方固件(Android 7.1.1,N2G47W)和不同的(最新)LineageOS em> nightlies,他们都在这一点上失败了。然而,当我考虑 API 文档时,似乎我在做正确的事情,而且——老实说——实际上并没有很多事情可以做不同的事情。它实际上是如何工作的似乎很明显。
请注意,只要我 不 要求用户身份验证 - 因此不要在回调方法中创建签名,而是在外部,在 initSign(...) 之后 - 它工作正常 -即使在 TrustZone 中由 Keymaster/Gatekeeper 提供硬件支持的密钥存储。但是一旦我需要身份验证, - 因此在回调中对 Signature 对象执行 update(...) 和 sign() 调用 - 这一切都会崩溃。
我试图追踪 OpenSSL 库中的错误或找出 -30 响应代码的含义,但都无济于事。
有什么建议吗?我已经走了很长一段路,并在服务器端和 Android 上实现了大量的东西,以推进这个项目,但现在我被卡住了,似乎无法执行用户身份验证密码健全。
我尝试将KeyProperties.SIGNATURE_PADDING_RSA_PSS 替换为KeyProperties.SIGNATURE_PADDING_RSA_PKCS1,将SHA512withRSA/PSS 替换为SHA512withRSA,然后将KeyProperties.DIGEST_SHA512 替换为KeyProperties.DIGEST_SHA256,将SHA512withRSA 替换为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() 的调用保持在一起是有意义的。
非常感谢任何帮助。
【问题讨论】:
-
请发布代码,而不是引用您正在使用的代码。
-
完成。 (代码现在包含在帖子中。)
-
我已经查看了您的代码,但找不到任何实质性错误。我有一个使用 Nexus 的类似示例,所以我怀疑它可能是不受支持的参数组合。我建议您简化代码并应用试错过程。例如使用不使用 PSS 填充
-
它是,事实上,一个不受支持的参数组合。事实证明,真的很难找到一个可行的参数化。由于约束似乎在任何地方都没有记录,因此您确实必须偶然发现。
标签: java android authentication rsa fingerprint