2008-01-17

RSA signed streams

I'm writing an app that needs me to be able to write to a file and make sure that nobody changes it. The idea that I had was to create 2 classes:

  1. WritableSignedStream:

    Accepts a targetStream parameter + a privateKeyXml string. I add some space at the beginning of the target stream to reserve it for header info. You then write to the stream as normal (it reports a smaller Length than that of the target stream so your app is unaware of the header). When you close the stream it writes the public key to the stream + an RSA encrypted hash of the data part of the file.
  2. ReadableSignedStream:

    Accepts a sourceStream parameter in the constructor. Just before you read for the first time it will compute an MD5 hash of the data part of the file, and then compare it with the signed hash in the file (which I first decrypt using the stored public key).

These classes therefore provide two functions:

  • You can sign a stream, the app can check the stream was signed by you by inspecting the public key within it.
  • You can additionally pass a publicKeyXml to the ReadableSignedStream constructor and it will use that instead of the public key within the outer stream, this allows you to ensure nobody has altered your file in any way.

Anyway, to the point. I could always use RsaCryptoServiceProvider.Encrypt() to encrypt the MD5 hash, but whenever I tried to DeCrypt() I would get an exception telling me that my public key was a "Bad key".

It annoyed me for a while, but then I realised something pretty obvious really. The way RSA works is that you are supposed to Encrypt using the Public key and DeCrypt using the Private key. I was using it the wrong way around. The solution was simple....

(Signing)
RSACryptoServiceProvider RsaProvider;
RsaProvider = new RSACryptoServiceProvider();
RsaProvider.FromXmlString(privateKeyXml);

MD5 md5 = new MD5CryptoServiceProvider();
byte[] md5HashBuffer = md5.ComputeHash(dataStream);
byte[] signedMD5HashBuffer = GetSignedMd5Hash(md5HashBuffer);


private byte[] GetSignedMd5Hash(byte[] hashBuffer)
{
RSAPKCS1SignatureFormatter rsaFormatter = new RSAPKCS1SignatureFormatter();
rsaFormatter.SetKey(RsaProvider);
rsaFormatter.SetHashAlgorithm("MD5");
return rsaFormatter.CreateSignature(hashBuffer);
}


(Verifying)
MD5 md5 = new MD5CryptoServiceProvider();
byte[] currentMD5HashBufferForFile = md5.ComputeHash(dataStream);
RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider();
rsaProvider.FromXmlString(PublicKeyXml);


RSAPKCS1SignatureDeformatter rsaDeFormatter = new RSAPKCS1SignatureDeformatter();
rsaDeFormatter.SetKey(rsaProvider);
rsaDeFormatter.SetHashAlgorithm("MD5");
if (!rsaDeFormatter.VerifySignature(currentMD5HashBufferForFile, FileEncryptedMd5Hash))
throw new Exception("Someone has messed with the file");

I hope someone finds this useful :-)

1 comment:

Anonymous said...

Hi!
I was also trapped here. I can not get your code running. Especially, I can't resolve "FileEncryptedMd5Hash".
Additionally it is still unclear for me how to decrypt the signed hash with the public key only!
thanks anyway.

sysFred