I have tested a solution to verify an ECDSA signature (How can I get a PublicKey object from EC public key bytes?) that works perfect with the given data.
This is the data:
byte[] pubKey = DatatypeConverter.parseHexBinary("049a55ad1e210cd113457ccd3465b930c9e7ade5e760ef64b63142dad43a308ed08e2d85632e8ff0322d3c7fda14409eafdc4c5b8ee0882fe885c92e3789c36a7a");
byte[] message = DatatypeConverter.parseHexBinary("54686973206973206a75737420736f6d6520706f696e746c6573732064756d6d7920737472696e672e205468616e6b7320616e7977617920666f722074616b696e67207468652074696d6520746f206465636f6465206974203b2d29");
byte[] signature = DatatypeConverter.parseHexBinary("304402205fef461a4714a18a5ca6dce6d5ab8604f09f3899313a28ab430eb9860f8be9d602203c8d36446be85383af3f2e8630f40c4172543322b5e8973e03fff2309755e654");
And this is the code (which prints true):
private static boolean isValidSignature(byte[] pubKey, byte[] message,byte[] signature) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException, InvalidKeySpecException {
Signature ecdsaVerify = Signature.getInstance("SHA256withECDSA", new BouncyCastleProvider());
ecdsaVerify.initVerify(getPublicKeyFromBytes(pubKey));
ecdsaVerify.update(message);
return ecdsaVerify.verify(signature);
}
private static PublicKey getPublicKeyFromBytes(byte[] pubKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("prime256v1");
KeyFactory kf = KeyFactory.getInstance("ECDSA", new BouncyCastleProvider());
ECNamedCurveSpec params = new ECNamedCurveSpec("prime256v1", spec.getCurve(), spec.getG(), spec.getN());
ECPoint point = ECPointUtil.decodePoint(params.getCurve(), pubKey);
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, params);
ECPublicKey pk = (ECPublicKey) kf.generatePublic(pubKeySpec);
return pk;
}
public static void main (String[] args) {
System.out.println(isValidSignature(pubKey, message, signature));
}
My problem comes when I change the signature and data to an example input from an already implemented system:
final static byte[] pubKey = DatatypeConverter.parseHexBinary("0447303876C6FED5550DF3EE1136989FCD87293D54A5D8E2F2F6D7FBE9A81089B889A5917443AF33E696178CEF4C9D6A4288B2745B29AF6C8BCAD1348F78EB9F9B");
final static byte[] message = DatatypeConverter.parseHexBinary("02158001f53611a06e2d1a270000013ed9305dc2780524015110500000002d0100140092569202017aa00c5dd30000000000000000000000000000000007d1000001020001b20788b80059f48d95cdefc8c6000200200030d41e0000012016840310a50733a9870fffd0430100");
final static byte[] signature = DatatypeConverter.parseHexBinary("531F8918FF250132959B01F7F56FDFD9E6CA3EC2144E12A6DA37C281489A3D96");
New data outputs this error:
java.security.SignatureException: error decoding signature bytes.
at org.bouncycastle.jcajce.provider.asymmetric.util.DSABase.engineVerify(Unknown Source)
at java.security.Signature$Delegate.engineVerify(Signature.java:1178)
at java.security.Signature.verify(Signature.java:612)
at its.sec.exec.TestProgram.isValidSignature(TestProgram.java:168)
at its.sec.exec.TestProgram.execution(TestProgram.java:101)
at its.sec.exec.TestProgram.main(TestProgram.java:55)
I assume the problem is about the signature that comes with the secured message because:
Last thing worth mention is that my documentation says that the signature must be preceded by a field called "R" which "contains the x coordinate of the elliptic curve point resulting from multiplying the generator element by the ephemeral private key" and its length must be the same as the signature (32 byte).
Can someone point me out what I'm missing here?
EDIT: Solution
As Peter Dettman pointed in his answer, the signature
was not correctly formatted (also content was incorrect too) in order to be computed by the verify()
method. Here is a good explanation that mainly says that:
When encoded in DER, this (signature) 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.
Applying that change, signature
grows to 70 bytes and the execution outputs no error.
The expected ECDSA signature format that the BC (and other provider) implementations work with is a DER-encoded ASN.1 sequence containing two integer values r
and s
. This signature format has been specified in ANSI X9.62. This is the format in the first set of data you give (note that signature
is a total of 70 bytes).
In the second set of data, signature
is only 32 bytes, and is not an ASN.1 sequence at all. I would guess that this value is only the s
value, and it is missing the r
value and the ASN.1 INTEGER encoding for them both, instead encoding the values as a unsigned big integer value with the same size as the key.
this is a sample code to write r and s in ASN1 DER encoded format
// construct the ASN1Sequence with r and s
ByteArrayOutputStream outs = new ByteArrayOutputStream();
byte radd = (byte)(((signed[0] & 0x80) > 0) ? 1 : 0);
byte sadd = (byte)(((signed[32] & 0x80) > 0) ? 1 : 0);
byte length = (byte)(0x44 + radd + sadd);
outs.write(0x30);
outs.write(length); // length 68 bytes +
outs.write(0x02); // ASN1Integer
outs.write(0x20 + radd); // length 32 bytes
if(radd > 0)
outs.write(0x00); // positive val
outs.write(signed, 0, 32);
outs.write(0x02); // ASN1Integer
outs.write(0x20 + sadd); // length 32 bytes
if(sadd > 0)
outs.write(0x00); // positive val
outs.write(signed, 32, 32);
signed = outs.toByteArray();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With