indutny / elliptic

Fast Elliptic Curve Cryptography in plain javascript
1.7k stars 381 forks source link

"p256"/"secp256r1" always generates the same signature for same data #221

Closed Goddchen closed 4 years ago

Goddchen commented 4 years ago

Hi have an issue with using this library. I am creating P-256 signatures. I always fail to verify my signature with other tools like openssl or in my Java/Kotlin code. So I ran some tests... When I generate signatures with openssl or Java (BouncyCastle) over the same input data, I always get a different signare r and s. Which is consistent with what I expected since P-256 is non-deterministic.

But when I create multiple signatures over the same data with this library. I always end up with the same r and s.

Any ideas what I might be doing wrong?

    var key = ec.keyFromPrivate("...")
    var message = "..."
    var hash = shajs("sha256").update(message).digest("hex")
    var signature = key.sign(hash)
    var signature2 = key.sign(hash)
    var signature3 = key.sign(hash)

I end up with 3 identical signatures.

indutny commented 4 years ago

See: https://github.com/indutny/elliptic#implementation-details

Goddchen commented 4 years ago

Hi! Thanks for answering so quickly!

Unfortunately I don't get what you want to tell me with the link :/ Do you mean the "ECDSA is using deterministic k value generation as per RFC6979." part? Does it mean it is correct that I get deterministic signatures even with "p256"? If so, then why won't neither openssl, nor Java validate the signature? Both say it is invalid.

indutny commented 4 years ago

Yeah, the signatures are going to be the same for the same input data and private key.

Are you feeding hex string as an input to OpenSSL too? As a blind guess, I think .digest('hex') might be a cuplrit. Could you try replacing it with just .digest()?

In any case, it would help a lot if you'd post both:

Goddchen commented 4 years ago

Hi!

I just checked and it doesn't matter if I supply the hash from .digest('hex') or from .digest(). The generated signature is the same.

I don't have the openssl commands at hand right now but I have the Kotlin code:

        val pemEncodedPublicKey = "..."
        val r = BigInteger("16623944143491971676463174827292691248276097906054754250778903209279348118")
        val s = BigInteger("8915207623119386215103726274096406162374916832888856290823602997291992632846")
        val msg = "{1234 0 0 0 0.00 0.00 0.00 0.00 0.00}"
        val publicKey = KeyFactory.getInstance("ECDSA", BouncyCastleProvider())
            .generatePublic(X509EncodedKeySpec(PemReader(StringReader(pemEncodedPublicKey)).readPemObject().content))
        val signature = Signature.getInstance("SHA256withECDSA", BouncyCastleProvider())
        signature.initVerify(publicKey)
        signature.update(msg.toByteArray())
        val isValid = signature.verify(DERSequence(ASN1EncodableVector().apply {
            add(ASN1Integer(r))
            add(ASN1Integer(s))
        }).getEncoded(ASN1Encoding.DER))
        Assertions.assertThat(isValid).isTrue()

And this is my full code of how I create the signature with elliptic:

    var pemPrivateKey = PrivateKey.fromPEM(pemEncodedPrivateKey)
    var privateKey = ec.keyFromPrivate(pemPrivateKey.keyRaw)
    var msgHash = shajs("sha256").update(createMeterUpdateString(meterUpdate)).digest()
    var signature = privateKey.sign(msgHash)
Goddchen commented 4 years ago

Here's what I do in openssl:

First I export the signature from elliptic with signature.toDER("hex").

Then I convert it to binary with echo "3045022100aa8986096d7e915677892f95be506ff6e0e343da3d9f1694f1d3ab75b21a761602205bb4334cedb549dbce4c094d19a88931f80786a467baefb2e2c0b98edb9213a5" | xxd -r -p > signature.bin.

Then I try to verify it with: openssl dgst -verify pub.pem -hex -signature signature.bin message and I get Verification Failure.

Goddchen commented 4 years ago

Might it be possible that I'm reading the private key wrong? This is my private key variable: var pemEncodedPrivateKey = "-----BEGIN PRIVATE KEY-----\nMIGEAgE...............ZUQAxwUrukh\n-----END PRIVATE KEY-----"

Goddchen commented 4 years ago

I found my issue after a lot of debugging. It was indeed the format of the private key. I am now passing in the raw elliptic curve coordinate formatted as hex and now it is working!