java.security.SignatureException: 签名编码无效。 Azure 验证的签名

java.security.SignatureException: Invalid encoding for signature. signature validated by Azure

使用 Signature.verify 验证签名时,我收到 "Invalid encoding for signature" 异常。 使用 Azure 服务验证相同签名时,签名被验证。

我有一个散列数据 (SHA-256)、一个 public 密钥和一个我要验证的签名。 使用 com.microsoft.azure.keyvault.KeyVaultClient.sign 方法接收签名,签名算法 "ES256".

这有效(使用 ES256 算法):

    com.microsoft.azure.keyvault.KeyVaultClient keyVaultClient;
    String keyPairIdentifier;

    boolean verify(byte[] hashData, byte[] signature, JsonWebKeySignatureAlgorithm signingAlgorithm) {
        com.microsoft.azure.keyvault.models.KeyVerifyResult result = keyVaultClient.verify(keyPairIdentifier, signingAlgorithm, hashData, signature);
        return result.value().booleanValue();
    }

失败(证书持有存储在 Azure keyvault 中的相同 public 密钥):

    Signature ecdsaSign = Signature.getInstance("SHA256withECDSA");
    ecdsaSign.initVerify(certificate.getPublicKey());
    ecdsaSign.update(hashData);
    ecdsaSign.verify(signature)

预期结果 - true(签名已验证)

实际结果:

java.security.SignatureException: Could not verify signature
    at sun.security.ec.ECDSASignature.engineVerify(ECDSASignature.java:325)
    at java.security.Signature$Delegate.engineVerify(Signature.java:1222)
    at java.security.Signature.verify(Signature.java:655)
    at TestKV.KeyVault.VerifyDPSignature.verifySignatureUsingCertificate(VerifyDPSignature.java:143)
    at TestKV.KeyVault.VerifyDPSignature.main(VerifyDPSignature.java:104)
Caused by: java.security.SignatureException: Invalid encoding for signature
    at sun.security.ec.ECDSASignature.decodeSignature(ECDSASignature.java:400)
    at sun.security.ec.ECDSASignature.engineVerify(ECDSASignature.java:322)
    ... 4 more 
Caused by: java.io.IOException: Sequence tag error
    at sun.security.util.DerInputStream.getSequence(DerInputStream.java:330)
    at sun.security.ec.ECDSASignature.decodeSignature (ECDSASignature.java:376)

Azure 支持 JWS which simply concatenates fixed-size I2OSP of r and s but Java JCE, like most but not all standards, uses ASN.1 DER encoding e.g. rfc3279(注意:现在有用于 ECDSA 和其他哈希值的 OID)。

要将 JWS/plain 转换为 DER,请参阅我的(交叉)https://security.stackexchange.com/questions/174095/convert-ecdsa-signature-from-plain-to-der-format 了解 C 方法,但 Java 使其更容易,因为 BigInteger 完成了一半的工作你:

// byte[64] plain contains the JWS-style r,s (de-base64-ed if necessary)
byte[] r = new BigInteger(1,Arrays.copyOfRange(plain,0,32)).toByteArray();
byte[] s = new BigInteger(1,Arrays.copyOfRange(plain,32,64)).toByteArray();
byte[] der = new byte[6+r.length+s.length]; der[0] = 0x30; der[1] = der.length-2; int o = 2;
der[o++] = 2; der[o++] = (byte)r.length; System.arraycopy (r,0, der,o, r.length); o+=r.length;
der[o++] = 2; der[o++] = (byte)s.length; System.arraycopy (s,0, der,o, s.length); //o+=s.length;

2020-05 更正并添加: 也 Java 9 up 直接使用像 SHA256withECDSAinP1363format 这样的算法名称来处理这个问题,并且在 [= 的所有版本上27=] 如果您添加 BouncyCastle,它会使用 SHA256withPLAIN-ECDSASHA256withCVC-ECDSA 等名称。参见 and

dave_thompson_085 - 谢谢! 你附上的代码有一些错误,签名部分的标签应该是0x02,而不是0x30,而且你复制第一部分后没有增加o。 这是更改后的代码:

    byte[] r = new BigInteger(1,Arrays.copyOfRange(signature,0,32)).toByteArray();
    byte[] s = new BigInteger(1,Arrays.copyOfRange(signature,32,64)).toByteArray();
    byte[] der = new byte[6+r.length+s.length];
    der[0] = 0x30; // Tag of signature object
    der[1] = (byte)(der.length-2); // Length of signature object
    int o = 2;
    der[o++] = 0x02; // Tag of ASN1 Integer
    der[o++] = (byte)r.length; // Length of first signature part
    System.arraycopy (r,0, der,o, r.length);
    o += r.length;
    der[o++] = 0x02; // Tag of ASN1 Integer
    der[o++] = (byte)s.length; // Length of second signature part
    System.arraycopy (s,0, der,o, s.length);

格式更改后我没有得到 "Sequence tag error" 异常。但是还是验证失败。

谢谢!