Petaluma007
Petaluma007

Reputation: 41

BouncyCastle ECDSA Signature Verification Failed Using prime256v1 and SHA256withECDSA Algorithm

I need to verify an ECDSA signature with Java using BouncyCastle crypto provider. So far BouncyCastle failed to verify the signature.

The signature is created in Atmel AT88CK590 Crypto Authentication module and Public key can be obtained out from the module. Here is the Public Key, in C/C++ format, of 64 octets long:

uint8_t pubKey[] = {
    // X coordinate of the elliptic curve.
    0xc1, 0x71, 0xCB, 0xED, 0x65, 0x71, 0x82, 0x2E, 0x8F, 0x8A, 0x43, 0x8D, 0x72, 0x56, 0xD1, 0xC8,
    0x86, 0x3C, 0xD0, 0xBC, 0x7F, 0xCC, 0xE3, 0x6D, 0xE7, 0xB7, 0x17, 0xED, 0x29, 0xC8, 0x38, 0xCB,

    // Y coordinate of the elliptic curve.
    0x80, 0xCD, 0xBE, 0x0F, 0x1D, 0x5C, 0xC5, 0x46, 0x99, 0x24, 0x8F, 0x6E, 0x0A, 0xEA, 0x1F, 0x7A,
    0x43, 0xBA, 0x2B, 0x03, 0x80, 0x90, 0xE9, 0x25, 0xB2, 0xD0, 0xE6, 0x48, 0x93, 0x91, 0x64, 0x83
};

The original message, the signature and Public key in Base64 encoding:

// Raw message to sign
private static final String tokenStr = "12345678901234567890123456789012";
// Base64 encoded
private static final String pubKeyStr = "wXHL7WVxgi6PikONclbRyIY80Lx/zONt57cX7SnIOMuAzb4PHVzFRpkkj24K6h96
Q7orA4CQ6SWy0OZIk5Fkgw==";
// Base64 encoded
private static final String signatureStr = "XF2WossFTA82ndYFGEH0FPqAldkFQLGd/Bv/Qh8UYip7sXUvCUnFgi1YXjN3WxLn
IwSo3OaHLCOzGAtIis0b3A==";

To convert the Public key I am using the following:

private static PublicKey getPublicKeyFromBytes(byte[] pubKey, String ecSpec, String provider)
        throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
    ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec(ecSpec);
    KeyFactory kf = KeyFactory.getInstance(ECDSA_CRYPTO, provider);
    ECNamedCurveSpec params = new ECNamedCurveSpec(ecSpec, spec.getCurve(), spec.getG(), spec.getN());
    ECPoint pubPoint =  ECPointUtil.decodePoint(params.getCurve(), pubKey);
    ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(pubPoint, params);
    PublicKey publicKey = kf.generatePublic(pubKeySpec);

    return publicKey;
}

To convert the signature to DER format I am using the following:

private static byte[] toDERSignature(byte[] tokenSignature) throws IOException {
    byte[] r = Arrays.copyOfRange(tokenSignature, 0, tokenSignature.length / 2);
    byte[] s = Arrays.copyOfRange(tokenSignature, tokenSignature.length / 2, tokenSignature.length);
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    DEROutputStream derOutputStream = new DEROutputStream(byteArrayOutputStream);
    ASN1EncodableVector v = new ASN1EncodableVector();

    v.add(new ASN1Integer(new BigInteger(1, r)));
    v.add(new ASN1Integer(new BigInteger(1, s)));
    derOutputStream.writeObject(new DERSequence(v));

    byte[] derSignature = byteArrayOutputStream.toByteArray();

    return derSignature;
}

Here is the code to verify the signature:

Security.addProvider(new BouncyCastleProvider());

byte[] tokenBytes = tokenStr.getBytes("UTF-8");
String urlDecodePubKeyStr = pubKeyStr.replace(newlineHtml, "");
byte[] pubKeyBytes = DatatypeConverter.parseBase64Binary(urlDecodePubKeyStr);
String urlDecodeSignatureStr = signatureStr.replace(newlineHtml, "");
byte[] signBytes = DatatypeConverter.parseBase64Binary(urlDecodeSignatureStr);
byte[] derSignature = toDERSignature(signBytes);
ByteBuffer bb = ByteBuffer.allocate(pubKeyBytes.length + 1);

bb.put((byte)4);
bb.put(pubKeyBytes);

PublicKey ecPublicKey = getPublicKeyFromBytes(bb.array(), "prime256v1", "BC");

