Take the 2-minute tour ×
Cryptography Stack Exchange is a question and answer site for software developers, mathematicians and others interested in cryptography. It's 100% free, no registration required.

I having trouble verifying an ECDSA signature signed using client side javascript with Java/BouncyCastle.

The javascript signing function source:

sign: function (hash, priv) {
        var d = priv;
        var n = ecparams.getN();
        var e = BigInteger.fromByteArrayUnsigned(hash);

        do {
            var k = ECDSA.getBigRandom(n);
            var G = ecparams.getG();
            var Q = G.multiply(k);
            var r = Q.getX().toBigInteger().mod(n);
        } while (r.compareTo(BigInteger.ZERO) <= 0);

        var s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n);

  return ECDSA.serializeSig(r, s);
},

serializeSig: function (r, s) {
        var rBa = r.toByteArrayUnsigned();
        var sBa = s.toByteArrayUnsigned();

        var sequence = [];
        sequence.push(0x02); // INTEGER
        sequence.push(rBa.length);
        sequence = sequence.concat(rBa);

        sequence.push(0x02); // INTEGER
        sequence.push(sBa.length);
        sequence = sequence.concat(sBa);

        sequence.unshift(sequence.length);
        sequence.unshift(0x30) // SEQUENCE

        return sequence;
},

The server side verify function source:

  /**
     * Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key.
     *
     * @param data      Hash of the data to verify.
     * @param signature ASN.1 encoded signature.
     * @param pub       The public key bytes to use.
     */
    public static boolean verify(byte[] data, byte[] signature, byte[] pub) {
        ECDSASigner signer = new ECDSASigner();
        ECPublicKeyParameters params = new ECPublicKeyParameters(ecParams.getCurve().decodePoint(pub), ecParams);
        signer.init(false, params);
        try {
            ASN1InputStream decoder = new ASN1InputStream(signature);
            DERSequence seq = (DERSequence) decoder.readObject();
            DERInteger r = (DERInteger) seq.getObjectAt(0);
            DERInteger s = (DERInteger) seq.getObjectAt(1);
            decoder.close();
            return signer.verifySignature(data, r.getValue(), s.getValue());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

Does anyone know what format the javascript signature is in (DER?) and if so how to convert it to ASN.1?

I tried:

public static byte[] toASN1(byte[] data) {
            try {
                ByteArrayOutputStream baos = new ByteArrayOutputStream(400);
                ASN1OutputStream encoder = new ASN1OutputStream(baos);

                encoder.write(data);
                encoder.close();
                return baos.toByteArray();
            } catch (IOException e) {
                throw new RuntimeException(e);  // Cannot happen, writing to memory stream.
            }
}

but validation still fails.

share|improve this question
4  
This question appears to be off-topic because it ask about using/implementing a crypto-lib (here: BouncyCastle) in an application. Like the help of Crypto.SE states: “If your question is about using a cryptographic library (e.g. Crypto++, OpenSSL etc) and using it in your application, you probably need Stack Overflow. (A good indicator is the existence of source code in your question or most good answers).” –  e-sushi Mar 4 at 23:28
add comment

1 Answer 1

up vote 8 down vote accepted

Disclaimer: I don't know Javascript and I do not practice BouncyCastle. However, I do know Java, and ASN.1.

ASN.1 is a notation for structured data, and DER is a set of rules for transforming a data structure (described in ASN.1) into a sequence of bytes, and back.

This is ASN.1, namely the description of the structure which an ECDSA signature exhibits:

ECDSASignature ::= SEQUENCE {
    r   INTEGER,
    s   INTEGER
}

When encoded in DER, this becomes the following sequence of bytes:

0x30 b1 0x02 b2 (vr) 0x02 b3 (vs)

where:

  • b1 is a single byte value, equal to the length, in bytes, of the remaining list of bytes (from the first 0x02 to the end of the encoding);
  • b2 is a single byte value, equal to the length, in bytes, of (vr);
  • b3 is a single byte value, equal to the length, in bytes, of (vs);
  • (vr) is the signed big-endian encoding of the value "$r$", of minimal length;
  • (vs) is the signed big-endian encoding of the value "$s$", of minimal length.

"Signed big-endian encoding of minimal length" means that the numerical value must be encoded as a sequence of bytes, such that the least significant byte comes last (that's what "big endian" means), the total length is the shortest possible to represent the value (that's "minimal length"), and the first bit of the first byte specifies the sign of the value (that's "signed"). For ECDSA, the $r$ and $s$ values are positive integers, so the first bit of the first byte must be a 0; i.e. the first byte of (vr) (respectively (vs)) must have a value between 0x00 and 0x7F.

For instance, if we were to encode the numerical value 117, it would use a single byte 0x75, and not a two-byte sequence 0x00 0x75 (because of minimality). However, the value 193 must be encoded as two bytes 0x00 0xC1, because a single byte 0xC1, by itself, would denote a negative integer, since the first (aka "leftmost") bit of 0xC1 is a 1 (a single byte of value 0xC1 represents the value -63).

I insist on these details because my guess is that it is what the Javascript code does incorrectly. The Javascript code invokes a method called toByteArrayUnsigned; that name is evocative of conversion to an unsigned representation (i.e. always positive, even if the first bit is a 1), and that's wrong for DER. I invite you to save the raw signature in a file and decode it "by hand" to see if it matches the fine details of ASN.1 encoding, as explained above (the openssl asn1parse command may help, too).

(If my guess is right, you should have a probability of about 1/4 of getting a correct signature nonetheless -- when the values $r$ and $s$ happen to be short enough to have a high bit set to 0 when using unsigned encoding, i.e. a situation where the bug is benign.)

share|improve this answer
    
Thank you for excellent answer. You were right on the money about validation passing every 1 in 4 times. I tried changing toByteArrayUnsigned to toByteArray but kept getting a DER decoding error. However if I pass the r & s values directly without using serialiseSig it validates correctly. –  Ben Reeves Feb 2 '12 at 9:34
    
Note that for Java, you can simply perform new BigInteger(1, byte[] r).toByteArray() as the default Java encoding of a BigInteger is identical to the ASN.1 encoding of INTEGER. –  owlstead Mar 31 at 15:40
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.