如何从 Java 中的 Apple 公钥 JSON 响应中获取公钥?

Posted

技术标签:

【中文标题】如何从 Java 中的 Apple 公钥 JSON 响应中获取公钥?【英文标题】:How to get public key from Apple public key JSON response in Java? 【发布时间】:2020-03-30 02:28:56 【问题描述】:

我们正在尝试在我们的 ios 应用中添加 Sign In with Apple。当客户端工作正常时,我们的后端是用 Java 编写的,我们无法解码 Apple 的公钥。当您点击 URL https://appleid.apple.com/auth/keys 时,它会为您提供公钥。但是,当我尝试将 PublicKey 对象设为 Java 时,它无法识别那里的 ne 值。那些是 Base64 编码的吗?

当我尝试解码 Base64 中的 ne 值时,它给了我 illegal character 2d。我怀疑它是 base64 的原因是在 NodeJS 包 (https://www.npmjs.com/package/node-rsa) 中,他们正在通过 base64 解码 n 和 e 值。但问题是指数值(e)是AQAB,它永远不会是base64。如何从中创建 PublicKey 对象?

我使用的代码是:

HttpResponse<String> responsePublicKeyApple =  Unirest.get("https://appleid.apple.com/auth/keys").asString();

ApplePublicKeyResponse applePublicKeyResponse = new Gson().fromJson(responsePublicKeyApple.getBody(),ApplePublicKeyResponse.class);

System.out.println("N: "+applePublicKeyResponse.getKeys().get(0).getN());
System.out.println("E: "+applePublicKeyResponse.getKeys().get(0).getE());

byte[] decodedBytesE = Base64.getDecoder().decode(applePublicKeyResponse.getKeys().get(0).getE());
String decodedE = new String(decodedBytesE);

System.out.println("decode E: "+decodedE);

byte[] decodedBytesN = Base64.getDecoder().decode(applePublicKeyResponse.getKeys().get(0).getN());
String decodedN = new String(decodedBytesN);

System.out.println("decode N: "+decodedN);

BigInteger bigIntegerN = new BigInteger(decodedN,16);
BigInteger bigIntegerE = new BigInteger(decodedE,16);

RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(bigIntegerN,bigIntegerE);
KeyFactory keyFactory = KeyFactory.getInstance(SignatureAlgorithm.RS256.getValue());
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);

解码部分ne 值失败。另一件事是苹果的回应是说他们使用RS256 算法来签署令牌但是当我尝试这样做时

KeyFactory keyFactory = KeyFactory.getInstance(SignatureAlgorithm.RS256.getValue());

上面写着RS256 keyfactory is not available

如何解决这两个问题?请帮忙。

【问题讨论】:

RS256 是使用 RSA 和 SHA-256 哈希的签名算法。如果要创建/解析 RSA 密钥,请使用 KeyFactory.getInstance("RSA"); @michalk 感谢您的澄清。如何从响应的 n 和 e 值创建 PublicKey 对象? 【参考方案1】:

实际上这些NE 值是使用base64url 编码的,如RFC7518 中所述。此代码将向您展示如何执行您的请求。我使用 Jackson 来阅读您提供的 JSON:

String json = Files.lines(Paths.get("src/main/resources/test.json")).collect(Collectors.joining());
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

// here is the parsing of PublicKey
ApplePublicKeyResponse applePublicKeyResponse = objectMapper.readValue(json, ApplePublicKeyResponse.class);
        
Key key = applePublicKeyResponse.getKeys().get(0);

byte[] nBytes = Base64.getUrlDecoder().decode(key.getN());
byte[] eBytes = Base64.getUrlDecoder().decode(key.getE());

BigInteger n = new BigInteger(1, nBytes);
BigInteger e = new BigInteger(1, eBytes);

RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(n,e);
KeyFactory keyFactory = KeyFactory.getInstance(key.getKty()); //kty will be "RSA"
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);

如前所述,key.getKty() 将返回"RSA"。因此,您应该将此值传递给 KeyFactory.getInstance,因为您要解析 RSA 密钥,而 RS256 是使用 RSA 和 SHA-256 哈希的签名算法的名称。

我使用了BigInteger 的构造函数,它接受符号和原始字节。 Signum 设置为 1 以获得正值。

【讨论】:

以上是关于如何从 Java 中的 Apple 公钥 JSON 响应中获取公钥?的主要内容,如果未能解决你的问题,请参考以下文章

Apple Pay - 如何将商家公钥与支付令牌中的 publicKeyHash 进行比较?

java - 如何将字符串化的json对象转换回java中的json? [复制]

在没有 Bouncy Castle 的情况下,如何在 java 中从私有(ecdsa)生成公钥?

如何从 cloudformation 模板中的参数文件传递公钥?

我可以存储 Apple 的公钥来验证令牌签名吗?

如何从 python 中的 x509 证书中提取公钥?