【问题标题】:Android - require fingerprint authentication to access (RSA/PSS) signing keyAndroid - 需要指纹身份验证才能访问 (RSA/PSS) 签名密钥
【发布时间】: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 上创建签名,我...

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

在回调内部,密钥的使用是经过验证的,所以我...

  1. 再次从CryptoObject 包装器中提取Signature 对象
  2. 使用 Signature 对象上的 update(...) 方法将要签名的数据 (message) 流式传输到签名算法中
  3. Signature对象使用sign()方法获取签名
  4. 将该签名编码为Base64println(...) 输出到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 中,在顶级 &lt;manifest ...&gt; ... &lt;/manifest&gt; 标记内。

&lt;uses-permission android:name="android.permission.USE_FINGERPRINT" /&gt;

运行项目并让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


【解决方案1】:

将您的 getPrivateKey 方法更改为:

 private PrivateKey getPrivateKey() {

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

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

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

如果要检查某个键是否存在:

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

【讨论】:

    【解决方案2】:

    终于找到了解决办法。

    这里实际上有两个问题。

    1. 我尝试使用 SHA-512 作为 RSA/PSS 的掩码生成函数,Android 的加密库“可能”不支持该函数用途。

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

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

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

    首先,您可以确实使用 SHA-512 作为 RSA/PSS 的 MGF,只要您setUserAuthenticationRequired(false),所以它必须得到加密库的支持。只有当您启用身份验证时,它才会突然失败并且您必须回退到 SHA-256。我没有进行广泛的测试,哪些散列函数可以用作具有身份验证的 RSA/PSS 的 MGF,哪些不可以。我刚刚发现 SHA-512 不起作用,但 SHA-256 起作用,所以当启用身份验证时,MGF 的选择在某种程度上受到了“限制”。

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

    请注意,到目前为止,这似乎没有任何记录,而且抛出的异常也没有用。它只是说“签名验证失败”(是的,它说“verification”,即使我们实际上是在生成签名),所以你不知道你必须更改 MGF 和要签名的消息的长度。

    因此,我可能还没有找到更多内容。我只是通过“反复试验”发现了这种参数化,因此不知道密码库的实际约束是什么样的。

    【讨论】:

      猜你喜欢
      • 2019-06-20
      • 1970-01-01
      • 2021-01-12
      • 1970-01-01
      • 1970-01-01
      • 2016-11-01
      • 2017-06-21
      • 2015-11-03
      • 2012-03-25
      相关资源
      最近更新 更多