Digital Signature in C# Without Using Bouncycastle

Digital signature using bouncy castle and certificate private key

I don´t know exactly what you need based on your code, but there X509 namespace/code is at
bcgit/bc-csharp - X509 and there is an utility class for conversion between System.Security.Cryptography and BouncyCastle
bcgit/bc-csharp - DotNetUtilities.cs

BouncyCastle got lots of test (and examples). Have a look at bcgit/bc-csharp - TestCertificateGen.cs too. Maybe this helps you.

EDIT: In general it should go something like this

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.X509;

// Your loaded certificate
X509Certificate cert = null;

// Your loaded RSA key
AsymmetricKeyParameter privateKey = null;

AsymmetricKeyParameter publicKey = cert.GetPublicKey();

ISigner signer = SignerUtilities.GetSigner(cert.SigAlgName);

// Init for signing, you pass in the private key
signer.Init(true, privateKey);

// Init for verification, you pass in the public key
signer.Init(false, publicKey);

Greetings

Validate EC SHA 256 signature in .net without bouncy castle

SignerUtilities.GetSigner() hashes implicitly, i.e. sha256HashByteArray is hashed again. Therefore instead of ECDsa#VerifyHash() (does not hash implicitly) the method ECDsa#VerifyData() (hashes implicitly) must be used.

Also, SignerUtilities.GetSigner() returns a signature in ASN.1 format, and ECDsa#VerifyData() expects a signature in r|s format (as you already figured out).

If both are taken into account, the verification is successful:

byte[] publicKey = Convert.FromBase64String("MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEd34IR9wYL76jLyZ148O/hjXo9iaFz/q/xEMXCwYPy6yxbxYzWDZPegG4FH+snXaXQPYD6QIzZNY/kcMjIGtUTg==");
byte[] sha256HashByteArray = Convert.FromBase64String("S3i6LAEzew5SDjQbq59/FraEAvGDg9y7fRIfbnhHPf4=");
byte[] signatureRS = Convert.FromBase64String("10f9tmAIvdD1HSq7Ux8IoKw8VhaIgqjywfzn9W2Twik0vEUB5RTbH4VJZ+FEZfRI4pQgPE9GiKCwIeknGAZddQ==");

var ecDsa = ECDsa.Create();
ecDsa.ImportSubjectPublicKeyInfo(publicKey, out _);

var isValid = ecDsa.VerifyData(sha256HashByteArray, signatureRS, HashAlgorithmName.SHA256);
Console.WriteLine(isValid); // True

Regarding the signature formats:

The posted signature in ASN.1 format

MEUCIQDXR/22YAi90PUdKrtTHwigrDxWFoiCqPLB/Of1bZPCKQIgNLxFAeUU2x+FSWfhRGX0SOKUIDxPRoigsCHpJxgGXXU=

is hex encoded

3045022100d747fdb66008bdd0f51d2abb531f08a0ac3c56168882a8f2c1fce7f56d93c229022034bc4501e514db1f854967e14465f448e294203c4f4688a0b021e92718065d75

From this, the signature in r|s format can be derived as (s. here)

d747fdb66008bdd0f51d2abb531f08a0ac3c56168882a8f2c1fce7f56d93c22934bc4501e514db1f854967e14465f448e294203c4f4688a0b021e92718065d75

or Base64 encoded:

10f9tmAIvdD1HSq7Ux8IoKw8VhaIgqjywfzn9W2Twik0vEUB5RTbH4VJZ+FEZfRI4pQgPE9GiKCwIeknGAZddQ==

Bouncy Castle SHA-384withECDSA Signature Verification Giving an Exception

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);
}

Bouncy Castle Sign and Verify SHA256 Certificate With C#

