1

signature1 value is always 39 byte[] array when i convert to hex, length is greater than 64. I wanted to generate exact 64 length hex signature value from P-128 ECC Algorithm in c#.

Let me explain below code:

  1. Making hash of plain text message which i have to sign

  2. Read 128bit PEM key from local folder

  3. Pass key and generate signature.

        public static string GenerateSignature(string message, string privateKeyPath)
        {
                string hashMessage;
                using (SHA256 sha256 = SHA256.Create())
                {
                    byte[] data = sha256.ComputeHash(Encoding.Default.GetBytes(message));
                    hashMessage = convertByteArrayToHexString(data);
                    Console.WriteLine("Signing message: {0}", message);
                    Console.WriteLine("Hash of the signing message: {0}", hashMessage);
                }
                byte[] hashMessageBytes = Encoding.UTF8.GetBytes(hashMessage);
                ISigner signer = SignerUtilities.GetSigner("SHA256withECDSA");
                AsymmetricCipherKeyPair publicKey = getPrivateKeyFromPemFile(privateKeyPath);
                signer.Init(true, publicKey.Private);
                signer.BlockUpdate(hashMessageBytes, 0, hashMessageBytes.Length);
                byte[] signature1 = signer.GenerateSignature();
    
    }
    
Sai Sherlekar
  • 1,804
  • 1
  • 17
  • 18

1 Answers1

2

There are different signature formats, one is the ASN.1 DER format, which is used in your code and is explained here. For curve secp128r1 (P-128) the generated signatures have a length between 38 and 40 bytes.

The format you seem to expect is r | s (see here), which for curve secp128r1 has a length of 32 bytes.

You can easily convert between the two formats manually.

Newer versions of BouncyCastle for C# also allow the direct generation of the signature in the r | s format, see here, e.g. with SHA-256withPLAIN-ECDSA (instead of SHA-256withECDSA).

A few notes:

  • According to NIST, a key size of 128 bits is meanwhile too small (instead, at least 224 is recommended for 2019-2030), here. secp128r1 was still recommended in SEC 2: Recommended Elliptic Curve Domain Parameters V1.0 (from 2000), but no longer in SEC 2: Recommended Elliptic Curve Domain Parameters V2.0 (from 2010).
  • SHA-256withECDSA (SHA-256withPLAIN-ECDSA) hashes the message implicitly (with SHA-256), i.e. the explicit hashing is not necessary and results in a double (redundant) hashing.
  • Furthermore, in your explicit hashing, not the actual binary hash is passed to the signer, but the UTF-8 encoded hexadecimal encoded hash.
  • secp128r1 corresponds to a key size (order of the base point) of 128 bits (16 bytes). But with SHA256, you are using a digest that is twice as large (32 bytes). In this case according to NIST FIPS 186-4 the leftmost n bits of the hash are used, here.

Edit:

To concretise what has been said so far: The following code UTF-8 encodes the string message and generates an ECDSA signature in r|s format (which corresponds to IEEE P1363, here) based on curve secp128r1 and digest SHA256:

public static string GenerateSignature(string message, string privateKeyPath)
{
    byte[] messageBytes = Encoding.UTF8.GetBytes(message);
    ISigner signer = SignerUtilities.GetSigner("SHA-256withPLAIN-ECDSA");
    AsymmetricCipherKeyPair keyPair = getPrivateKeyFromPemFile(privateKeyPath); // get secp128r1 key pair
    signer.Init(true, keyPair.Private);
    signer.BlockUpdate(messageBytes, 0, messageBytes.Length);
    byte[] signature = signer.GenerateSignature();
    return ByteArrayToString(signature); // https://stackoverflow.com/a/311179/9014097
}

Note the differences compared to the code you originally posted:

  • Since (Org.BouncyCastle.Crypto.) ISigner#GenerateSignature() hashes implicitly, no explicit hashing is performed.
  • SHA-256withPLAIN-ECDSA generates the signature directly in r|s format and exists since v1.8.4 (bccrypto-csharp-1.8.4, released 27th October 2018).

In case of an older BouncyCastle version (where only SHA-256withECDSA is available), the signature has to be converted manually from ASN.1 DER to r|s format. This is easy, since r and s can be extracted directly from the ASN.1 DER format, see here.

If the verification on the client side still fails with the above code, check that the keys, curves and digests on both sides match and the verification process itself.

For completeness:
Of course, also (Org.BouncyCastle.Crypto.Signers.) ECDsaSigner.GenerateSignature(byte[] msg) can be used to generate the signature. It is important that this method does not hash implicitly, i.e. here indeed you must hash explicitly. This method returns r and s as (Org.BouncyCastle.Math.) BigInteger[] so that both parts can be easily hex encoded with e.g. toString(16) and then concatenated to r|s.

Topaco
  • 40,594
  • 4
  • 35
  • 62
  • Tapaco, I need IEEE P1363 output with single hex string of 64 length, I am getting signature(r,s) and if i joined it converts 64 length hex. Still its not validating at my client place. please help – Sai Sherlekar Jul 24 '20 at 08:42
  • For curve secp128r1 (P-128) the generated signatures have a length between 38 and 40 bytes. I need exact 32 bytes(using IEEE P1363) then only it will conver to 64 hex – Sai Sherlekar Jul 24 '20 at 08:43
  • How did you modify the code to generate the signature in _r|s_ format, did you replace `SHA-256withECDSA` with `SHA-256withPLAIN-ECDSA`? For more complex changes, e.g. if you manually derived the _r|s_ format from the ASN.1 DER format, please edit your answer and add the current code to create the r|s signature (please do not change the already posted code, but add the new code). – Topaco Jul 24 '20 at 09:40
  • There are other problems in your code, especially points 2 and 3 of my list at the bottom: `ISigner#GenerateSignature()` does the hashing _implicitly_ (which is why the digest name is included in the specifier), so it is not necessary to hash _explicitly_. Thus you could try to replace `hashMessageBytes` in `BlockUpdate` with `Encoding.UTF8.GetBytes(message)`. – Topaco Jul 24 '20 at 10:01
  • The last codes posted differ significantly from the original code. It makes little sense to work with three codes, so I post a possible solution based on the original code, see the Edit section in my answer. – Topaco Jul 24 '20 at 18:28