Search code examples
c#cryptographydigital-signaturebouncycastleecdsa

Bouncy Castle SHA-384withECDSA Signature Verification Giving an Exception


I am working on signature verification using BouncyCastle using C# and .NET 4.7.

I was following an answer here on SO that explained the verification process using SHA-256withECDSA here.

As per that my code for verifying signature for that example using both approaches works well as per below code fragment.

public static void Main(string[] args)
{
    //SHA256 With ECDSA - Baseline that works. Values received from BC answer on SO
    String sigSHA256
        = "e1f5cecccedfe5228d9331098e84b69a0675cdd9ac066ecfada7fea761f52a4cde902a0abd362883127230326fb556af14e894d39a3e14437aaa4134a3476c84";
    String msgSHA256 = "00000000dcb320137ddd6f825660750ab655219fad66951c64f0420be8ac902975197ed2b0da54cd3d502d34dd04c8d74b2958a0b8792ae4730df6d25a6969bcad9f93a7d6229e5a0100000017cf5242732bba21a0b0e7dad7102cf7bdb2c8d7a665045816a886d7";
    String pubSHA256 = "b679e27513e2fff8fdeb54409c242776f3517f370440d26885de574a0b0e5309a9de4ea055b0bf302d9f00875f80e28cd29bb95a48aa53746d7de9465123dbb7";
    
    VerifySHA256Bouncy(HexStringToByteArray(msgSHA256), HexStringToByteArray(sigSHA256), HexStringToByteArray(pubSHA256));
    
    Console.ReadLine();

}

public static void VerifySHA256Bouncy(byte[] message, byte[] signature, byte[] pubkey)
{
    BigInteger x = new BigInteger(1, pubkey.Take(32).ToArray());
    BigInteger y = new BigInteger(1, pubkey.Skip(32).ToArray());

    X9ECParameters ecParams = NistNamedCurves.GetByName("P-256");
    ECDomainParameters domainParameters = new ECDomainParameters(ecParams.Curve, ecParams.G, ecParams.N, ecParams.H, ecParams.GetSeed());
    var G = ecParams.G;
    Org.BouncyCastle.Math.EC.ECCurve curve = ecParams.Curve;
    Org.BouncyCastle.Math.EC.ECPoint q = curve.CreatePoint(x, y);

    ECPublicKeyParameters pubkeyParam = new ECPublicKeyParameters(q, domainParameters);
    // expected format is SEQUENCE {INTEGER r, INTEGER s}
    var derSignature = new DerSequence(
        // first 32 bytes is "r" number
        new DerInteger(new BigInteger(1, signature.Take(32).ToArray())),
        // last 32 bytes is "s" number
        new DerInteger(new BigInteger(1, signature.Skip(32).ToArray())))
        .GetDerEncoded();
    var verifier = SignerUtilities.GetSigner("SHA-256withECDSA");
    verifier.Init(false, pubkeyParam);
    verifier.BlockUpdate(message, 0, message.Length);
    
    bool result = verifier.VerifySignature(derSignature);
    Console.WriteLine("result: " + result);
}

I am trying to adapt it to my use case which is verifying a SHA-384withECDSA signature. The only difference I found was the keylengths to be different. For SHA-256withECDSA the public key length used translated to a byte array of 64. Hence in the implementation I am doing:

BigInteger x = new BigInteger(1, pubkey.Take(32).ToArray());
BigInteger y = new BigInteger(1, pubkey.Skip(32).ToArray());

For SHA-384withECDSA, I have tweaked it as below.