System.out.println("\nSignature: " + Hex.toHexString(signBytes).toUpperCase());
System.out.println("DER Signature: " + Hex.toHexString(derSignature).toUpperCase());
System.out.println(ecPublicKey.toString());

Signature signature = Signature.getInstance("SHA256withECDSA", "BC");

signature.initVerify(ecPublicKey);
signature.update(tokenBytes);

boolean result = signature.verify(derSignature);

System.out.println("BC Signature Valid: " + result);

The output:

Signature: 5C5D96A2CB054C0F369DD6051841F414FA8095D90540B19DFC1BFF421F14622A7BB1752F0949C5822D585E33775B12E72304A8DCE6872C23B3180B488ACD1BDC
DER Signature: 304402205C5D96A2CB054C0F369DD6051841F414FA8095D90540B19DFC1BFF421F14622A02207BB1752F0949C5822D585E33775B12E72304A8DCE6872C23B3180B488ACD1BDC
EC Public Key
           X: c171cbed6571822e8f8a438d7256d1c8863cd0bc7fcce36de7b717ed29c838cb
           Y: 80cdbe0f1d5cc54699248f6e0aea1f7a43ba2b038090e925b2d0e64893916483

BC Signature Valid: false

Have anyone run into the same problem before? What did I miss here?

Upvotes: 3

Views: 3585

Answers (1)

dave_thompson_085
dave_thompson_085

Reputation: 38771

That signature value is not computed on the hash as is standard and usual, but directly on the data. Either:

  • the Atmel doesn't hash so your could must hash first and supply the hash otuput to be signed

  • the Atmel should hash but you didn't set some necessary option or something

  • you actually want an unconventional 'raw' signature with no hash. Note this limits the data to 32 or maybe 31 bytes. And if the values to be signed can be affected by an adversary and the random generator used for signing is weak, I think that gives an attack that recovers your privatekey (although I haven't worked through it). Even with the if's that's a risk I would avoid.

At any rate, the value you have can be verified using scheme NoneWithECDSA.

In addition, in testing this I found some possible improvements in your code that you may or may not want. First, you don't need to quasi-encode and then decode the point, given you have X,Y in fixed format you can just use them. Second, you do need to DER-encode the signature, but it can be done more simply. Here's my version, with both changes, in a single linear block for clarity:

public static void main (String[] args) throws Exception {
    Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
    // test data; real data would come from outside
    byte[] dataBytes = "12345678901234567890123456789012".getBytes("UTF-8");
    String sigString = "XF2WossFTA82ndYFGEH0FPqAldkFQLGd/Bv/Qh8UYip7sXUvCUnFgi1YXjN3WxLn
IwSo3OaHLCOzGAtIis0b3A==";
    String pubkeyString = "wXHL7WVxgi6PikONclbRyIY80Lx/zONt57cX7SnIOMuAzb4PHVzFRpkkj24K6h96
Q7orA4CQ6SWy0OZIk5Fkgw==";
    String ecSpec="prime256v1"; int size=32; // bytes for x,y in pubkey also r,s in sig

    byte[] pubkeyBytes = DatatypeConverter.parseBase64Binary(pubkeyString.replaceAll("
","") );
    KeyFactory kf = KeyFactory.getInstance ("ECDSA", "BC");
    ECNamedCurveParameterSpec cspec = ECNamedCurveTable.getParameterSpec(ecSpec);
    BigInteger x = new BigInteger(1, Arrays.copyOfRange(pubkeyBytes,0,size));
    BigInteger y = new BigInteger(1, Arrays.copyOfRange(pubkeyBytes,size,size*2));
    ECPublicKeySpec kspec = new ECPublicKeySpec (cspec.getCurve().createPoint(x, y), cspec);
    PublicKey k = kf.generatePublic(kspec);

    byte[] sigBytes = DatatypeConverter.parseBase64Binary(sigString.replaceAll("
","") );
    ASN1EncodableVector v = new ASN1EncodableVector();
    v.add(/*r*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(sigBytes,0,size))));
    v.add(/*s*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(sigBytes,size,size*2))));
    byte[] sigDer = new DERSequence(v).getEncoded();

    Signature sig = Signature.getInstance("NoneWithECDSA", "BC"); // NOTE None instead of a hash
    sig.initVerify (k); sig.update (dataBytes); 
    System.out.println ("verify="+sig.verify(sigDer));
}

PS: you comment the publickey halves as X [Y] coordinate of the elliptic curve. They are coordinates of the point on the curve which is the publickey; the curve itself doesn't have coordinates.

Upvotes: 2

Related Questions