java - ES256 JWT 验证 - SignatureException : invalid encoding for signature: java. io.IOException:序列标记错误

标签 java jwt cryptography elliptic-curve java-security

我有一个使用 Elliptic Curve ES256 签名的 JWT,我正在尝试验证它:

eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMzQifQ.eyJpc3MiOiJodHRwczovL2p3dC1pZHAuZXhhbXBsZS5jb20iLCJzdWIiOiJtYWlsdG86bWlrZUBleGFtcGxlLmNvbSIsIm5iZiI6MTU3NTY1NjY3NCwiZXhwIjoxOTI0OTkxOTk5LCJpYXQiOjE1NzU2NTY2NzQsImp0aSI6ImlkMTIzNDU2IiwidHlwIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9yZWdpc3RlciJ9.kcj0QQrERKIfny1TfHY-Z9iDFazr84xCssTDuXtV1n1dvY7CYXuP5ZBvpi9ArOQjsS8YCd0bKsWaQ-17VnF_1A

使用此公钥:

-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEoBUyo8CQAFPeYPvv78ylh5MwFZjT
CLQeb042TjiMJxG+9DLFmRSMlBQ9T/RsLLc+PmpB1+7yPAR+oR5gZn3kJQ==
-----END PUBLIC KEY-----

这只是

{
  "kty": "EC",
  "use": "sig",
  "crv": "P-256",
  "kid": "1234",
  "x": "oBUyo8CQAFPeYPvv78ylh5MwFZjTCLQeb042TjiMJxE=",
  "y": "vvQyxZkUjJQUPU/0bCy3Pj5qQdfu8jwEfqEeYGZ95CU=",
  "alg": "ES256"
}

它在 https://jwt.io/ 上正确验证然而,当我尝试使用 native Java 验证它时,它会引发错误:


java.security.SignatureException: invalid encoding for signature: java.io.IOException: Sequence tag error
    at com.ibm.crypto.provider.AbstractSHAwithECDSA.engineVerify(Unknown Source)
    at java.security.Signature$Delegate.engineVerify(Signature.java:1219)
    at java.security.Signature.verify(Signature.java:652)
    at curam.platforminfrastructure.outbound.oauth2.impl.ValidateJWT.validateEllipticCurve(ValidateJWT.java:235)
    at curam.platforminfrastructure.outbound.oauth2.impl.ValidateJWT.validateJWT(ValidateJWT.java:88)
    at curam.platforminfrastructure.outbound.oauth2.impl.ValidateJWT.testJWT(ValidateJWT.java:53)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:89)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209)

我的代码如下:

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.math.BigInteger;
import java.security.AlgorithmParameters;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.SignatureException;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.util.Base64;
import java.util.Base64.Decoder;
import java.util.Base64.Encoder;

public class ValidateJWT {

  final Decoder urlDecoder = Base64.getUrlDecoder();
  final Encoder urlEncoder = Base64.getUrlEncoder();
  final Decoder decoder = Base64.getDecoder();
  final Encoder encoder = Base64.getEncoder();
  final ObjectMapper objectMapper = new ObjectMapper();

