java - 在Java中生成有效的ES256签名

标签 java cryptography jwt mapkit cxf

我正在尝试集成 Apple Map Web Snapshot,它需要 URL 中的签名查询参数。我能够从 NPM 成功生成并验证 JWA 包中的 ES256 签名,但不能在 Java 中生成和验证。请帮助我找到等效的库来生成有效签名,我已经尝试了一些 Java 中的 JWA 库。

// Required modules.
const { readFileSync } = require("fs");
const { sign } = require("jwa")("ES256");

/* Read your private key from the file system. (Never add your private key
 * in code or in source control. Always keep it secure.)
 */ 
const privateKey = readFileSync("[file_system_path]");
// Replace the team ID and key ID values with your actual values.
const teamId = "[team ID]";
const keyId = "[key ID]";

// Creates the signature string and returns the full Snapshot request URL including the signature.
function sign(params) {
    const snapshotPath = `/api/v1/snapshot?${params}`;
    const completePath = `${snapshotPath}&teamId=${teamId}&keyId=${keyId}`;
    const signature = sign(completePath, privateKey);
    // In this example, the jwa module returns the signature as a Base64 URL-encoded string.

    // Append the signature to the end of the request URL, and return.
    return `${completePath}&signature=${signature}`;
}

// Call the sign function with a simple map request.
sign("center=apple+park") 

// The return value expected is: "/api/v1/snapshot?center=apple+park&teamId=[team ID]&keyId=[key ID]&signature=[base64_url_encoded_signature]"

Apache CXF - 该库在节点中生成类似于 JWA 模块,但无法进行身份验证。

      String teamId = [Team Id];
       String keyId = [Key id];
       String privateKey = [private key path];

       String privateKeyContent = getKeyFileContent(privateKey);

       String API_VERSION_PATH = "/api/v1/snapshot?";

       String param = [QueryParam];

       //example -> param = "center=[city,country or lat,lang]&size=90x90&lang=en&radius=2";

       String params = param + "&teamId="+ teamId + "&keyId=" + keyId;

       String payload = API_VERSION_PATH + params;

       PrivateKey key = KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(
               Base64.decodeBase64(privateKeyContent)));

       JwsCompactProducer compactProducer = new JwsCompactProducer(payload);
       compactProducer.getJwsHeaders().setSignatureAlgorithm(SignatureAlgorithm.ES256);
       //compactProducer.getJwsHeaders().setKeyId(keyId);


       compactProducer.signWith(key);

       String signed = compactProducer.getEncodedSignature();

       String encodedSignature = new String(Base64.encodeBase64URLSafe(compactProducer.getEncodedSignature().getBytes()));

       System.out.println(SNAPSHOT_API_PATH + payload + "&signature=" + signed);

JJWT - 该库生成大签名,然后在节点模块中生成签名。

String signed = new String(Base64.encodeBase64URLSafe(Jwts.builder().setPayload(payload)
                .signWith(io.jsonwebtoken.SignatureAlgorithm.ES256, key).compact().getBytes()));

        System.out.println(SNAPSHOT_API_PATH + payload + "&signature=" + signed);

示例输出签名

compactProducer.getEncodedSignature() signed --> qQ5G9_lwGJ9w158FVSmtPx_iH43xlg2_gx9BlHEJbER73xpAeIHtDRnT8wnveH_UEPxNe7Zgv4csJ48Oiq-ZIQ

Base64.encodeBase64URLSafe(signature) --> cVE1RzlfbHdHSjl3MTU4RlZTbXRQeF9pSDQzeGxnMl9neDlCbEhFSmJFUjczeHBBZUlIdERSblQ4d252ZUhfVUVQeE5lN1pndjRjc0o0OE9pcS1aSVE

JJWT signed -> ZXlKaGJHY2lPaUpGVXpJMU5pSjkuTDJGd2FTOTJNUzl6Ym1Gd2MyaHZkRDlqWlc1MFpYSTlRM1Z3WlhKMGFXNXZMRlZUUVNaMFpXRnRTV1E5V0ZaWU5GWlhSbEZUTXlaclpYbEpaRDFWUVRWTlNGWlhWMWhMLlExUEtoeGwzSjFoVWVUWGtmeXRLckliYm5zeDdZem5lZVpxTVc4WkJOVU9uLVlYeFhyTExVU05ZVTZCSG5Xc3FheFd3YVB5dlF0Yml4TVBSZGdjamJ3

