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

时间:2017-07-04 14:22:05

标签: java android authentication rsa fingerprint

我目前正在为我的计算机科学硕士论文所需的项目创建一种质询 - 响应认证形式。

为此,我需要创建一个带有私钥的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. 的实例
  3. 创建SHA512withRSA/PSS签名算法(Signature对象)
  4. 的实例
  5. 使用私钥(Signature
  6. 初始化initSign(...)算法以进行签名
  7. Signature对象包装到CryptoObject
  8. (执行一些额外的检查)
  9. authenticate(...) CryptoObject使用FingerprintManager的实例,在用户验证密钥后通过(以及其他)传递FingerprintManager.AuthenticationCallback(通过触摸)他/她的设备上的指纹传感器)
  10. 在回调中,密钥的使用已经过身份验证,因此我......

    1. 再次从Signature包装器中提取CryptoObject对象
    2. 使用update(...)对象上的Signature方法将要签名的数据(message)流式传输到签名算法中
    3. 使用sign()对象上的Signature方法获取签名
    4. 将该签名编码为Base64,将println(...)编码为 StdErr ,使其显示在adb logcat
    5. 我创建了一个示例代码,它的大小与它一样小。

      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 中创建一个包含单个活动的项目。在此活动中插入两个按钮,一个用于生成密钥(即标记为生成),另一个用于创建签名(即标记为 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与它一起运行。

      点击 Generate 按钮后,您应该会在日志中看到这样的输出。

      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 夜莺,他们都失败了。但是,当我考虑API文档时,似乎我正在做正确的事情,并且 - 说实话 - 并不是很多东西你实际上可能做的不同。它实际上看起来非常明显。

      请注意,只要我需要用户身份验证 - 因此不要在回调方法中创建签名,而是在initSign(...)之后立即创建签名 - 它工作正常 - 即使是由 TrustZone 中的Keymaster / Gatekeeper硬件支持的密钥存储。但是,只要我需要身份验证, - 并因此对回调中的update(...)对象进行sign()Signature调用 - 它就会分崩离析。

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

      有什么建议吗?我已经走了很长的路,并实施了大量的东西,包括服务器端和 Android ,以使这个项目继续前进,但现在我陷入了困境,似乎无法执行加密声音的用户身份验证。

      我尝试将KeyProperties.SIGNATURE_PADDING_RSA_PSS替换为KeyProperties.SIGNATURE_PADDING_RSA_PKCS1,将SHA512withRSA/PSS替换为SHA512withRSA,然后将KeyProperties.DIGEST_SHA512替换为KeyProperties.DIGEST_SHA256,将SHA512withRSA替换为SHA256withRSA {1}}。我还尝试了一个更小的密钥大小 - 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()的调用保持在一起是有意义的。

      非常感谢任何帮助。

2 个答案:

答案 0 :(得分:0)

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 :(得分:0)

终于找到了解决方案。

这里实际上有两个问题。

  1. 我尝试使用 SHA-512 作为RSA / PSS的掩码生成函数,这可能是&#34;可能&#34; Android 使用的加密库不支持。

  2. 我试图签署一个空的(0字节)消息,这个消息在某种程度上似乎有问题&#34;。

  3. 两者将MGF更改为 SHA-256 并且使消息长度为64字节时,签名生成成功。

    现在,两个&#34;要求&#34;看起来有点“怪异”#34;

    首先,可以确实使用 SHA-512 作为RSA / PSS的MGF,只要您 setUserAuthenticationRequired(false),所以加密库支持 。只有当您启用身份验证时突然失败并且 才能回退到 SHA-256 。我没有进行大量测试,哪些哈希函数用作带有身份验证的RSA / PSS的MGF,哪些不用。我刚发现 SHA-512 不起作用,但 SHA-256 不行,所以选择MGF是以某种方式&#34;限制&#34;何时启用身份验证。

    其次,您的邮件需要具有一定的最小尺寸才能在启用身份验证的情况下进行签名。例如,您无法签署空缓冲区。这对我来说毫无意义,因为RSA / PSS的第一步是对消息应用加密散列函数,其输出是固定长度的,因此签名方案真的不应该关注多长时间或多短消息是,但显然确实如此。像以前一样,我没有进行大量的测试,以找到消息变得足够长的确切截止点&#34;签字。但是,我发现64字节消息可以被签名,而空(0字节)消息不能,所以最小长度在[1; 64个字节,两个都包含限制。

    请注意,截至目前,这似乎无处可记录,并且抛出的异常也没有用。它只是说&#34;签名验证失败&#34; (是的,它说&#34; 验证&#34;即使我们实际上生成签名),所以你不知道你必须更改MGF和要签名的消息的长度。

    由于这个原因,可能还有更多我找不到的东西。我刚刚通过&#34;试验和错误&#34;找到了这个参数化。因此不知道加密库的实际约束是什么样的。