【问题标题】:Sign JWT with PrivateKey from android Fingerprint API使用来自 android Fingerprint API 的 PrivateKey 签署 JWT
【发布时间】:2017-08-14 23:47:51
【问题描述】:

我有一些声明,我想创建 JWT 并使用在指纹 API 中创建的 PrivateKey 对其进行签名。

这是 JWT 声明 -

Header:

{
     "alg": "RS256”,
     “kid”: “ABCDEDFkjsdfjaldfkjg”,
      “auth_type” : “fingerprint” / "pin"
}

Payload:
{
      “client_id”:”XXXXX-YYYYYY-ZZZZZZ”
}

为指纹创建密钥对 -

import android.os.Build;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.support.annotation.RequiresApi;
import android.util.Log;

import com.yourmobileid.mobileid.library.common.MIDCommons;

import org.jose4j.base64url.Base64;

import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
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.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.spec.RSAKeyGenParameterSpec;


@RequiresApi(api = Build.VERSION_CODES.M)
public class BiometricHelper {

    public static final String KEY_NAME = "my_key";
    static KeyPairGenerator mKeyPairGenerator;
    private static String mKid;
    private static KeyStore keyStore;

    public static void init() {
        try {
            mKeyPairGenerator = KeyPairGenerator.getInstance(  KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
        } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
            throw new RuntimeException("Failed to get an instance of KeyPairGenerator", e);
        }
        mKid = MIDCommons.generateRandomString();

         keyStore = null;

        try {
            keyStore = KeyStore.getInstance("AndroidKeyStore");
        } catch (KeyStoreException e) {
            throw new RuntimeException("Failed to get an instance of KeyStore", e);
        }

        createKeyPair();
    }


    /**
     * Generates an asymmetric key pair in the Android Keystore. Every use of the private key must
     * be authorized by the user authenticating with fingerprint. Public key use is unrestricted.
     */
    public static void createKeyPair() {
        try {

            mKeyPairGenerator.initialize(
                    new KeyGenParameterSpec.Builder(
                            KEY_NAME,
                            KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
                            .setAlgorithmParameterSpec(new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4))
                            .build());
            mKeyPairGenerator.generateKeyPair();
        } catch (InvalidAlgorithmParameterException e) {
            throw new RuntimeException(e);
        }
    }


    public static PrivateKey getPrivateKey() {
        PrivateKey privateKey = null;
        try {
            keyStore.load(null);
            privateKey = (PrivateKey) keyStore.getKey(KEY_NAME, null);
        } catch (KeyStoreException | CertificateException | UnrecoverableKeyException | NoSuchAlgorithmException | IOException e) {
            e.printStackTrace();
        }
        return privateKey;
    }

    public static PublicKey getPublicKey() {
        PublicKey publicKey = null;
        try {
            keyStore.load(null);
            publicKey = keyStore.getCertificate(KEY_NAME).getPublicKey();
        } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) {
            e.printStackTrace();
        }
        return publicKey;
    }

    public static KeyStore getKeyStore(){
        return keyStore;
    }
    public static String getPublicKeyStr()  {
        StringBuilder publicKey = new StringBuilder("-----BEGIN PUBLIC KEY-----\n");
        publicKey.append(Base64.encode((getPublicKey().getEncoded())).replace("==",""));
        publicKey.append("\n-----END PUBLIC KEY-----");


        Log.d("Key==","\n"+publicKey);
        return publicKey.toString();
    }

    public static String getKid() {

        Log.d("mKid==","\n"+mKid);
        return mKid;
    }
 }

并以这种方式创建 JWT -

@RequiresApi(api = Build.VERSION_CODES.M)
private String createJWT(){

    JwtClaims claims = new JwtClaims();
    claims.setClaim("client_id","”XXXXX-YYYYYY-ZZZZZZ”"); 

    JsonWebSignature jws = new JsonWebSignature();

    jws.setPayload(claims.toJson());
    jws.setKey(BiometricHelper.getPrivateKey());
    jws.setKeyIdHeaderValue(BiometricHelper.getKid());
    jws.setHeader("auth_type","fingerprint");
    jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);

    String jwt = null;
    try {
        jwt = jws.getCompactSerialization();

    } catch (JoseException e) {
        e.printStackTrace();
    }
    System.out.println("JWT: " + jwt);

    return jwt;
}

当我这样做时,它正在得到 -

