C# Verifying data (signed with RSA private key) with public key - c#

using BouncyCastle and with help from a stackoverflow question I got this:
using System.Net.Sockets;
using System.Security.Cryptography;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
...
TcpClient client = new TcpClient("127.0.0.1", 1337);
NetworkStream stream = client.GetStream();
StreamWriter writer= new StreamWriter(stream);
StreamReader reader = new StreamReader(stream);
writer.WriteLine("hello");
writer.AutoFlush = true;
string response = Convert.FromBase64String(reader.ReadToEnd()).ToString();
RSACryptoServiceProvider RCP;
var x = new PemReader(File.OpenText(pubkey));
var y = (RsaKeyParameters)x.ReadObject();
RCP = (RSACryptoServiceProvider)RSACryptoServiceProvider.Create();
var pa = new RSAParameters();
pa.Modulus = y.Modulus.ToByteArray();
pa.Exponent = y.Exponent.ToByteArray();
RCP.ImportParameters(pa);
byte[] test = RCP.Decrypt(response, true);
Now, obviously Decrypt will fail, as since I'm trying to decrypt something which was signed (not "encrypted") and definately not by the same "key". I'm confused since I thought I should use a method like VerifyData(), but this returns a bool and takes arguments I'm not sure I have.
What I wish to accomplish is the C# equivalent of openssl rsautl -verify -inkey public.pem -pubin. That is, "decrypt" with the pubkey to verify the contents of said message.
Am I on the right track here?
Mik

Actually a decrypt of a signature should never fail unless you specified the wrong encoding parameters - a signature is simply the private-key encrypted hash of the data (typically SHA1); the only benefit of using the verify routine is that it decrypts the block and does the hash comparison work for you, if you wanted to be bloody minded you could instead do a decrypt operation with your public key and compare the hash bytes yourself.
I'm not actually sure what response is in the context of your program; you should only be passing the RSA data to it, if you're also feeding it the data that was signed in the first place you are doing it wrong - similarly, do check if that function actually accepts a string, in my experience BouncyCastle generally expects a byte[]
I'd recommend using SignerUtilities.GetSigner() - it takes a string arg to tell it what sort of signature you're using, I would imagine "SHA1WithRSA" to be right for you.

Related

Unable to verify OpenSSL C++ Signature in C#

I have a keypair generated using openSSL in C++ which i am using to sign a message for authentication over a C# server which strictly uses RSACryptoServiceProvider(no BouncyCastle etc.). I have used PKCS#1 SHA256 to generate signature.The signature is then transported in hexadecimal form along with the public key. The problem is the signature fails to verify on the server.
I have tried removing the header which says "-----BEGIN RSA PUBLIC KEY-----" and so. But no results yet.The c++ code for generating keypair and signature is:
RSA *keypair = NULL;
bne = BN_new();
int ret = 0;
ret = BN_set_word(bne, ea);
keypair = RSA_new();
RSA_generate_key_ex(keypair , 2048, bne, NULL);
SHA256((unsigned char*)msg, strlen(msg) + 1, hash);
RSA_sign(NID_sha256, hash, SHA256_DIGEST_LENGTH, sign,&signLen, keypair);
I have collected the public key in a character buffer using BIO and converted the signature data to hex string for transporting to c#. I however tried to replicate the server by writing a sample form application and the signature and public key which are verified on c++,i have passed them hardcoded as inputs to c# sample.But they also fail.
Can anyone help me out what are the possible reasons or areas of fallacy?
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSA.ImportParameters(rsParam);
UTF8Encoding encoding = new UTF8Encoding();
byte[] datax = encoding.GetBytes(data);
byte[] sigx = encoding.GetBytes(sig);
SHA256Managed sha256 = new SHA256Managed();
byte[] hash = sha256.ComputeHash(datax);
iRes= RSA.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA256"), sigx);
The 'data' is passed and also the 'sig' which is a hard-coded value,'rsParam have been created using hard-coded public key used to generate 'sig'. The code for conversion of public key to rsParam is:
pubkey = Convert.FromBase64String(pemString);
RSAParameters rsParam = RSA.ExportParameters(false);
rsParam .Modulus = pubkey;
RSA.ImportParameters(rsParam );
I wrote this code just to know how the basic C# cryptography system works,so that it can be of some help to research on the actual issue.
I found the answer,instead of signing with RSA_Sign,i used RSA_private_encrypt with RSA_PKCS1_PADDING as a parameter to sign the SHA256 hash and the signature got verified on the server end,the server code as i mentioned earlier was not transparent to me so this was just a brute-force approach and it worked. The sample C# code i had written still doesnt verify it but it is not of any concern to me as for now.

