I'm attempting to make use of the Bouncy Castle apis to verify the signature of a clear signed message. (I've got signing, encryption, and decryption working, but some of the things I need to verify won't have been encrypted so I need to be able to do these separately).
I want to verify the signature, and if it is verified then return just the message text (without signature).
My function at the moment is the following;
public string GetVerifiedMessage(string signedCleartext, PgpPublicKey publicKey)
{
var inputStream = PgpUtilities.GetDecoderStream(ReadAsStream(signedCleartext));
var outStr = new MemoryStream();
inputStream.CopyTo(outStr);
outStr.Close();
var objectFactory = new PgpObjectFactory(inputStream);
var signatureList = (PgpSignatureList) objectFactory.NextPgpObject();
var signature = signatureList[0];
var literalData = (PgpLiteralData) objectFactory.NextPgpObject();
var data = literalData.GetInputStream();
signature.InitVerify(publicKey);
var memoryStream = new MemoryStream();
int ch;
while ((ch = data.ReadByte()) >= 0)
{
signature.Update((byte) ch);
memoryStream.WriteByte((byte) ch);
}
if (!signature.Verify())
{
throw new Exception("Signature was not verified");
}
return Encoding.UTF8.GetString(memoryStream.ToArray());
}
This is based off the examples in the bouncy castle codebase and elsewhere, although I have had to vary them to do this in memory rather than writing to files.
When running this, I get an error because the objectFactory does not contain any pgp objects. I have verified that third party tools can verify the signature on the example message I'm using.
I've tried a few variations - not copying to outStr gives an "Unknown object in stream 45" exception on trying to get the PgpObject, which is annoying as in it's current form it is useless.
Can anyone point out where I'm going wrong?
Related
I am using iText7 for performing PDF and signing operations. My scenario is I am computing hash on my local machine and sending this hash to signing server and in response get the signed PKCS1(Raw signature) and then I am embedding this signature into PDF.
My code snippet is as follows:
1: Read public cert from smart card device.
2: Initialize PdfReader from original document bytes containing signature field named "Signature1"
3: Initialize PdfSigner and set signature appearance:
PdfSigner pdfSigner = new PdfSigner(pdfReader, outputStream, new StampingProperties().UseAppendMode());
pdfSigner.SetFieldName("Signature1");
pdfSigner.GetDocument().GetCatalog().SetModified();
ImageData imageData = ImageDataFactory.Create(handSignatureBytes);
PdfSignatureAppearance signatureAppearance = pdfSigner.GetSignatureAppearance();
signatureAppearance.SetContact("contactInfo");
signatureAppearance.SetLocation("locationInfo");
signatureAppearance.SetPageNumber(1);
signatureAppearance.SetReason("signingReason");
signatureAppearance.SetSignatureGraphic(imageData);
signatureAppearance.SetRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);
signatureAppearance.SetSignatureCreator("Malik");
signatureAppearance.SetCertificate(x509Certificate);
4: I have implemented IExternalSignatureContainer interface to get document hash:
public class PreSigning : IExternalSignatureContainer
{
protected PdfDictionary sigDic;
private byte[] hash;
public PreSigning(PdfName filter, PdfName subFilter)
{
sigDic = new PdfDictionary();
sigDic.Put(PdfName.Filter, filter);
sigDic.Put(PdfName.SubFilter, subFilter);
}
public void ModifySigningDictionary(PdfDictionary signDic)
{
signDic.PutAll(sigDic);
}
public byte[] Sign(Stream data)
{
this.hash = DigestAlgorithms.Digest(data, DigestAlgorithms.GetMessageDigest("SHA256"));
return new byte[0];
}
public byte[] getHash()
{
return hash;
}
public void setHash(byte[] hash)
{
this.hash = hash;
}
}
5: Getting document hash:
PreSigning external = new PreSigning(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
pdfSigner.SignExternalContainer(external, estimatedSize);
byte[] documentHash = external.getHash();
6: Initialize PdfPKCS7 class to get Data To Be Signed and getting hash of Data To Be Signed to send to signing server:
PdfPKCS7 pdfPKCS7 = new PdfPKCS7(null, x509CertificatesChain, "SHA256", false);
dataToBeSigned = pdfPKCS7.GetAuthenticatedAttributeBytes(documentHash, PdfSigner.CryptoStandard.CMS, null, null);
byte[] dataToSignHash = DigestAlgorithms.Digest(new MemoryStream(dataToBeSigned), DigestAlgorithms.GetMessageDigest("SHA256"));
7: I have keep the outputStream from PdfSigner for signature embedding phase:
documentStreamBytes = ((MemoryStream)outputStream).ToArray();
8: Send the Data To Be Signed hash to signing server.
9: Get the PKCS1 data from signing server in response:
byte[] PKCS1 = Convert.FromBase64String(preSigningResponse.signedHash);
10: Initialized PdfPKCS7 class for getting PKCS7 from PKCS1:
PdfPKCS7 pdf = new PdfPKCS7(null, x509CertificatesChain, "SHA256", false);
pdf.SetExternalDigest(PKCS1, null, "RSA");
byte[] pkcs7Data = pdf.GetEncodedPKCS7(documentHash, PdfSigner.CryptoStandard.CMS, null, null, null);
11: Get the original document from documentStreamBytes:
Stream pdfReaderStream = new MemoryStream(documentStreamBytes);
PdfReader reader = new PdfReader(pdfReaderStream);
PdfDocument originalDocument = new PdfDocument(reader, new PdfWriter(new MemoryStream()));
12: I have implemented IExternalSignatureContainer for signature embedding using PdfSigner.SignDeferred() method:
public class PostSigning : IExternalSignatureContainer
{
protected byte[] _sig;
public PostSigning (byte[] sign)
{
_sig = sign;
}
public void ModifySigningDictionary(PdfDictionary signDic)
{
}
public byte[] Sign(Stream data)
{
return _sig;
}
}
13: Calling PdfSigner.SignDeferred() method to get the final document:
Stream resultStream = new MemoryStream();
IExternalSignatureContainer externalSignatureContainer = new PostSigning(pkcs7Data);
PdfSigner.SignDeferred(originalDocument, "Signature1", resultStream, externalSignatureContainer);
byte[] finalDoc = ((MemoryStream)resultStream).ToArray();
I am getting the following error:
Document has been altered or corrupted since the signature has been applied.
Can anyone help me regarding this scenario using iText7
There are numerous issues in your code.
Starting a signature twice
Foremost: In your second part (Embed the returned PKCS1 into PDF) you start again with the original file and create a new signature field therein. This results in a slightly different PDF than the one you prepared in your first part; thus, the signature value you have retrieved between the two parts obviously cannot be used for this new PDF.
You must change your architecture.
If you want to keep your two-step approach, you have to keep the file you created in your first part in outputStream and re-use it in the second part. And to fill in the retrieved signature, you must use PdfSigner.signDeferred instead of starting with a new PdfSigner.
Alternatively you can do it in one step, putting the signature server call into a custom IExternalSignatureContainer implementation.
Signing the wrong PDF data
In your first part you appear to set the outputStream contents as the data to sign (actually you even put it into a field that by its name, responseObject.base64Hash, should only hold a hash value; I don't understand that at all). But the bytes to sign are not the complete outputStream contents, a placeholder therein for the final signature value must be excluded.
You must only sign the resulting PDF without that placeholder. You can get it if instead of the ExternalBlankSignatureContainer as is you extend it by overriding the byte[] Sign(Stream data) method and grabbing the Stream parameter thereof. This stream contains exactly the bytes to sign.
The wrong signature format
You mention you get the signed Pkcs1 from your signing server. What you need, though, is a CMS signature container.
If your signature server also offers to return CMS (or PKCS#7) signature containers, use that kind of call.
Otherwise you must build a CMS container yourself, e.g. using the iText PdfPKCS7 class or BouncyCastle mechanisms.
(I've already been thru a lot of Stackoverflow/google results trying to find a fix for this.)
I am validating JWTs signed with RS256 using the default C# JwtSecurityTokenHandler. In some cases, the validation fails when it shouldn't. Concretely, tokens from a given Authorization Server validate properly while tokens form another Authorization Server won't.
BUT... Using the same JWTs and RSA Certificates on JWT.IO validates ALL the tokens succesfully. This is the part that makes me believe that there's something wrong/unusual in the C# implementation. I am also able to validate the same JWTs using the same Certificates using the oidc-client JavaScript library. The one place where the validation sometimes fails is in C#.
I traced the error down to JwtSecurityTokenHandler's ValidateSignature method. Searching the original github code and googling about RSA, I came down with this bare-bone method which allows me to reproduce the problem in a plain console app:
static void ValidateJWT(string token, string modulus, string exponent)
{
string tokenStr = token;
JwtSecurityToken st = new JwtSecurityToken(tokenStr);
string[] tokenParts = tokenStr.Split('.');
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(
new RSAParameters()
{
Modulus = FromBase64Url(modulus),
Exponent = FromBase64Url(exponent)
});
SHA256 sha256 = SHA256.Create();
byte[] hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(tokenParts[0] + '.' + tokenParts[1]));
RSAPKCS1SignatureDeformatter rsaDeformatter = new RSAPKCS1SignatureDeformatter(rsa);
rsaDeformatter.SetHashAlgorithm("SHA256");
var valid = rsaDeformatter.VerifySignature(hash, FromBase64Url(tokenParts[2]));
Console.WriteLine(valid); // sometimes false when it should be true
}
private static byte[] FromBase64Url(string base64Url)
{
string padded = base64Url.Length % 4 == 0
? base64Url : base64Url + "====".Substring(base64Url.Length % 4);
string base64 = padded.Replace("_", "/")
.Replace("-", "+");
return Convert.FromBase64String(base64);
}
It is from that RSACryptoServiceProvider and using RSAKeys from here (https://gist.github.com/therightstuff/aa65356e95f8d0aae888e9f61aa29414) that I was able to Export the Public Key that allows me to validate JWTs successfully on JWT.IO.
string publicKey = RSAKeys.ExportPublicKey(rsa);
I can't provide actual JWTs to this post (they expire anyways), but does anyone knows of a crypto behavior specific to C# that could explain these validation errors, which don't happen in JavaScript nor on JWT.IO ?
And if so, any solution for this?
Thanks,
Martin
https://www.rfc-editor.org/rfc/rfc7518#section-6.3.1.1
Note that implementers have found that some cryptographic libraries
prefix an extra zero-valued octet to the modulus representations they
return, for instance, returning 257 octets for a 2048-bit key, rather
than 256. Implementations using such libraries will need to take
care to omit the extra octet from the base64url-encoded
representation.
In the case of one of the tokens you provided on a copy of this issue elsewhere, the decode of the modulus includes a prefixed 0x00 byte. This causes downstream problems. But you can fix their non-conformance.
byte[] modulusBytes = FromBase64Url(modulus);
if (modulusBytes[0] == 0)
{
byte[] tmp = new byte[modulusBytes.Length - 1];
Buffer.BlockCopy(modulusBytes, 1, tmp, 0, tmp.Length);
modulusBytes = tmp;
}
It looks like RS256 treats the signature as opaque bytes, so it will encode it as-is. So you probably don't need this correction (though it's where my investigation started):
byte[] sig = FromBase64Url(tokenParts[2]);
if (sig.Length < modulusBytes.Length)
{
byte[] tmp = new byte[modulusBytes.Length];
Buffer.BlockCopy(sig, 0, tmp, tmp.Length - sig.Length, sig.Length);
sig = tmp;
}
And again I ran into trouble while porting a .NET Desktop App to a Windows Store App...
Long story short, I have a ZIP File with an encrypted and signed XML File and a certificate in it. The decryption is working (more or less) but now I have to "unsign" the XML and I'm stuck.
In the .NET App the unsigning is done with System.Security.Cryptography.Pkcs.SignedCms but that class does not exist in WinRt (as always...)
Is there any alternative in WinRT?
here is some of the code used in the .NET App:
public static byte[] CheckAndRemoveSignature(byte[] data, X509Certificate2Collection certStore, out SignedCms out_signature)
{
SignedCms signedMessage = new SignedCms();
signedMessage.Decode(data);
if ((certStore != null) && (certStore.Count > 0))
signedMessage.CheckSignature(certStore, true);
else
signedMessage.CheckSignature(true);
out_signature = signedMessage;
// return data without signature
return signedMessage.ContentInfo.Content;
}
I already searched a lot, but the only thing I found, that could have helped me was this post. Unfortunately the marked answer does not provide any helpful information :(
Windows 8 Metro cryptography - using SignedCms Pkcs7
I would really appreciate some help here :)
Edit
The essential problem is to get the original xml data out of the signed byte array.
Or, more specifically I need the functionality of these few lines of code in WinRT
SignedCms signedMessage = new SignedCms();
signedMessage.Decode(data);
byte[] result = signedMessage.ContentInfo.Content;
I tried the example from pepo, but I get a MalformedContent Exception
private byte[] CheckAndRemoveSignature(byte[] data)
{
try
{
// load using bouncyCastle
CmsSignedData sig = new CmsSignedData(data);
// var allSigsValid = VerifySignatures(sig);
byte[] content = sig.SignedContent.GetContent() as byte[];
return content;
}
catch (Exception ex)
{
cryptOutput.Text += "Error removing Signature: " + ex;
return data;
}
I get this exception:
Org.BouncyCastle.Cms.CmsException: Malformed content. ---> System.ArgumentException: unknown object in factory: DerApplicationSpecific
at Org.BouncyCastle.Asn1.Cms.ContentInfo.GetInstance(Object obj)
at Org.BouncyCastle.Cms.CmsUtilities.ReadContentInfo(Asn1InputStream aIn)
--- End of inner exception stack trace ---
at Org.BouncyCastle.Cms.CmsUtilities.ReadContentInfo(Asn1InputStream aIn)
at Org.BouncyCastle.Cms.CmsUtilities.ReadContentInfo(Stream input)
at Org.BouncyCastle.Cms.CmsSignedData..ctor(Byte[] sigBlock)
at TestApp.MainPage.CheckAndRemoveSignature(Byte[] data)
Code from Desktop App where the XML-File is signed:
private byte[] signInternal(byte[] data, X509Certificate2 signatureCert, bool signatureOnly)
{
CAPICOM.SignedData signedData = new CAPICOM.SignedDataClass();
CAPICOM.Utilities u = new CAPICOM.UtilitiesClass();
signedData.set_Content(u.ByteArrayToBinaryString(data));
GC.Collect();
CAPICOM.Signer signer = new CAPICOM.Signer();
signer.Options = CAPICOM.CAPICOM_CERTIFICATE_INCLUDE_OPTION.CAPICOM_CERTIFICATE_INCLUDE_END_ENTITY_ONLY;
CAPICOM.CertificateClass certClass = new CAPICOM.CertificateClass();
certClass.Import(Convert.ToBase64String(signatureCert.Export(X509ContentType.SerializedCert)));
signer.Certificate = certClass;
GC.Collect();
if (this.validateCert(signatureCert))
return (byte[])Convert.FromBase64String(signedData.Sign(signer, signatureOnly, CAPICOM.CAPICOM_ENCODING_TYPE.CAPICOM_ENCODE_BASE64));
else
return new byte[] { };
}
Solution
In the end it turned out that there was a big problem with encoding, that I did not think was noteworthy. The answer from pepo is working, however I will post my version, to show how it works if you get the file from a zip folder:
// get bytes from zip
byte[] data = getFileContentAsByteArray(zipBytes, ze.FileName);
var dataString = Encoding.UTF8.GetString(data, 0, data.Length);
// check and remove signature
bool isValid;
byte[] withoutSig = CheckAndRemoveSignature(dataString, out isValid);
private byte[] CheckAndRemoveSignature(string data, out bool isValid)
{
isValid = false;
// using bouncyCastle
try
{
var bytes = Convert.FromBase64String(data);
// assign data to CmsSignedData
CmsSignedData sig = new CmsSignedData(bytes);
// check if signature is valid
var allSigsValid = VerifySignaturesBC(sig);
if (allSigsValid.Equals(true)) { isValid = true; }
// get signature from cms
byte[] content = sig.SignedContent.GetContent() as byte[];
return content;
}
catch (Exception ex) { cryptOutput.Text += "Error in 'BouncyCastle unsign' " + ex; return null; }
}
Based on comments I understand that you have a PKCS#7 structure (SignedCms) and the content of that structure is XmlDocument.
Because there is no SignedCms in WinRT API you have two choices. Either use some ASN.1 library and parse PKCS#7 manually looking for content or use i.e. BouncyCastle which has SignedCms implemented and can parse that strusture.
You asked for an example using bouncyCastle. Here it is.
using Org.BouncyCastle.Cms;
using Org.BouncyCastle.X509.Store;
using System.Collections;
using System.Security.Cryptography.Pkcs;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
// make some pkcs7 signedCms to work on
SignedCms p7 = new SignedCms(new System.Security.Cryptography.Pkcs.ContentInfo(new byte[] { 0x01, 0x02 }));
p7.ComputeSignature(new CmsSigner(), false);
// encode to get signedCms byte[] representation
var signedCms = p7.Encode();
// load using bouncyCastle
CmsSignedData sig = new CmsSignedData(signedCms);
var allSigsValid = VerifySignatures(sig);
byte[] content = sig.SignedContent.GetContent() as byte[];
}
// taken from bouncy castle SignedDataTest.cs
private static bool VerifySignatures(
CmsSignedData sp)
{
var signaturesValid = true;
IX509Store x509Certs = sp.GetCertificates("Collection");
SignerInformationStore signers = sp.GetSignerInfos();
foreach (SignerInformation signer in signers.GetSigners())
{
ICollection certCollection = x509Certs.GetMatches(signer.SignerID);
IEnumerator certEnum = certCollection.GetEnumerator();
certEnum.MoveNext();
Org.BouncyCastle.X509.X509Certificate cert = (Org.BouncyCastle.X509.X509Certificate)certEnum.Current;
signaturesValid &= signer.Verify(cert);
}
return signaturesValid;
}
}
}
As for the ASN.1 library, I have only worked with bouncyCastle which has ASN.1 parser or ASN.1 editor which is a very useful GUI application for showing structure of PKCS#7, certificates etc. So I can recommend only those two.
You’re probably looking for something like Windows.Security.Cryptography.Certificates.CmsAttachedSignature.VerifySignature() and it's 'Content' property
See here
I have a set of questions:
What does a .p7s file contain? I read that it usually is an encrypted message sent to an e-mail, but in my case I have a file and a .p7s file on my hdd. The file is much larger than the .p7s, so I am wondering what is in it (and of course how to later use it).
2.this question occurs mostly because I have no naswer for 1. - If I have my public key in the form of a byte array, how can I verify the signature in C#? I found this on the internet, but again, it uses some text gotten from an e-mail and I honestly don't know what is happening:
public static bool Verify(byte[] signature, string text)
{
X509Certificate2 certificate = new X509Certificate2(#"D:\My-CV.docx.p7s");
if (signature == null)
throw new ArgumentNullException("signature");
if (certificate == null)
throw new ArgumentNullException("certificate");
//hash the text
// Methode 3 for Hashing
System.Security.Cryptography.SHA1 hash3 = System.Security.Cryptography.SHA1.Create();
System.Text.UnicodeEncoding encoder = new System.Text.UnicodeEncoding();
byte[] combined = encoder.GetBytes(text);
byte[] hash3byte = hash3.ComputeHash(combined);
//Adding the text from the email, to a contentInfo
ContentInfo content = new ContentInfo(hash3byte);
// decode the signature
SignedCms verifyCms = new SignedCms(content, true);
verifyCms.Decode(signature);
// verify it
try
{
verifyCms.CheckSignature(new X509Certificate2Collection(certificate), false);
return true;
}
catch (CryptographicException)
{
return false;
}
}
Can someone help me with this?
The .p7s file is containing the signature of your document. The content is not encrypted, but it's look like.
The document file is much larger, because it contains the date, the .p7s the signature.
To check the signature, you need the public part of the certificate with which the document was signed.
You can check this two posts to check the signature :
Check signature for x509 certificate
In C#, sign an xml with a x.509 certificate and check the signature
The last thing is to load the signature data, from the p7s file.
I make some search, and come back to you.
I've got the following code sample in Java, and I need to re-enact it in C#:
PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(pkcs8PrivateKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privKey = keyFactory.generatePrivate(privKeySpec);
Signature sign = Signature.getInstance("MD5withRSA");
sign.initSign(privKey);
sign.update(data);
byte[] signature = sign.sign();
Is it possible with the standard .Net Crypto API, or should I use BouncyCastle?
Thanks,
b.
Another way is to use CNG (Cryptography Next Generation), along with the Security.Cryptography DLL from CodePlex
Then you can write:
byte[] dataToSign = Encoding.UTF8.GetBytes("Data to sign");
using (CngKey signingKey = CngKey.Import(pkcs8PrivateKey, CngKeyBlobFormat.Pkcs8PrivateBlob))
using (RSACng rsa = new RSACng(signingKey))
{
rsa.SignatureHashAlgorithm = CngAlgorithm.MD5;
return rsa.SignData(dataToSign);
}
Updated thanks to Simon Mourier: with .Net 4.6, you no longer need a separate library
I am running into a very similar problem trying to create a native C# tool for packing Chrome extensions (using SHA1, not MD5, but that's not a big difference). I believe I have tried literally every possible solution for .Net: System.Security.Cryptography, BouncyCastle, OpenSSL.Net and Chilkat RSA.
The best solution is probably Chilkat; their interface is the cleanest and most straightforward, it's well-supported and well-documented, and there are a million examples. For instance, here's some code using their library that does something very close to what you want: http://www.example-code.com/csharp/rsa_signPkcs8.asp. However, it's not free (though $150 is not unreasonable, seeing as I have burned 2 days trying to figure this out, and I make a bit more than $75 a day!).
As a free alternative, JavaScience offers up a number of crypto utilities in source form for multiple languages (including C#/.Net) at http://www.jensign.com/JavaScience/cryptoutils/index.html. The one that's most salient to what you are trying to do is opensslkey (http://www.jensign.com/opensslkey/index.html), which will let you generate a RSACryptoServiceProvider from a .pem file. You can then use that provider to sign your code:
string pemContents = new StreamReader("pkcs8privatekey.pem").ReadToEnd();
var der = opensslkey.DecodePkcs8PrivateKey(pemContents);
RSACryptoServiceProvider rsa = opensslkey.DecodePrivateKeyInfo(der);
signature = rsa.SignData(data, new MD5CryptoServiceProvider());
You can use this code . At the first you should download "BouncyCastle.Crypto.dll" from http://www.bouncycastle.org/csharp/ .
/// <summary>
/// MD5withRSA Signing
/// https://www.vrast.cn
/// keyle_xiao 2017.1.12
/// </summary>
public class MD5withRSASigning
{
public Encoding encoding = Encoding.UTF8;
public string SignerSymbol = "MD5withRSA";
public MD5withRSASigning() { }
public MD5withRSASigning(Encoding e, string s)
{
encoding = e;
SignerSymbol = s;
}
private AsymmetricKeyParameter CreateKEY(bool isPrivate, string key)
{
byte[] keyInfoByte = Convert.FromBase64String(key);
if (isPrivate)
return PrivateKeyFactory.CreateKey(keyInfoByte);
else
return PublicKeyFactory.CreateKey(keyInfoByte);
}
public string Sign(string content, string privatekey)
{
ISigner sig = SignerUtilities.GetSigner(SignerSymbol);
sig.Init(true, CreateKEY(true, privatekey));
var bytes = encoding.GetBytes(content);
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 content, string signData, string publickey)
{
ISigner signer = SignerUtilities.GetSigner(SignerSymbol);
signer.Init(false, CreateKEY(false, publickey));
var expectedSig = Convert.FromBase64String(signData);
/* Get the bytes to be signed from the string */
var msgBytes = encoding.GetBytes(content);
/* Calculate the signature and see if it matches */
signer.BlockUpdate(msgBytes, 0, msgBytes.Length);
return signer.VerifySignature(expectedSig);
}
}
This SO question answers the PKCS#8 part of your code. The rest of the .NET RSA classes are a bizarre jumble of partially overlapping classes that are very difficult to fathom. It certainly appears that signature support is in either of the RSACryptoServiceProvider and/or RSAPKCS1SignatureFormatter classes.
Disclaimer: I know Java and cryptography, but my knowledge of C# and .NET is very limited. I am writing here only under the influence of my Google-fu skills.
Assuming that you could decode a PKCS#8-encoded RSA private key, then, from what I read on MSDN, the rest of the code should look like this:
byte[] hv = MD5.Create().ComputeHash(data);
RSACryptoServiceProvider rsp = new RSACryptoServiceProvider();
RSAParameters rsp = new RSAParameters();
// here fill rsp fields by decoding pkcs8PrivateKey
rsp.ImportParameters(key);
RSAPKCS1SignatureFormatter rf = new RSAPKCS1SignatureFormatter(rsp);
rf.SetHashAlgorithm("MD5");
byte[] signature = rf.CreateSignature(hv);
The relevant classes are in the System.Security.Cryptography namespace.
As for the PKCS#8 key blob decoding (i.e. filling in the rsp fields), I found this page which describes a command-line utility in C# which can perform that job. The source code is provided and is a single C# file. From what I read in it, that code decodes the PKCS#8 file "manually"; indirectly, this should mean that raw .NET (2.0) does not have facilities for PKCS#8 key file decoding (otherwise the author of that tool would not have went to the trouble of implementing such decoding). For your task at hand, you could scavenge from that source file the parts that you need, skipping anything about PEM and symmetric encryption; your entry point would be the DecodePrivateKeyInfo() function, which apparently expects a DER-encoded unencrypted PKCS#8 file, just like Java's PKCS8EncodedKeySpec.