public static void Main(string[] args)
{
    //SHA384 With ECDSA - Values received from Java integration example
    String sig384
        = "306402304f070f3cb570f92f573385880aaa58febc06b6842be59e8f56d196c63a5aacbb7124493bee84e0331c36eb9c4e3e27db0230628c89f28a53e4c2ed089abe2ada179cc64e3eb33204b0be07cdd34bd3cd5ed4d6f0aaf380cc0d436faee15509dadc14";
    String msg384 = "{\"transaction\":{\"amount\":\"64.50\",\"id\":\"248686\",\"type\":\"SALE\",\"result\":\"APPROVED\",\"card\":\"XXXXXXXXXXXX1111\",\"csc\":\"999\",\"authorization-code\":\"TAS231\",\"batch-string-id\":\"44\",\"display-message\":\"Transaction approved\",\"result-code\":\"000\",\"exp-date\":\"1218\"},\"payloadType\":\"transaction\"}";
    String pub384 = "307a301406072a8648ce3d020106092b240303020801010c0362000422ffee50bdb73df2698df79b8f62fa06c005acfb5d8e92c3088053620da94eb1f8978c769ace34231b51e41394b873b07a673dfb08e14e975fb26355a639f1be4339e787390ca4c8dd6463c76bc8421457906aafa8b9981445276fde833c136b";
    
    VerifySHA384Bouncy(Encoding.ASCII.GetBytes(msg384), HexStringToByteArray(sig384), HexStringToByteArray(pub384));
    
    Console.ReadLine();

}

public static void VerifySHA384Bouncy(byte[] message, byte[] signature, byte[] pubkey)
{
    BigInteger x = new BigInteger(1, pubkey.Take(62).ToArray());
    BigInteger y = new BigInteger(1, pubkey.Skip(62).ToArray());

    X9ECParameters ecParams = NistNamedCurves.GetByName("P-384");
    ECDomainParameters domainParameters = new ECDomainParameters(ecParams.Curve, ecParams.G, ecParams.N, ecParams.H, ecParams.GetSeed());
    var G = ecParams.G;
    Org.BouncyCastle.Math.EC.ECCurve curve = ecParams.Curve;
    Org.BouncyCastle.Math.EC.ECPoint q = curve.CreatePoint(x, y);

    ECPublicKeyParameters pubkeyParam = new ECPublicKeyParameters(q, domainParameters);
    // expected format is SEQUENCE {INTEGER r, INTEGER s}
    var derSignature = new DerSequence(
        // first 32 bytes is "r" number
        new DerInteger(new BigInteger(1, signature.Take(62).ToArray())),
        // last 32 bytes is "s" number
        new DerInteger(new BigInteger(1, signature.Skip(62).ToArray())))
        .GetDerEncoded();
    var verifier = SignerUtilities.GetSigner("SHA-384withECDSA");
    verifier.Init(false, pubkeyParam);
    verifier.BlockUpdate(message, 0, message.Length);
    
    bool result = verifier.VerifySignature(derSignature);
    Console.WriteLine("result: " + result);
}

The keylength for this example translates to a byte array of 124. Hence in my code I am doing

BigInteger x = new BigInteger(1, pubkey.Take(62).ToArray());
BigInteger y = new BigInteger(1, pubkey.Skip(62).ToArray());

My code for verifying a SHA-384withECDSA signature throws an exception:

System.ArgumentException HResult=0x80070057 Message=value invalid in Fp field element Parameter name: x Source=BouncyCastle.Crypto StackTrace: at Org.BouncyCastle.Math.EC.FpFieldElement..ctor(BigInteger q, BigInteger r, BigInteger x) at Org.BouncyCastle.Math.EC.FpCurve.FromBigInteger(BigInteger x) at Org.BouncyCastle.Math.EC.ECCurve.CreatePoint(BigInteger x, BigInteger y, Boolean withCompression) at Org.BouncyCastle.Math.EC.ECCurve.CreatePoint(BigInteger x, BigInteger y) at SignatureVerification.Program.VerifySHA384Bouncy(Byte[] message, Byte[] signature, Byte[] pubkey)

I am not sure if I am way off in my approach to the problem or I am comparing apples to oranges, I am not finding enough examples for SHA-384withECDSA to be able to figure this out. Any help would be greatly appreciated.