Bouncy Castle doesn't support XML formats at all. Unless your use-case strictly requires it, you'll find it much easier going just to use Base64 encodings, with certificates (X.509) and private keys (PKCS#8) stored in PEM format. These are all string formats, so should be usable with JSON directly.

There are other problems in the code samples: signing should use the private key, signatures shouldn't be treated as ASCII strings, possibly your messages are actually UTF8. I would expect the inner sign/verify routines to perhaps look like this:

    public string SignData(string msg, ECPrivateKeyParameters privKey)
{
try
{
byte[] msgBytes = Encoding.UTF8.GetBytes(msg);

ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA");
signer.Init(true, privKey);
signer.BlockUpdate(msgBytes, 0, msgBytes.Length);
byte[] sigBytes = signer.GenerateSignature();

return Convert.ToBase64String(sigBytes);
}
catch (Exception exc)
{
Console.WriteLine("Signing Failed: " + exc.ToString());
return null;
}
}

public bool VerifySignature(ECPublicKeyParameters pubKey, string signature, string msg)
{
try
{
byte[] msgBytes = Encoding.UTF8.GetBytes(msg);
byte[] sigBytes = Convert.FromBase64String(signature);

ISigner signer = SignerUtilities.GetSigner("SHA-256withECDSA");
signer.Init(false, pubKey);
signer.BlockUpdate(msgBytes, 0, msgBytes.Length);
return signer.VerifySignature(sigBytes);
}
catch (Exception exc)
{
Console.WriteLine("Verification failed with the error: " + exc.ToString());
return false;
}
}

A further issue is that I think .NET didn't get ECDSA support until .NET 3.5, in any case there's no ECDsa class in .NET 1.1 (which is BC's target for the upcoming 1.8 release - we will be "modernising" after that), so DotNetUtilities doesn't have support for ECDSA. However, we can export to PKCS#12 and import to BC. An example program:

    public void Program()
{
Console.WriteLine("Attempting to load cert...");
System.Security.Cryptography.X509Certificates.X509Certificate2 thisCert = LoadCertificate();

Console.WriteLine(thisCert.IssuerName.Name);
Console.WriteLine("Signing the text - Mary had a nuclear bomb");

byte[] pkcs12Bytes = thisCert.Export(X509ContentType.Pkcs12, "dummy");
Pkcs12Store pkcs12 = new Pkcs12StoreBuilder().Build();
pkcs12.Load(new MemoryStream(pkcs12Bytes, false), "dummy".ToCharArray());

ECPrivateKeyParameters privKey = null;
foreach (string alias in pkcs12.Aliases)
{
if (pkcs12.IsKeyEntry(alias))
{
privKey = (ECPrivateKeyParameters)pkcs12.GetKey(alias).Key;
break;
}
}

string signature = SignData("Mary had a nuclear bomb", privKey);

Console.WriteLine("Signature: " + signature);

Console.WriteLine("Verifying Signature");

var bcCert = DotNetUtilities.FromX509Certificate(thisCert);
if (VerifySignature((ECPublicKeyParameters)bcCert.GetPublicKey(), signature, "Mary had a nuclear bomb."))
Console.WriteLine("Valid Signature!");
else
Console.WriteLine("Signature NOT valid!");
}

I haven't really tested any of the above code, but it should give you something to go on. Note that BC has key and certificate generators too, so you could choose to use BC for everything (except XML!), and export/import to/from .NET land only where necessary.

C# Sign Data with RSA using BouncyCastle

Okay I could not find any documentation on how to do this. But I ended up figuring it out.
I am pasting the full code here so hopefully it can help someone in the future.

This class will calculate a RSA signature with a sha1 hash for the provided string and verify it as well.

using System;
using System.IO;
using System.Text;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities.Encoders;

namespace API.Crypto
{
public class RsaSha1Signing
{
private RsaKeyParameters MakeKey(String modulusHexString, String exponentHexString, bool isPrivateKey)
{
var modulus = new Org.BouncyCastle.Math.BigInteger(modulusHexString, 16);
var exponent = new Org.BouncyCastle.Math.BigInteger(exponentHexString, 16);

return new RsaKeyParameters(isPrivateKey, modulus, exponent);
}

public String Sign(String data, String privateModulusHexString, String privateExponentHexString)
{
/* Make the key */
RsaKeyParameters key = MakeKey(privateModulusHexString, privateExponentHexString, true);

/* Init alg */
ISigner sig = SignerUtilities.GetSigner("SHA1withRSA");

/* Populate key */
sig.Init(true, key);

/* Get the bytes to be signed from the string */
var bytes = Encoding.UTF8.GetBytes(data);

/* Calc the signature */
sig.BlockUpdate(bytes, 0, bytes.Length);
byte[] signature = sig.GenerateSignature();

/* Base 64 encode the sig so its 8-bit clean */
var signedString = Convert.ToBase64String(signature);

return signedString;
}

public bool Verify(String data, String expectedSignature, String publicModulusHexString, String publicExponentHexString)
{
/* Make the key */
RsaKeyParameters key = MakeKey(publicModulusHexString, publicExponentHexString, false);

/* Init alg */
ISigner signer = SignerUtilities.GetSigner("SHA1withRSA");

/* Populate key */
signer.Init(false, key);

/* Get the signature into bytes */
var expectedSig = Convert.FromBase64String(expectedSignature);

/* Get the bytes to be signed from the string */
var msgBytes = Encoding.UTF8.GetBytes(data);

/* Calculate the signature and see if it matches */
signer.BlockUpdate(msgBytes, 0, msgBytes.Length);
return signer.VerifySignature(expectedSig);
}
}
}

Invalid signature when creating a certificate using BouncyCastle with an external Azure KeyVault (HSM) Key

The problem is that the signature being returned by key vault is in a "raw" (64-byte) format, where the first 32 are R and the last 32 are S. For this to work in bouncycastle, your GenerateSignature method needs to return this in an ASN.1 formatted byte array, which in the end will be somewhere between 70 and 72 bytes.

You can look around online on what this practically means, but you will want to:

  1. Create a new byte array for your result
  2. split the output from key vault into two initially 32-bit arrays, R and S
  3. If the 0th element of either of the R or S arrays has a high MSB, you need to insert a 0 before the start of the respective array (otherwise do nothing and the array stays 32 bytes long).
  4. Build the necessary ASN.1 headers (either manually like I showed below, or maybe bouncycastle has some library features to create an ASN.1 message). So at the end, the output byte array should contain
0x30
one byte containing the length of the rest of the array*
0x02
a byte containing the length of the R array (either 32 or 33 depending on if + or -)
0x02
a byte containing the length of the S array (either 32 or 33 depending on if + or -)
the entire S array


  1. Return this array as the output of GenerateSignature

* so the entire length will be length of R + length of S + 4 header bytes (R length, R header, S length, S header)

I have tested this approach with a key of my own as returned by a cloud service which also returns the 64 byte R+S response and it works.



Related Topics



Leave a reply



Submit