How to encrypt (RSA + PKCS1) with bouncycastle without a pem file

I'm trying to encrypt a string given a public key that I've retrieved from an API. The public key is plain text (base 64 encoded), something like:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCH9/o5IG0tu9VNYiJSltzV5ewK9TNoLeToSYkoH4lEytRM61AMeO6aBRZ3dsY1Czb+fgK6Q+M4ub/9jbcXIGmVLvTypdn+VW1dotXzMP5sfDgCUuhScjH7gqsXQAvaF5LxjLUbL5I5zCGXbPVwBCEyVhN0oNp3TtNKoMcF6AjNhwIDAQAB
I now want to encrypt a string using this public key. I've found some relevant code that reads from a PEM format, but obviously it won't work here:
byte[] dataToEncrypt = Encoding.UTF8.GetBytes(aString);
var encryptEngine = new Pkcs1Encoding(new RsaEngine());
using (var txtreader = new StringReader(key))
{
var keyParameter = (AsymmetricKeyParameter)new PemReader(txtreader).ReadObject();
encryptEngine.Init(true, keyParameter);
}
var encrypted = Convert.ToBase64String(encryptEngine.ProcessBlock(dataToEncrypt, 0, dataToEncrypt.Length));
keyParameter ends up being null. The documentation for Bouncycastle seems to be pretty scant, I don't really have an idea of what I should be using to get the right AsymmetricKeyParameter type.
I suppose I could write a PEM file from the key but feels a bit wasteful.
So the broader question is: How do I encrypt using PKSC1 padding when I have the public key as a string already?
The more precise question is: What AsymmetricKeyParameter type should I be using?
Thanks in advance.
EDIT
I've found a workaround using the native RSACryptoServiceProvider here. Still, would be good to know how to do this with BC.

AsymmetricKeyParameter as byte[]

I'm trying to do an ECDH key exchange using C# BouncyCastle.
I have been successful in creating the necessary AsymmetricCipherKeyPair objects and I'm also able to generate the shared key of the other partys public key.
However, to actually exchange the public key, I need it as a byte[] or at least anything I can turn into raw data, since the protocol I'm using to transport the keys between the parties wont take any BouncyCastle object.
X9ECParameters ecPars = NistNamedCurves.GetByName("P-521");
ECDomainParameters ecDomPars = new ECDomainParameters(ecPars.Curve, ecPars.G, ecPars.N, ecPars.H, ecPars.GetSeed());
IAsymmetricCipherKeyPairGenerator gen = GeneratorUtilities.GetKeyPairGenerator("ECDH");
gen.Init(new ECKeyGenerationParameters(ecDomPars, new SecureRandom()));
AsymmetricCipherKeyPair keyPair = gen.GenerateKeyPair();
IBasicAgreement keyAgreement = AgreementUtilities.GetBasicAgreement("ECDH");
keyAgreement.Init(keyPair.Private);
So what I'm needing here is the key value of keyPair.Public as a byte[].
I hope you understand where I'm heading and can help me.
If you have only an ECPublicKeyParameter 'pub' (i.e. from keyPair.Public), you can get the public point encoding:
byte[] data = pub.Q.GetEncoded();
At the receiving end:
ECCurve curve = ecDomPars.Curve;
ECPoint q = curve.DecodePoint(data);
ECPublicKeyParameter peerPub = new ECPublicKeyParameter(q, ecDomPars);
It's more typical to exchange certificates, or else you will have no assurance of whom you're "agreeing" with.
I'm a bit concerned that you might be "rolling your own crypto protocol"; if this is for a real application, please consider using an existing protocol, maybe just TLS.

Generate authenticated CMSEnvelopedData Messages with bouncycastle