最佳答案

NodeJS代码中的签名是由jwa('ES256')#sign方法生成的,该方法具有以下功能:

  1. ES256:使用 P-256 曲线和 SHA-256 哈希算法 [1] 的 ECDSA。
  2. 签名将是 (r, s) 对,其中 r 和 s 是 256 位无符号整数 [2]
  3. 签名采用 base64url 编码 [3]

广告 1:ES256 的相应实现可以使用板载方式在 Java 中实现(SunEC 提供商,Java 1.7 或更高版本),[4] :

Signature ecdsa = Signature.getInstance("SHA256withECDSA");
ecdsa.initSign(privateKey);     
String payload = "The quick brown fox jumps over the lazy dog";
ecdsa.update(payload.getBytes(StandardCharsets.UTF_8));
byte[] signatureDER = ecdsa.sign();

这里privateKeyjava.security.PrivateKey类型的私钥,类似于CXF代码中的key

Ad 2:Java 代码返回 ASN.1 DER 格式的签名,因此必须转换为 (r,s) 格式 [5] 。可以实现用户定义的方法,也可以使用支持库中的方法,例如Nimbus JOSE + JWT 库的方法 com.nimbusds.jose.crypto.impl.ECDSA.transcodeSignatureToConcat [6] [7] [8] :

byte[] signature = transcodeSignatureToConcat(signatureDER, 64);

广告 3:在 Java 中可以使用内置装置 [9] 进行 Base64url 编码:

String signatureBase64url = Base64.getUrlEncoder().withoutPadding().encodeToString(signature);

由于每次都会生成不同的签名,因此无法直接比较两个代码中生成的签名。不过,可以通过使用jwa-npm库验证Java代码中生成的签名来测试与jwa-npm库的兼容性:

const jwa = require("jwa");
const ecdsa = jwa('ES256');

var message = "The quick brown fox jumps over the lazy dog";
var verify = ecdsa.verify(message, signatureBase64url, publicKey);

这里,signatureBase64url是用Java代码生成的签名。 publicKey 是对应的 X.509 PEM 格式的公钥 (-----BEGIN PUBLIC KEY-----...) [10]

<小时/>

jwa('ES256')#sign 方法的功能与发布的 JJWT 或 Apache CXF 代码的功能不同:最后两个生成 JWT [11] 。 header是{"alg": "ES256"}的base64url编码。因此,签名是 Base64url 编码的 header 和 Base64url 编码的负载的签名,两者之间用点分隔:

String payload = "The quick brown fox jumps over the lazy dog";

//JJWT
String jwtJJWT = Jwts.builder().setPayload(payload).signWith(io.jsonwebtoken.SignatureAlgorithm.ES256, privateKey).compact();

//CXF
JwsCompactProducer compactProducer = new JwsCompactProducer(payload);
compactProducer.getJwsHeaders().setSignatureAlgorithm(SignatureAlgorithm.ES256);
String jwtCXF = compactProducer.signWith(privateKey);
String signatureCXF = compactProducer.getEncodedSignature(); // signature, 3. portion of JWT

由此生成的 JWT 示例:

eyJhbGciOiJFUzI1NiJ9.VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw.rcrzqr3ovu7SH9ci-S6deLn6BuiQkNv9CmeOnUPwva30pfK9BOX0YOgjZP5T08wjxCBTTHV3aex0M76toL8qpw

关于java - 在Java中生成有效的ES256签名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59907482/

相关文章:

Java Long 原始类型最大限制

java - 绘制另一个组件

java - 无法在 Android 上生成 RSA 私钥

encryption - 客户端加密 - 最佳实践

java - 用于 Java 的 JWT(JSON Web token )库

java - 在 Java 中通过 SSL 使用 WebService

java - 发送 C# 和 Java 的套接字数据 - 总体和具体

c# - 如何将 Rijndael 加密与 .Net Core 类库一起使用? (不是 .Net 框架)

laravel 5.5 不支持 jwt 授权库

c# - .net core 2.2 应用程序的 JWT 身份验证不使用身份