我有一些声明,我想创建JWT并使用在指纹API中创建的私钥对其进行签名。
这是智威汤逊的说法——
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)
到目前为止,我尝试了许多其他方法使用私钥签署 JWT,但我没有找到解决方案。
任何帮助不胜感激
您创建的密钥仅用于加密,不用于签名。改变
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());
使用 gradle 依赖项
compile group: 'com.nimbusds', name: 'nimbus-jose-jwt', version: '4.41.1'
库我能够解决问题并使用AndroidKeyStoreRSAPrivateKey签署JWT
这里RSASSASigner构造函数,它从Android KeyStore获取PrivateKey,这个签名者用于签署JWSObject。
在寻找解决方案时,我没有在网络上找到太多关于此的信息,因此在此处发布有关如何使用来自android指纹API的私钥对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
对于任何阅读找到这个问题和答案的人来说,值得一提的是,这个密钥没有指纹保护 - (setUserAuthenticationRequired(true)
没有在密钥上设置,并且BiometricPrompt
没有用于批准签名操作。
要使用 jose4j 正确执行此操作,您需要使用它jws.prepareSigningPrimitive()
方法 - https://bitbucket.org/b_c/jose4j/issues/176/signing-not-possible-with-an 有一个讨论和一个完整示例的链接。