I am trying to encrypt data with a password and store it inside a ASN.1 encoded CMS message (using C# and BouncyCastle 1.4)
The code I have seems to have two problems:
the data does not seem to be signed with a HMAC, so when I tamper with the encodedData (by enabling the commented out line), the decryption still succeeds.
when I decrypt the data I have tampered with, I get beck corrupted plain text. However only a two blocks of plaintext data are corrupted. This seems to suggest that the encryption does not actually use CBC mode.
(edit: disregard the second point, this is exactly how CBC is supposed to work)
This is what I am testing with:
public void TestMethod1()
{
byte[] data = new byte[1024]; // plaintext: a list of zeroes
CmsEnvelopedDataGenerator generator = new CmsEnvelopedDataGenerator();
CmsPbeKey encryptionKey = new Pkcs5Scheme2PbeKey("foo", new byte[] { 1, 2, 3 }, 2048);
generator.AddPasswordRecipient(encryptionKey, CmsEnvelopedDataGenerator.Aes256Cbc);
CmsProcessableByteArray cmsByteArray = new CmsProcessableByteArray(data);
CmsEnvelopedData envelopeData = generator.Generate(cmsByteArray, CmsEnvelopedDataGenerator.Aes256Cbc);
byte[] encodedData = envelopeData.GetEncoded();
// encodedData[500] = 10; // tamper with the data
RecipientID recipientID = new RecipientID();
CmsEnvelopedData decodedEnvelopeData = new CmsEnvelopedData(encodedData);
RecipientInformation recipient = decodedEnvelopeData.GetRecipientInfos().GetFirstRecipient(recipientID);
byte[] data2 = recipient.GetContent(encryptionKey);
CollectionAssert.AreEqual(data, data2);
}
What am I doing wrong? What would be the correct way to write this?
To add an HMAC to a CMS message, you would have to use a AuthenticatedData-structure.
I am not especially familiar with Bouncy Castle, but from a cursory look at the API, I would say that it does not support AuthenticatedData. In fact, it looks like it only supports SignedData for authentication.
So your options seems to be:
Use another library (or write your own code) to handle the AuthenticatedData-structure.
Calculate the HMAC and provide it in a non-standard way (in a proprietary Attribute or out-of-band).
Use SignedData with an RSA key pair instead.

How to Verify Signature, Loading PUBLIC KEY From PEM file?

I'm posting this in the hope it saves somebody else the hours I lost on this really stupid problem involving converting formats of public keys. If anybody sees a simpler solution or a problem, please let me know!
The eCommerce system I'm using sends me some data along with a signature. They also give me their public key in .pem format. The .pem file looks like this:
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDe+hkicNP7ROHUssGNtHwiT2Ew
HFrSk/qwrcq8v5metRtTTFPE/nmzSkRnTs3GMpi57rBdxBBJW5W9cpNyGUh0jNXc
VrOSClpD5Ri2hER/GcNrxVRP7RlWOqB1C03q4QYmwjHZ+zlM4OUhCCAtSWflB4wC
Ka1g88CjFwRw/PB9kwIDAQAB
-----END PUBLIC KEY-----
Here's the magic code to turn the above into an "RSACryptoServiceProvider" which is capable of verifying the signature. Uses the BouncyCastle library, since .NET apparently (and appallingly cannot do it without some major headaches involving certificate files):
RSACryptoServiceProvider thingee;
using (var reader = File.OpenText(#"c:\pemfile.pem"))
{
var x = new PemReader(reader);
var y = (RsaKeyParameters)x.ReadObject();
thingee = (RSACryptoServiceProvider)RSACryptoServiceProvider.Create();
var pa = new RSAParameters();
pa.Modulus = y.Modulus.ToByteArray();
pa.Exponent = y.Exponent.ToByteArray();
thingee.ImportParameters(pa);
}
And then the code to actually verify the signature:
var signature = ... //reads from the packet sent by the eCommerce system
var data = ... //reads from the packet sent by the eCommerce system
var sha = new SHA1CryptoServiceProvider();
byte[] hash = sha.ComputeHash(Encoding.ASCII.GetBytes(data));
byte[] bSignature = Convert.FromBase64String(signature);
///Verify signature, FINALLY:
var hasValidSig = thingee.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA1"), bSignature);
Potential problem: using Encoding.ASCII.GetBytes(data) is almost certainly the wrong way to get the hash. That means they can only send a hash which doesn't have any high bits set.
If this is in a "packet" you should get the raw data from the packet as a byte array. If it is represented as text, it should be in some encoded form - e.g. hex or base64. What does the hash look like?

Categories