繁体   English   中英

Android-需要指纹认证才能访问(RSA / PSS)签名密钥

[英]Android - require fingerprint authentication to access (RSA/PSS) signing key

我目前正在为我的计算机科学硕士学位论文所需的项目创建一种挑战响应身份验证形式。

为此,我需要使用通过指纹进行身份验证的私钥创建RSA-PSS签名,以便仅当设备所有者实际在场时才可以使用它来创建签名。

为此,我使用Android KeyStore (由ARM TrustZone中的Keymaster / Gatekeeper支持)生成RSA密钥对( KEY_ALGORITHM_RSA ),以与RSA-PSS签名算法( SIGNATURE_PADDING_RSA_PSS )一起使用来创建和验证签名( PURPOSE_SIGN | PURPOSE_VERIFY ) 。 我还需要通过将相应的属性设置为true来进行用户认证。

稍后,要在缓冲区的final byte[] message上创建签名,我...

  1. 获取FingerprintManager服务的实例
  2. 创建SHA512withRSA/PSS签名算法的实例( Signature对象)
  3. 初始化用于使用私钥进行签名的Signature算法( initSign(...)
  4. Signature对象包装到CryptoObject
  5. (执行一些其他检查)
  6. 使用FingerprintManager的实例对CryptoObject进行authenticate(...) ,并(其中包括)传递一个FingerprintManager.AuthenticationCallback以在用户对密钥进行身份验证之后调用(通过触摸其设备上的指纹传感器)

在回调内部,密钥的使用已通过身份验证,因此我...

  1. 再次从CryptoObject包装器中提取Signature对象
  2. 使用Signature对象上的update(...)方法将要Signature的数据( message )流式传输到签名算法
  3. Signature对象上使用sign()方法获取签名
  4. 将该签名编码为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中创建一个具有单个活动的项目。 在此活动中插入两个按钮,一个按钮用于生成密钥(即标记为Generate ),另一个按钮用于创建签名(即标记为Sign )。

然后将示例代码插入到您的主要活动中,并将onclick事件从“ 生成”按钮链接到public void generate(final View view)方法,从“ 签名”按钮链接到public void sign(final View view)方法。

最后,将以下内容插入顶级<manifest ...> ... </manifest>标记内的AndroidManifest.xml

<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

这就是我卡住的地方。

奇怪的是,由于SignatureException的消息,我无法进行Signature/MAC verification failed 请注意,它说verification failed ,虽然我实际上在签名验证),并且整个堆栈跟踪都显示,但仅调用signSomething(...)函数。

我已经在LG Nexus 5X上使用官方固件(Android 7.1.1, N2G47W )和其他(最新的) LineageOS夜间软件进行了尝试,但它们都在此时失败。 但是,当我考虑API文档时,似乎我在做正确的事情,而且-老实说-实际上,您可以做的事情并不多。 实际上,它的工作原理似乎非常明显。

需要注意的是,只要我要求用户身份验证-因此不会产生在回调方法的签名,但外面,右后initSign(...) -它工作得很好-即使硬件支持的密钥存储由TrustZone中的Keymaster / Gatekeeper提供。 但是,一旦我需要身份验证,并因此对回调内的Signature对象进行了update(...)sign()调用,它们就会全部中断。

我试图在OpenSSL库中查找错误,或找出-30响应代码的含义,但两者均无济于事。

有什么建议么? 我已经走了很长一段路,并在服务器端和Android上实现了大量东西,以使该项目得以继续进行,但是现在我陷入了困境,似乎无法执行加密方式的用户身份验证。

我尝试将KeyProperties.SIGNATURE_PADDING_RSA_PSS替换为KeyProperties.SIGNATURE_PADDING_RSA_PKCS1SHA512withRSA/PSS替换为SHA512withRSA ,然后将KeyProperties.DIGEST_SHA512KeyProperties.DIGEST_SHA256SHA512withRSASHA256withRSA 我还尝试了较小的密钥大小-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()的调用保持在一起是有意义的。

任何帮助都非常感谢。

将您的getPrivateKey方法更改为:

 private PrivateKey getPrivateKey() {

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

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

在您的代码中,您遍历所有密钥,并grep最后一个密钥,这不一定是您想要的那个-甚至更糟:如果该密钥没有私钥,则返回null

如果要检查密钥是否存在:

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

终于找到了解决方案。

实际上,这里有两个问题。

  1. 我试图将SHA-512用作RSA / PSS的掩码生成功能, Android使用的加密库“可能”不支持SHA / 512

  2. 我试图签署一个空的(0字节)消息,该消息似乎有点“有问题”。

当我将MGF 更改为SHA-256 并将消息的长度设置为64字节时,签名生成就成功了。

现在,两个“要求”似乎都有些“怪异”。

首先,你的确可以使用SHA-512作为MGF的RSA / PSS,只要你setUserAuthenticationRequired(假的),所以它必须通过密码库的支持。 仅当启用身份验证时,它突然失败,并且您必须退回到SHA-256 我没有进行广泛的测试,哪些哈希函数可以用作带有身份验证的RSA / PSS的MGF,而哪些不能。 我只是发现SHA-512不起作用,而SHA-256起作用,因此启用身份验证后,MGF的选择受到某种程度的“限制”。

其次,您的消息需要具有一定的最小大小,以便在启用身份验证的情况下对其进行签名。 例如,您不能签署一个空的缓冲区。 这对我完全没有意义,因为RSA / PSS的第一步是对消息应用加密哈希函数,该函数的输出是固定长度的,因此签名方案实际上不必关心消息的长度是多少。是,但显然确实如此。 像以前一样,我没有进行广泛的测试来找到消息变得“足够长”以进行签名的确切截止点。 但是,我发现可以对64字节的消息进行签名,而对空白(0字节)的消息不能进行签名,因此最小长度在[1; 64]个字节(包括两个上限)。

请注意,到目前为止,这似乎无处可查,抛出的异常也没有用。 它只是说“签名验证失败”(是的,即使我们实际上正在生成签名,它也说“ 验证 ”),因此您不知道必须更改MGF和要签名的消息的长度。

因此,可能还有更多我尚未发现的东西。 我只是通过“尝试和错误”发现了这种参数化,因此不知道密码库的实际约束是什么样的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM