2

I have to verify a certificate. I'm not an expert of cryptography, so probably I did something (or everything :) ) wrong. When the code reach the last step (boolean b = sig.verify(CertSign);), it fires an exception: java.security.SignatureException: error decoding signature bytes. Could someone help me to figure out what I'm doing wrong?

The following is a test code that shows the problem:

    private void test() {
        byte [] CertBody = new byte[]{(byte)0x7F,(byte)0x4E,(byte)0x81,(byte)0x82,
                               (byte)0x5F,(byte)0x29,(byte)0x01,(byte)0x00,
                               (byte)0x42,(byte)0x08,(byte)0xFB,(byte)0x55,
                               (byte)0x54,(byte)0x4F,(byte)0x02,(byte)0xFF,
                               (byte)0xFF,(byte)0x01,(byte)0x5F,(byte)0x4C,
                               (byte)0x07,(byte)0xFF,(byte)0x53,(byte)0x4D,
                               (byte)0x52,(byte)0x44,(byte)0x54,(byte)0x02,
                               (byte)0x7F,(byte)0x49,(byte)0x4E,(byte)0x06,
                               (byte)0x09,(byte)0x2B,(byte)0x24,(byte)0x03,
                               (byte)0x03,(byte)0x02,(byte)0x08,(byte)0x01,
                               (byte)0x01,(byte)0x07,(byte)0x86,(byte)0x41,
                               (byte)0x04,(byte)0x6C,(byte)0x17,(byte)0x5B,
                               (byte)0xB9,(byte)0xEF,(byte)0x5D,(byte)0x02,
                               (byte)0x20,(byte)0x51,(byte)0xCC,(byte)0xB2,
                               (byte)0x89,(byte)0x09,(byte)0x42,(byte)0x15,
                               (byte)0x85,(byte)0xD3,(byte)0x1A,(byte)0xCD,
                               (byte)0xA7,(byte)0x1B,(byte)0x08,(byte)0x4A,
                               (byte)0x48,(byte)0x8F,(byte)0x7B,(byte)0x77,
                               (byte)0x5B,(byte)0x57,(byte)0x9B,(byte)0xB7,
                               (byte)0x41,(byte)0x3B,(byte)0xDA,(byte)0x4C,
                               (byte)0xDE,(byte)0x70,(byte)0x11,(byte)0x17,
                               (byte)0xE0,(byte)0xD9,(byte)0xD1,(byte)0x0F,
                               (byte)0x74,(byte)0xD6,(byte)0x25,(byte)0xB2,
                               (byte)0x86,(byte)0xCA,(byte)0x04,(byte)0x64,
                               (byte)0x54,(byte)0x4F,(byte)0x1B,(byte)0x53,
                               (byte)0xF3,(byte)0x24,(byte)0x02,(byte)0xD3,
                               (byte)0xB3,(byte)0x74,(byte)0xEF,(byte)0xA6,
                               (byte)0xC7,(byte)0x5F,(byte)0x20,(byte)0x08,
                               (byte)0xEA,(byte)0xF4,(byte)0xBE,(byte)0xCC,
                               (byte)0x03,(byte)0x18,(byte)0x02,(byte)0xA3,
                               (byte)0x5F,(byte)0x25,(byte)0x04,(byte)0x5A,
                               (byte)0xAF,(byte)0xA9,(byte)0xDF,(byte)0x5F,
                               (byte)0x24,(byte)0x04,(byte)0x5C,(byte)0x90,
                               (byte)0xDD,(byte)0x5F};

        byte [] CertSign = new byte[]{(byte)0x3D,(byte)0x61,(byte)0x23,
                                      (byte)0xBE,(byte)0x6C,(byte)0x0B,
                                      (byte)0xC1,(byte)0x3E,(byte)0x7A,
                                      (byte)0x2D,(byte)0x60,(byte)0x3D,
                                      (byte)0x28,(byte)0xF0,(byte)0x29,
                                      (byte)0xCC,(byte)0x8C,(byte)0x55,
                                      (byte)0xF1,(byte)0x9A,(byte)0x5C,
                                      (byte)0x7E,(byte)0xBE,(byte)0xFD,
                                      (byte)0x43,(byte)0x27,(byte)0x1A,
                                      (byte)0x9D,(byte)0xA1,(byte)0x7C,
                                      (byte)0x81,(byte)0x09,(byte)0x5C,
                                      (byte)0x1D,(byte)0x26,(byte)0x27,
                                      (byte)0x76,(byte)0x73,(byte)0x74,
                                      (byte)0x87,(byte)0xF9,(byte)0x6C,
                                      (byte)0x2A,(byte)0xC9,(byte)0xA3,
                                      (byte)0x32,(byte)0x3C,(byte)0x60,
                                      (byte)0x51,(byte)0x71,(byte)0x6D,
                                      (byte)0x2F,(byte)0xC0,(byte)0xF6,
                                      (byte)0x89,(byte)0x14,(byte)0x93,
                                      (byte)0xB2,(byte)0xB6,(byte)0x87,
                                      (byte)0x21,(byte)0xAF,(byte)0x01,
                                      (byte)0x42};


        String Algorithm = "brainpoolP256r1";

        byte [] PublicPoint = new byte[]{(byte)0x04,(byte)0x68,(byte)0xF8,
                                         (byte)0xD6,(byte)0xB3,(byte)0x94,
                                         (byte)0xD2,(byte)0x8E,(byte)0x2B,
                                         (byte)0x4D,(byte)0xFA,(byte)0x36,
                                         (byte)0x52,(byte)0xAE,(byte)0xAC,
                                         (byte)0xDA,(byte)0xD3,(byte)0x19,
                                         (byte)0x21,(byte)0xC2,(byte)0x12,
                                         (byte)0x8D,(byte)0x58,(byte)0x9F,
                                         (byte)0x4E,(byte)0xF1,(byte)0xB1,
                                         (byte)0xC4,(byte)0x10,(byte)0x97,
                                         (byte)0x29,(byte)0xDC,(byte)0xA8,
                                         (byte)0xA1,(byte)0xC9,(byte)0x53,
                                         (byte)0x04,(byte)0xC8,(byte)0x77,
                                         (byte)0x4F,(byte)0x57,(byte)0xFA,
                                         (byte)0xFB,(byte)0x15,(byte)0x59,
                                         (byte)0xB9,(byte)0x2F,(byte)0x72,
                                         (byte)0x6A,(byte)0xE4,(byte)0x22,
                                         (byte)0x64,(byte)0x3E,(byte)0x1B,
                                         (byte)0x11,(byte)0x8D,(byte)0x69,
                                         (byte)0xAC,(byte)0x36,(byte)0x25,
                                         (byte)0xAD,(byte)0x1C,(byte)0x30,
                                         (byte)0x04,(byte)0xA9};

        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
           Security.addProvider(new BouncyCastleProvider());
        }

        ECNamedCurveParameterSpec params1 = ECNamedCurveTable.getParameterSpec(Algorithm);
        try {
            KeyFactory fact = KeyFactory.getInstance("ECDSA", "BC");
            ECCurve curve = params1.getCurve();
            java.security.spec.EllipticCurve ellipticCurve = EC5Util.convertCurve(curve, params1.getSeed());
            java.security.spec.ECPoint point=ECPointUtil.decodePoint(ellipticCurve, PublicPoint);
            java.security.spec.ECParameterSpec params2=EC5Util.convertSpec(ellipticCurve, params1);
            java.security.spec.ECPublicKeySpec keySpec = new java.security.spec.ECPublicKeySpec(point,params2);
            ECPublicKey PK = (ECPublicKey)fact.generatePublic(keySpec);
            Signature sig;
            sig = Signature.getInstance("ECDSA", "BC");
            sig.initVerify(PK);
            sig.update(CertBody);
            boolean b = sig.verify(CertSign);
            if (b)
            {
                Logger.getLogger(CardManager.class.getName()).log(Level.INFO, "verificato");
            }
        } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeySpecException | InvalidKeyException | SignatureException ex) {
            Logger.getLogger("test").log(Level.SEVERE, null, ex);
        }
    } 

I Expected to have the b equals true (or false) but instead I get the following exception:

_java.security.SignatureException: error decoding signature bytes.
    at org.bouncycastle.jcajce.provider.asymmetric.util.DSABase.engineVerify(DSABase.java:80)
    at java.security.Signature$Delegate.engineVerify(Signature.java:1223)
    at java.security.Signature.verify(Signature.java:656)_
Rudy Barbieri
  • 389
  • 1
  • 4
  • 16
  • Where did you get the bye array data from? And the algorithm names? – user207421 Apr 18 '19 at 09:56
  • The body part contains Algoritmh name and public point. It's encoded using DER encoding. The signature is not in the body, and it's the bytes of the content part of the DER encoded signature – Rudy Barbieri Apr 18 '19 at 10:16
  • This [question](https://stackoverflow.com/q/30175149/3080094) could be related, from what I can understand "CertSign" should be 70 bytes? – vanOekel Apr 19 '19 at 12:25
  • I saw that question and I'm currently tring to test it. Unfortunately I'm stuck with another problem: when I set KeyFactory fact = KeyFactory.getInstance("SHA256withECDSA", "BC"); I got *no such algorithm: SHA256withECDSA for provider BC*. Extending the signature with the DER encoded header, keeping ECDSA as algorithm doesn't work – Rudy Barbieri Apr 19 '19 at 13:22

1 Answers1

6

Reason for the Exception

As already mentioned in the comments to the question, the length of the signature differs from the expected length.

The exception happens, since a DER encoded signature with 70 bytes is expected, but your CertSign contains only 64 bytes.

Assumptions

Since it is 64 bytes, one could assume that it is an ECDSA signature consisting of two concatenated BigInteger numbers with 32 bytes each.

Conversion of ECDSA signature to DER encoded signature

To convert that to a DER encoded signature one can use sth like:

private byte[] DEREncodeSignature(byte [] signature) throws IOException {
    BigInteger r = new BigInteger(1, Arrays.copyOfRange(signature, 0, 32));
    BigInteger s = new BigInteger(1, Arrays.copyOfRange(signature, 32, 64));
    ASN1EncodableVector v = new ASN1EncodableVector();
    v.add(new ASN1Integer(r));
    v.add(new ASN1Integer(s));
    return new DERSequence(v).getEncoded(ASN1Encoding.DER);
}

Then the lines in your code where you use the signature needs to be adjusted, e.g. to sth like this:

    boolean b = sig.verify(DEREncodeSignature(CertSign));
    if (b) {
        Logger.getLogger(CardManager.class.getName()).log(Level.INFO, "verificato");
    }
    else {
        Logger.getLogger(CardManager.class.getName()).log(Level.INFO, "controllo fallito");
    }

Quick Test

The exception is then gone, the output in the debug console looks like this:

May 11, 2019 5:09:48 PM CardManager test
INFO: controllo fallito

So the verification actually fails, but you mention that you expect b to be either true or false, so maybe it's the right result. But without having more data this is not possible to validate.

Further Tests

I also quickly tried different signature algorithms with your code. For each of them one gets a false.

String[] signatureAlgorithms = new String[] {"RIPEMD160withECDSA", "SHA224withECDDSA", "SHA256withECDDSA", "SHA384withECDDSA", "SHA512withECDDSA", "SHA1withECDSA", "NONEwithECDSA", "SHA224withECDSA", "SHA256withECDSA", "SHA384withECDSA", "SHA512withECDSA", "SHA3-224withECDSA", "SHA3-256withECDSA", "SHA3-384withECDSA", "SHA3-512withECDSA"};
for(String algorithm : signatureAlgorithms) {
    Signature sig;
    Logger.getLogger(CardManager.class.getName()).log(Level.INFO, "trying aloorithm " + algorithm + ": ");
    sig = Signature.getInstance(algorithm, "BC");
    ...

Still - without further data (e.g. used signature algorithm etc.) it is difficult to say what the reason for the mismatch is.

Use Different Test Data

To verify that the routines including the new DEREncodeSignature is working correctly I signed the message 'Hello world!' with ECDSA generator using the brainpoolP256r1 parameter spec and SHA256withECDDSA signature algorithm, then the data looks like this:

byte[] CertBody = new byte[]{(byte) 0x48,
        (byte) 0x65, (byte) 0x6C, (byte) 0x6C,
        (byte) 0x6F, (byte) 0x20, (byte) 0x77,
        (byte) 0x6F, (byte) 0x72, (byte) 0x6C,
        (byte) 0x64, (byte) 0x21};

byte[] CertSign = new byte[]{
        (byte)0x0C,(byte)0x5B,(byte)0xE9,
        (byte)0xA4,(byte)0xF7,(byte)0xC9,
        (byte)0x5A,(byte)0x69,(byte)0x8E,
        (byte)0x91,(byte)0x50,(byte)0xB4,
        (byte)0x4E,(byte)0x33,(byte)0x14,
        (byte)0x7C,(byte)0x2F,(byte)0x15,
        (byte)0x93,(byte)0x97,(byte)0x8B,
        (byte)0xD8,(byte)0x65,(byte)0x20,
        (byte)0x1B,(byte)0x1D,(byte)0xF4,
        (byte)0x8A,(byte)0xB3,(byte)0x81,
        (byte)0x55,(byte)0x6A,

        (byte)0x96,
        (byte)0xE4,(byte)0x89,(byte)0x05,
        (byte)0x80,(byte)0xAA,(byte)0x34,
        (byte)0x3A,(byte)0x86,(byte)0x11,
        (byte)0x5F,(byte)0x0B,(byte)0x8F,
        (byte)0xF7,(byte)0xE7,(byte)0xA6,
        (byte)0x7D,(byte)0xCC,(byte)0x9C,
        (byte)0xD0,(byte)0xC8,(byte)0x94,
        (byte)0xCA,(byte)0x78,(byte)0x9C,
        (byte)0xFE,(byte)0x41,(byte)0x2A,
        (byte)0xB0,(byte)0xE1,(byte)0x7E,
        (byte)0x29,
};

byte[] PublicPoint = new byte[]{
        (byte) 0x04,
        (byte) 0x1E, (byte) 0xC8, (byte) 0xB7,
        (byte) 0x00, (byte) 0xF1, (byte) 0xFD,
        (byte) 0x06, (byte) 0x97, (byte) 0x73,
        (byte) 0x71, (byte) 0x09, (byte) 0x12,
        (byte) 0xF2, (byte) 0xB7, (byte) 0xEF,
        (byte) 0xA5, (byte) 0x23, (byte) 0xA1,
        (byte) 0xC1, (byte) 0x6C, (byte) 0xA7,
        (byte) 0xD1, (byte) 0x0C, (byte) 0x25,
        (byte) 0x6E, (byte) 0x04, (byte) 0x09,
        (byte) 0x7A, (byte) 0x62, (byte) 0xC3,
        (byte) 0x0E, (byte) 0x93, (byte) 0x54,
        (byte) 0x7F, (byte) 0x0C, (byte) 0xE4,
        (byte) 0x0F, (byte) 0xF8, (byte) 0x63,
        (byte) 0x82, (byte) 0x6D, (byte) 0x0B,
        (byte) 0x50, (byte) 0xC0, (byte) 0x59,
        (byte) 0x1F, (byte) 0xFC, (byte) 0x36,
        (byte) 0x1B, (byte) 0x0E, (byte) 0x2A,
        (byte) 0xA3, (byte) 0xD4, (byte) 0x29,
        (byte) 0x4E, (byte) 0x30, (byte) 0x91,
        (byte) 0x44, (byte) 0x28, (byte) 0x2E,
        (byte) 0x15, (byte) 0x76, (byte) 0x48,
        (byte) 0xE7
};

If that data is taken and run through the routines incl. DEREncodeSignature then the following output is shown in the debug console:

May 12, 2019 12:13:42 AM CardManager test
INFO: verificato

(using sig = Signature.getInstance("SHA256withECDDSA", "BC"); of course)

This means that for ECDSA generated data with brainpoolP256r1 parameter spec and SHA256withECDDSA it would work with the alternative test data.

Stephan Schlecht
  • 26,556
  • 1
  • 33
  • 47
  • Yes, you're right. I found the problem last friday (I simplty forgot to write here the solution). The signature I had, comes from a DER encoded, but was not a sequence of two integer, instead it was an other data type. So the correct procedure is to get the content part from the DER encoded certificate, then reencode it as a sequence of two integer. You deserve the reward. – Rudy Barbieri May 12 '19 at 16:35