Solution

  • There are several issues:

    • An analysis with an ASN.1 Parser shows that the public key is given in X.509 format, s. e.g. here. I.e. the raw key x|y results as the last 2 * 48 = 96 bytes:
    pub384 = pub384.Substring(pub384.Length - 96 * 2); // 96 * 2 due to the hex encoding
    
    • The determination of x and y coordinate is (x and y are 48 bytes each):
    BigInteger x = new BigInteger(1, pubkey.Take(48).ToArray());
    BigInteger y = new BigInteger(1, pubkey.Skip(48).ToArray());
    
    • Furthermore, the analysis with the ASN.1 parser reveals that the public key belongs to the curve brainpoolp384t1:
    X9ECParameters ecParams = ECNamedCurveTable.GetByName("brainpoolp384t1"); 
    
    • Also, the signature sig384 is already specified in ASN.1/DER format and not in r|s (IEEE P1363) format, so the determination of derSignature can be omitted:
    bool result = verifier.VerifySignature(signature); // true
    

    With these changes, the signature is successfully verified.


    Full code:

    using Org.BouncyCastle.Asn1.X9;
    using Org.BouncyCastle.Crypto;
    using Org.BouncyCastle.Crypto.Parameters;
    using Org.BouncyCastle.Math;
    using Org.BouncyCastle.Math.EC;
    using Org.BouncyCastle.Security;
    using Org.BouncyCastle.Utilities.Encoders;
    using System;
    using System.Linq;
    using System.Text;
    ...
    public static void Main(string[] args)
    {
        string sig384 = "306402304f070f3cb570f92f573385880aaa58febc06b6842be59e8f56d196c63a5aacbb7124493bee84e0331c36eb9c4e3e27db0230628c89f28a53e4c2ed089abe2ada179cc64e3eb33204b0be07cdd34bd3cd5ed4d6f0aaf380cc0d436faee15509dadc14";
        string msg384 = "{\"transaction\":{\"amount\":\"64.50\",\"id\":\"248686\",\"type\":\"SALE\",\"result\":\"APPROVED\",\"card\":\"XXXXXXXXXXXX1111\",\"csc\":\"999\",\"authorization-code\":\"TAS231\",\"batch-string-id\":\"44\",\"display-message\":\"Transaction approved\",\"result-code\":\"000\",\"exp-date\":\"1218\"},\"payloadType\":\"transaction\"}";
        string pub384 = "307a301406072a8648ce3d020106092b240303020801010c0362000422ffee50bdb73df2698df79b8f62fa06c005acfb5d8e92c3088053620da94eb1f8978c769ace34231b51e41394b873b07a673dfb08e14e975fb26355a639f1be4339e787390ca4c8dd6463c76bc8421457906aafa8b9981445276fde833c136b";
        pub384 = pub384.Substring(pub384.Length - 96 * 2); // Fix 1
        VerifySHA384Bouncy(Encoding.ASCII.GetBytes(msg384), HexStringToByteArray(sig384), HexStringToByteArray(pub384));
    }
    
    public static void VerifySHA384Bouncy(byte[] message, byte[] signature, byte[] pubkey)
    {
        BigInteger x = new BigInteger(1, pubkey.Take(48).ToArray()); // Fix 2
        BigInteger y = new BigInteger(1, pubkey.Skip(48).ToArray());
    
        X9ECParameters ecParams = ECNamedCurveTable.GetByName("brainpoolp384t1"); // Fix 3
        ECDomainParameters domainParameters = new ECDomainParameters(ecParams.Curve, ecParams.G, ecParams.N, ecParams.H, ecParams.GetSeed());
        ECPoint G = ecParams.G;
        ECCurve curve = ecParams.Curve;
        ECPoint q = curve.CreatePoint(x, y);
    
        ECPublicKeyParameters pubkeyParam = new ECPublicKeyParameters(q, domainParameters);
        ISigner verifier = SignerUtilities.GetSigner("SHA-384withECDSA");
        verifier.Init(false, pubkeyParam);
        verifier.BlockUpdate(message, 0, message.Length);
        bool result = verifier.VerifySignature(signature); // Fix 4
        
        Console.WriteLine("result: " + result); // result: True
    }
    
    private static byte[] HexStringToByteArray(string str)
    {
        return Hex.Decode(str);
    }