W/System.err: org.jose4j.lang.InvalidKeyException: The given key (algorithm=RSA) is not valid for SHA256withRSA
W/System.err:     at org.jose4j.jws.BaseSignatureAlgorithm.initForSign(BaseSignatureAlgorithm.java:97)
W/System.err:     at org.jose4j.jws.BaseSignatureAlgorithm.sign(BaseSignatureAlgorithm.java:68)
W/System.err:     at org.jose4j.jws.JsonWebSignature.sign(JsonWebSignature.java:101)

到目前为止,我尝试了许多其他方式来使用 PrivateKey 签署 JWT,但我没有找到解决方案。

感谢任何帮助

【问题讨论】:

    标签: android jwt digital-signature android-fingerprint-api


    【解决方案1】:

    您创建的密钥仅用于加密,而不用于签名。改变

    mKeyPairGenerator.initialize(
            new KeyGenParameterSpec.Builder(
                        KEY_NAME,
                        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
                        .setAlgorithmParameterSpec(new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4))
                        .build());
    

    mKeyPairGenerator.initialize(
          new KeyGenParameterSpec.Builder(
                      KEY_NAME,
                      KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
                      .setDigests(KeyProperties.DIGEST_SHA256)
                      .setAlgorithmParameterSpec(new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4))
                      .build());
    

    【讨论】:

    • 感谢您的帮助@pedrofb。是的,更改 KeyProperties 是有意义的。来自 KeyProperties.PURPOSE_ENCRYPT 的 PURPOSE_SIGN 但我仍然遇到 ClassCastException 的问题:android.security.keystore.AndroidKeyStoreRSAPrivateKey 无法转换为 java.security.interfaces.RSAPrivateKey
    • 你能发布新的堆栈跟踪吗?您是否检查过 jose4j 是否与 Android 密钥库兼容?私钥受到保护以防提取。如果库试图操纵它们,它将失败。
    • 感谢您的评论实际上帮助了我,当我搜索与 Android 密钥库兼容的 jose4j 时,我发现他们知道错误,并且已在最新版本中修复。bitbucket.org/b_c/jose4j/pull-requests/8/…
    • 那么,你的问题解决了吗?您需要进一步说明吗?
    【解决方案2】:

    使用 gradle 依赖

    compile group: 'com.nimbusds', name: 'nimbus-jose-jwt', version: '4.41.1'
    

    库我能够解决问题并使用 AndroidKeyStoreRSAPrivateKey 签署 JWT

    这里的 RSASSASigner 构造函数从 Android KeyStore 获取 PrivateKey,这个签名者用于签署 JWSObject。

    在寻找解决方案时,我在 Web 上没有找到太多关于此的信息,因此在此处发布解决方案,了解如何使用来自 android Fingerprint API 的 PrivateKey 对 JWT 进行签名。感谢 pedrofb 的帮助:)

    @RequiresApi(api = Build.VERSION_CODES.M)
    private String createJWT(){
        RSASSASigner signer = new RSASSASigner(BiometricHelper.getPrivateKey());
        JSONObject message = new JSONObject();
        message.put("client_id",mConfiguration.getClientID());
    
        JWSObject jwsObject = new JWSObject(
                new JWSHeader.Builder(JWSAlgorithm.RS256).keyID(BiometricHelper.getKid())
               .customParam("auth_type","touchid").build(),new Payload(message ));
        try {
            jwsObject.sign(signer);
        } catch (JOSEException e) {
            e.printStackTrace();
        }
    
        String jwt = jwsObject.serialize();
    
        Log.d("JWT============","\n"+jwt);
    
        return jwt;
    }
    

    在处理这件事时,我遇到了 Nimbus-JOSE-JWT 旧版本 https://bitbucket.org/connect2id/nimbus-jose-jwt/issues/169/android-m-support 中报告的一些错误

    【讨论】:

      【解决方案3】:

      对于阅读此问题和答案的任何人,值得一提的是,此密钥不受指纹保护 - (setUserAuthenticationRequired(true) 未设置在密钥上,BiometricPrompt 未用于批准签名操作。

      要使用 jose4j 正确执行此操作,您需要使用它的 jws.prepareSigningPrimitive() 方法 - https://bitbucket.org/b_c/jose4j/issues/176/signing-not-possible-with-an 有一个讨论和一个完整示例的链接。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-05-17
        • 1970-01-01
        • 1970-01-01
        • 2012-06-14
        • 2019-04-20
        • 2019-08-27
        相关资源
        最近更新 更多