  public boolean validateEllipticCurve(final String JWT, final String PUBLICKEY)
      throws IOException {
    final String[] at_arr = JWT.split("\\.");
    final String headerB64u = at_arr[0];
    final String payloadB64u = at_arr[1];
    final String signed_data = headerB64u + "." + payloadB64u;
    final byte[] signature = urlDecoder.decode(at_arr[2]);
    final byte[] at_headerJSON = urlDecoder.decode(headerB64u);

    final JsonNode pkRoot = objectMapper.readTree(PUBLICKEY);
    final String xStr = pkRoot.get("x").textValue();
    final String yStr = pkRoot.get("y").textValue();
    final byte xArr[] = decoder.decode(xStr);
    final byte yArr[] = decoder.decode(yStr);
    final BigInteger x = new BigInteger(xArr);
    final BigInteger y = new BigInteger(yArr);

    try {
      final AlgorithmParameters parameters = AlgorithmParameters
          .getInstance("EC");
      parameters.init(new ECGenParameterSpec("secp256k1"));
      final ECParameterSpec ecParameterSpec = parameters
          .getParameterSpec(ECParameterSpec.class);
      final KeyFactory keyFactory = KeyFactory.getInstance("EC");

      final ECPoint ecPoint = new ECPoint(x, y);
      final ECPublicKeySpec keySpec = new ECPublicKeySpec(ecPoint,
          ecParameterSpec);
      final ECPublicKey publicKey = (ECPublicKey) keyFactory
          .generatePublic(keySpec);

      final JsonNode key_root = objectMapper.readTree(at_headerJSON);
      final String alg = key_root.get("alg").textValue();

      Signature dataVerifyingInstance = null;
      switch (alg) {
      case "ES256":
        dataVerifyingInstance = Signature.getInstance("SHA256withECDSA");
        break;
      case "ES384":
        dataVerifyingInstance = Signature.getInstance("SHA384withECDSA");
        break;
      case "ES512":
        dataVerifyingInstance = Signature.getInstance("SHA512withECDSA");
        break;
      }

      dataVerifyingInstance.initVerify(publicKey);
      dataVerifyingInstance.update(signed_data.getBytes());
      final boolean verification = dataVerifyingInstance.verify(signature);
      return verification;
    } catch (final SignatureException | InvalidKeySpecException
        | InvalidKeyException | InvalidParameterSpecException
        | NoSuchAlgorithmException ex) {
      ex.printStackTrace();
      return false;
    }
  }

}

最后一行似乎引发了异常。

final boolean verification = dataVerifyingInstance.verify(signature);// <== here

我不确定是否是由于使用了错误的曲线(secp256k1、secp256r1、secp256v1)而导致了问题,或者可能是其他原因。任何提示将不胜感激。

最佳答案

问题在于,为 JWS 计算的签名(在椭圆曲线的情况下)是 RS 值的原始串联,如 RFC7515 中指定的那样。 。所以这是 32 + 32 = 64 字节(在 256 位曲线的情况下)。

然而,Java 实现希望此签名采用 DER 序列形式。这就是您遇到此异常的原因 - 只是您的签名(RS 值)不是 DER 编码的。您甚至可以检查负责解码签名 (sun.security.ec.ECDSASignature::decodeSignature) 和编码签名的代码(用于签名操作 - sun.security.ec。 ECDSASignature::encodeSignature)。正如您在源代码中看到的,他们使用来自 sun.ec 包的内部类进行 DER 编码。并且不鼓励您自己使用这些类。

您的最佳选择是将原始签名转换为 DER 编码签名。如果您愿意,可以使用 BouncyCaSTLe,它具有开箱即用的 DER 编码处理功能。以下是将 EC 原始签名转换为 DER 编码签名的方法:

private static byte[] toDerSignature(byte[] jwsSig) throws IOException {

    byte[] rBytes = Arrays.copyOfRange(jwsSig, 0, 32);
    byte[] sBytes = Arrays.copyOfRange(jwsSig, 32, 64);

    BigInteger r = new BigInteger(1, rBytes);
    BigInteger s = new BigInteger(1, sBytes);

    DERSequence sequence = new DERSequence(new ASN1Encodable[] {
            new ASN1Integer(r),
            new ASN1Integer(s)
    });

    return sequence.getDEREncoded();
}

并使用此签名进行验证。

关于java - ES256 JWT 验证 - SignatureException : invalid encoding for signature: java. io.IOException:序列标记错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59228957/

相关文章:

cryptography - Javacard 在 APDU 中发送 RSA 公钥

java - 地理编码器在 Kitkat 版本以上的 Android 中不起作用

java - 如何从java代码中获取控制台命令的结果?

javascript - Passport.js 验证中间件后没有日志

c# - Silverlight C# 中的 MD5 或其他加密

c# - .NET RSACryptoServiceProvider 使用4096私钥加密,如何在Android上解密

java - 使用 hessian 从 android 发送 double 到 php 时出现解析错误

java - 取消选择 ListView 中的项目

c# - JwtSecurityTokenHandler 线程安全吗

python - 使用模板渲染实现 flask_jwt_extended