Related
I'm having a problem with C# encrypting and decrypting using RSA. I have developed a web service that will be sent sensitive financial information and transactions. What I would like to be able to do is on the client side, Encrypt the certain fields using the clients RSA Private key, once it has reached my service it will decrypt with the clients public key.
At the moment I keep getting a "The data to be decrypted exceeds the maximum for this modulus of 128 bytes." exception. I have not dealt much with C# RSA cryptography so any help would be greatly appreciated.
This is the method i am using to generate the keys
private void buttonGenerate_Click(object sender, EventArgs e)
{
string secretKey = RandomString(12, true);
CspParameters param = new CspParameters();
param.Flags = CspProviderFlags.UseMachineKeyStore;
SecureString secureString = new SecureString();
byte[] stringBytes = Encoding.ASCII.GetBytes(secretKey);
for (int i = 0; i < stringBytes.Length; i++)
{
secureString.AppendChar((char)stringBytes[i]);
}
secureString.MakeReadOnly();
param.KeyPassword = secureString;
RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(param);
rsaProvider = (RSACryptoServiceProvider)RSACryptoServiceProvider.Create();
rsaProvider.KeySize = 1024;
string publicKey = rsaProvider.ToXmlString(false);
string privateKey = rsaProvider.ToXmlString(true);
Repository.RSA_XML_PRIVATE_KEY = privateKey;
Repository.RSA_XML_PUBLIC_KEY = publicKey;
textBoxRsaPrivate.Text = Repository.RSA_XML_PRIVATE_KEY;
textBoxRsaPublic.Text = Repository.RSA_XML_PUBLIC_KEY;
MessageBox.Show("Please note, when generating keys you must sign on to the gateway\n" +
" to exhange keys otherwise transactions will fail", "Key Exchange", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
Once i have generated the keys, i send the public key to the web service which stores it as an XML file.
Now i decided to test this so here is my method to encrypt a string
public static string RsaEncrypt(string dataToEncrypt)
{
string rsaPrivate = RSA_XML_PRIVATE_KEY;
CspParameters csp = new CspParameters();
csp.Flags = CspProviderFlags.UseMachineKeyStore;
RSACryptoServiceProvider provider = new RSACryptoServiceProvider(csp);
provider.FromXmlString(rsaPrivate);
ASCIIEncoding enc = new ASCIIEncoding();
int numOfChars = enc.GetByteCount(dataToEncrypt);
byte[] tempArray = enc.GetBytes(dataToEncrypt);
byte[] result = provider.Encrypt(tempArray, true);
string resultString = Convert.ToBase64String(result);
Console.WriteLine("Encrypted : " + resultString);
return resultString;
}
I do get what seems to be an encrypted value. In the test crypto web method that i created, i then take this encrypted data, try and decrypt the data using the clients public key and send this back in the clear. But this is where the exception is thrown. Here is my method responsible for this.
public string DecryptRSA(string data, string merchantId)
{
string clearData = null;
try
{
CspParameters param = new CspParameters();
param.Flags = CspProviderFlags.UseMachineKeyStore;
RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(param);
string merchantRsaPublic = GetXmlRsaKey(merchantId);
rsaProvider.FromXmlString(merchantRsaPublic);
byte[] asciiString = Encoding.ASCII.GetBytes(data);
byte[] decryptedData = rsaProvider.Decrypt(asciiString, false);
clearData = Convert.ToString(decryptedData);
}
catch (CryptographicException ex)
{
Log.Error("A cryptographic error occured trying to decrypt a value for " + merchantId, ex);
}
return clearData;
}
If anyone could help me that would be awesome, as i have said i have not done much with C# RSA encryption/decryption.
Allow me a bit of terminology. There is asymmetric encryption and there is digital signature.
Asymmetric encryption is about keeping confidentiality. Some sensitive data is transformed into something unreadable, save for the entity who knows the decryption key. The decryption key is necessarily the private key: if the decryption key is the public key, then everybody can decrypt the data (the public key is, well, public) and there is no confidentiality anymore. In asymmetric encryption, one encrypts with the public key and decrypts with the corresponding private key.
Digital signatures are meant to prove integrity. Someone computes a kind of keyed checksum over the data, in such a way that the link between the checksum and the data can be verified later. This is a "signature" only because the power to compute that checksum requires knowledge of something which is not public -- in plain words, signing uses the private key. Verification, however, should be doable by anybody, and thus use the public key.
A fair bit of confusion is implied by the fact that "the" RSA algorithm is actually a mathematical operation which can be declined into both an asymmetric encryption system, and a digital signature system. The confusion is further enhanced by the RSA standard, aka PKCS#1, which implicitly relies on how RSA digital signatures were first described, i.e. as a "reversed encryption" ("the signer encrypts the data with his private key"). Which leads to things like RSA signatures called "sha1WithRSAEncryption". This is quite unfortunate.
Therefore, you must first decide whether you want confidentiality or signatures. For confidentiality, for data sent from clients to the server, the server shall own a private key, and the clients use the server public key to encrypt the data. For signatures, each client shall have his own private key and use it to sign the data, and the server verifies the signatures. From your description I cannot tell what you are really after, thanks to the confusion I allude to above.
Also, there is something called authentication which may look like digital signatures, but is weaker. The point of signatures is than anybody can verify the signature. In particular, the signature can be shown to a judge and thus serve as legal weapon against the signer (the signature is legally binding -- at least if you do it right, and in the current state of regulations over electronic signatures, this is not easy). In most situations you only need something weaker and simpler, in which the server is convinced that it talks to the right client, but cannot afterwards convince anybody else that this client was really there. Any web site with user passwords is using such authentication.
With that being said...
RSA asymmetric encryption covers only short messages. For a 1024-bit RSA key (i.e. a key where the most important part, the "RSA modulus", is a big number with a value between 2^1023 and 2^1024, and encrypted messages will be of length 128 bytes), the maximum size of an encrypted message is 117 bytes (that's the actual source of your error message). When we want to send longer messages, we use an hybrid system, in which we only encrypt a small bunch of random bits (say 128 bits) and use that bunch as a key for a symmetric encryption system (e.g. AES) which can process much longer messages (and much faster, too).
RSA signatures, similarly, can be computed only on short messages, hence the PKCS#1 standard mandates that a signature is actually computed over a hash value. The hash value is the output of a specific hash function, which is computed over the message to sign. The hash function has a fixed-sized output (e.g. 256 bits for SHA-256) but accepts input messages of (almost) arbitrary length. Hash functions are public (there is no key in them) and, for proper security, must have some special properties. SHA-256 is, right now, not a bad choice. SHA-1 (a predecessor of SHA-256) has been proven to have some weaknesses and should be avoided. MD5 has (a kind-of uncle of SHA-1) has bigger weaknesses and shall not be used.
Proper use of asymmetric encryption, especially in an hybrid scheme, and digital signatures, is trickier than what the text above may suggest. It is awfully easy to get it wrong at some point, invisibly, i.e. the code will appear to work but will leak data useful for an attacker. The right way to use asymmetric encryption or digital signatures is to rely on existing, well-thought protocols. A protocol is an assembly of cryptographic elements into a coherent system, where leaks are taken care of. The prime example is TLS, also known as SSL. It is a protocol which ensures confidential data transmission, with integrity and authentication (possibly mutual authentication). The HTTPS protocol is a mixture of HTTP and SSL. The bright side is that HTTPS has existing implementations, notably in C#. The code which is easiest to implement and debug is the code which has already been implemented and debugged. So use HTTPS and you will live longer and happier.
I understand why you are asking the question. The problem is that RSA is not used like a typical block cypher (like AES or 3DES) that encrypts 8 bytes at a time, all day long. RSA is a math operation that returns the remainder of a division (the modulus). Back in grade school, when you learned long division, remember that the remainder can never be greater than the divisor:if you are dividing 20 by 7, your remainder is 6. No matter what integer you divide by 7, the remainder cannot be greater than six.
RSA math is the same way. For example, if you are using a 1024-bit RSA public key, the remainder can never be greater than 2^1024, which is only 128 bytes. So you can only encrypt 128 bytes at a time with this key. (That's one reason we measure the size of RSA keys by the number of bits.)
Technically you could use this RSA key in a loop to encrypt 128 byte chunks of your data at a time. In reality, we almost never do this because RSA math is BIG and SLOW. Instead, we use what is called "two-phase" encryption. We use RSA to encrypt only a short "session key", and then use that session key in a fast symmetric-keyed block cypher (like AES) to encrypt the actual data.
The whole protocol is:
Obtain the RSA public key of your destination. This is often delivered embedded in a certificate; if it is, be sure to validate the certificate to make sure the key is genuine. Let's say the RSA key is 2048 bits long.
Generate a cryptographically strong pseudo-random number to use as a key for the block cypher (you need 256 bits as the key for AES-256, for example.) Note that 256 < 2048, the max that RSA-2048 can encrypt at once. We call this random number the "session key".
Encrypt the session key using the RSA 2048-bit public key. It will give you 2048 bits of encrypted session key. Note that this operation is very slow.
Encrypt all the secret data using AES-256, using the session key. Note that this is much faster than step 3.
Bundle the public key ID from the certificate, the RSA encrypted session key, and the AES encrypted data together. I'd also tag it with a format identifier and version number, so you know what format it is in and how to decrypt it.
Send the bundle to the destination.
At the destination you use the format identifier and version to take apart the bundle.
Retrieve the private key whose identity is in the public key ID field.
Use this private key in RSA to decrypt the session key.
Use the session key in AES to decrypt the data.
If you are going to do this, you should know that it is exactly what the CMS (PKCS#7) format is for. I would encourage you to learn about the standard and adopt it, rather than trying to invent your own format. Microsoft's CSP supports it, so it should be easy.
If you don't follow a standard you will have to make your own decisions about things like "what format should the AES key bits be in in the RSA encryption process?" More likely, you would almost certainly make security mistakes, weakening your system. Besides, you will find that tools such as the CSP will be very difficult to work with if you don't follow a standard.
In DecryptRSA, is "data" base 64 encoded? If it is, you have to undo that first.
Honestly I think you shouldn't implement that routine yourself to protect "sensitive financial information", unless you have a lot of experience with cryptography. There are too many ways to make errors. Better use some ready solution - maybe SSL and certificates, or just PGP or GnuPG?
RSA is primarily used to validate secure hashes of data - rather than encrypting the data itself. So, given a large blob of data, you might use SHA512 to create a hash of that data, then use RSA to sign that hash.
You'll want to use a symmetric encryption algorithm for large blocks of data - something like AES or 3DES.
Managing secure transactions isn't easy and really ought to be left to those guys that spend all day and night thinking about it. If you're exposing the service as over the web, just use SSL which already encrypts and secures your data.
First decide what you are trying to protect against. If you "encrypt" something using the private key, anyone can "decrypt" it with the public key, since the public key is - well - public.
If you actually want to sign it, you should (as Paul Alexander explains) sign a hash with the private key which can then be verified on the server.
To encrypt data using RSA you should first generate a random symmetric key (f.x. AES), encrypt the key using a public key and encrypt the data using the symmetric key. You can then transmit the encrypted key together with the encrypted data to the holder of the private key, who can then first decrypt the encrypted key with the private key and then decrypt the data with the symmetric key.
You might also consider using SSL, but remember to carefully consider the authentication. You will probably need client authentication and have to decide which certificates to trust (you should not just blindly accept any certificate issued by Verisign).
I have an API that decrypts data. This API receives only key (bytes of an AES key). This API does not receive initialization vector (It uses vector of zeros as an initialization vector).
I'm receiving from 3rd party an encrypted data. This data was encrypted to AES 256 using a specific key and initialization vector.
I have the key and the initialization vector. Is there a way to decrypt this data using the API? or, in other words, if the key and the initialization vector are constants and I have them both, can I create a key that will allow me to decrypt this data with a {0, 0, 0, ...} initialization vector?
Thanks.
Yes it is possible to decrypt ... no not the way you described
AES is a block cipher
most of the time the cipher isn't used as it is, but it is put into a special mode of operation (this is what your API probably is not capable of)
you will have to implement the decryption routine around the cipher and use your AES-API just as a crypto-primitive
for example, the decryption of AES-CBC using a generic AES implementation
split ciphertext into 128 bit blocks and number them from index 1 onward
prepend the IV as cypher_block 0
now to obtain the plaintext we can define a function around your API function
plaintext_block[i] = cypher_block[i-1] XOR aes_decrypt(cypher_block[i],key)
as you can see, you can obtain all plaintext blocks from index 1 onward ...
once you have obtained all plaintext blocks you will probably want to strip padding, but that's another story ...
Cryptography gurus please help.
I've learned that encryption key with symmetric algorithms (e.g. AES) should be derived from password via the PBKDF2 function, using the random salt in each encryption. I've also learned that IV should not be hard-coded, or directly bound to (derived from) password string or encryption key. Until now I was generating both key derivation salt and IV randomly, 16 bytes each for my AES-256 encryption, and storing them along with encrypted payload.
Now I'm thinking random-generation of IV is redundant, if I use random salt, as I can derive both key and IV from password string with that salt. Or maybe I shouldn't?
So my question is ultimately this:
Can I derive initialization vector from password (as I do with key), or should I generate random IV each time, given the fact that I use random salt in each encryption?
So can I use the below C# code?
// Derive key and initialization vector from password:
// ---> NOTE: _salt is random 16 bytes in each encryption.
byte[] key, iv;
using (Rfc2898DeriveBytes derivedBytes = new Rfc2898DeriveBytes(password, _salt, _iterations))
{
key = derivedBytes.GetBytes(32);
iv = derivedBytes.GetBytes(16);
}
Yes you can use it that way, as long as you never ever use the same salt for the same password (even in time) to calculate the key and IV. The IV only has to be unique when you encrypt with the same key, and you would calculate a new key each time. In principle you could even use an all zero IV, as the key is never repeated, but you are better off using a derived one.
Note that if one of your colleagues decides that PasswordDeriveBytes - the broken implementation of PBKDF1 from Microsoft - would be better suited for the task, then you may very well be vulnerable to all kinds of attacks. This is just an example what can go wrong if your security margins are tight...
Fully random IV's should certainly be preferred.
What do you mean " using the random salt in each encryption"? It is best to derive the salt and IV randomly, such as the output from a cryptography standard random number generator and store it with the derived bytes. Generate a new IV and salt for each password.
Why from a cryptography standard RNG? Deriving from the password means any weakness in the derive bytes function is reflected in both the bytes and the IV. It is not difficult in modern programming languages to generate it from a RNG and using a RNG ensures the IV for new passwords encrypted is not predictable. There are probably better reasons but I am drawing a blank.
The more links there are between different parts of any crypto system then the easier it will be for any attacker to use those links as a back door from one part of the system to another. Remember that the IV is sent in the clear while the key must be kept secret, so any sort of link between them is a huge risk to take.
Use Rfc2898DeriveBytes to generate your key and use a good crypto RNG to generate the IV. Remember that the attacker will see the IV so there is no need to go through the full RFC 2898 process. Use the standard crypto RNG for the IV that will probably be faster than the RFC 2898 process because it does not have the iterations.
The most important part of semantic security for the initialization vector when using AES-CBC is that it should not be predictable.
With your suggested implementation a given key will always have the same initialization vector, but you wouldn't be using the same keys because of your 128 bit salt. Seems pretty unpredictable, that said, it's not a best practice, and generally when you do something clever to save 16 bytes of space your are losing some security of some sort or opening yourself up to some unknown attack vector.
I think you should use RNG and take the space 16 byte hit, being conservative is the name of the game when dealing with encryption. There are other things such as authenticated encryption that you should probably look into as well, I have an example implementation on codereview.
Ultimately there are other things that are important that provide additional overhead beyond the iv for security such as authenticated encryption, versioning, and keyrotation, and there really haven't been any high level encryption frameworks for C#. I've been working a C# implemenation of Google's Keyczar framework. You can follow it, if you like, on github Keyczar-dotnet. It's pretty much feature complete, and has 90% test coverage, but conservatively, I wouldn't recommend using it until it gets officially accepted as part of the project, and then will likely have a larger group of eyes on it in the future.
I'm having a problem with C# encrypting and decrypting using RSA. I have developed a web service that will be sent sensitive financial information and transactions. What I would like to be able to do is on the client side, Encrypt the certain fields using the clients RSA Private key, once it has reached my service it will decrypt with the clients public key.
At the moment I keep getting a "The data to be decrypted exceeds the maximum for this modulus of 128 bytes." exception. I have not dealt much with C# RSA cryptography so any help would be greatly appreciated.
This is the method i am using to generate the keys
private void buttonGenerate_Click(object sender, EventArgs e)
{
string secretKey = RandomString(12, true);
CspParameters param = new CspParameters();
param.Flags = CspProviderFlags.UseMachineKeyStore;
SecureString secureString = new SecureString();
byte[] stringBytes = Encoding.ASCII.GetBytes(secretKey);
for (int i = 0; i < stringBytes.Length; i++)
{
secureString.AppendChar((char)stringBytes[i]);
}
secureString.MakeReadOnly();
param.KeyPassword = secureString;
RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(param);
rsaProvider = (RSACryptoServiceProvider)RSACryptoServiceProvider.Create();
rsaProvider.KeySize = 1024;
string publicKey = rsaProvider.ToXmlString(false);
string privateKey = rsaProvider.ToXmlString(true);
Repository.RSA_XML_PRIVATE_KEY = privateKey;
Repository.RSA_XML_PUBLIC_KEY = publicKey;
textBoxRsaPrivate.Text = Repository.RSA_XML_PRIVATE_KEY;
textBoxRsaPublic.Text = Repository.RSA_XML_PUBLIC_KEY;
MessageBox.Show("Please note, when generating keys you must sign on to the gateway\n" +
" to exhange keys otherwise transactions will fail", "Key Exchange", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
Once i have generated the keys, i send the public key to the web service which stores it as an XML file.
Now i decided to test this so here is my method to encrypt a string
public static string RsaEncrypt(string dataToEncrypt)
{
string rsaPrivate = RSA_XML_PRIVATE_KEY;
CspParameters csp = new CspParameters();
csp.Flags = CspProviderFlags.UseMachineKeyStore;
RSACryptoServiceProvider provider = new RSACryptoServiceProvider(csp);
provider.FromXmlString(rsaPrivate);
ASCIIEncoding enc = new ASCIIEncoding();
int numOfChars = enc.GetByteCount(dataToEncrypt);
byte[] tempArray = enc.GetBytes(dataToEncrypt);
byte[] result = provider.Encrypt(tempArray, true);
string resultString = Convert.ToBase64String(result);
Console.WriteLine("Encrypted : " + resultString);
return resultString;
}
I do get what seems to be an encrypted value. In the test crypto web method that i created, i then take this encrypted data, try and decrypt the data using the clients public key and send this back in the clear. But this is where the exception is thrown. Here is my method responsible for this.
public string DecryptRSA(string data, string merchantId)
{
string clearData = null;
try
{
CspParameters param = new CspParameters();
param.Flags = CspProviderFlags.UseMachineKeyStore;
RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(param);
string merchantRsaPublic = GetXmlRsaKey(merchantId);
rsaProvider.FromXmlString(merchantRsaPublic);
byte[] asciiString = Encoding.ASCII.GetBytes(data);
byte[] decryptedData = rsaProvider.Decrypt(asciiString, false);
clearData = Convert.ToString(decryptedData);
}
catch (CryptographicException ex)
{
Log.Error("A cryptographic error occured trying to decrypt a value for " + merchantId, ex);
}
return clearData;
}
If anyone could help me that would be awesome, as i have said i have not done much with C# RSA encryption/decryption.
Allow me a bit of terminology. There is asymmetric encryption and there is digital signature.
Asymmetric encryption is about keeping confidentiality. Some sensitive data is transformed into something unreadable, save for the entity who knows the decryption key. The decryption key is necessarily the private key: if the decryption key is the public key, then everybody can decrypt the data (the public key is, well, public) and there is no confidentiality anymore. In asymmetric encryption, one encrypts with the public key and decrypts with the corresponding private key.
Digital signatures are meant to prove integrity. Someone computes a kind of keyed checksum over the data, in such a way that the link between the checksum and the data can be verified later. This is a "signature" only because the power to compute that checksum requires knowledge of something which is not public -- in plain words, signing uses the private key. Verification, however, should be doable by anybody, and thus use the public key.
A fair bit of confusion is implied by the fact that "the" RSA algorithm is actually a mathematical operation which can be declined into both an asymmetric encryption system, and a digital signature system. The confusion is further enhanced by the RSA standard, aka PKCS#1, which implicitly relies on how RSA digital signatures were first described, i.e. as a "reversed encryption" ("the signer encrypts the data with his private key"). Which leads to things like RSA signatures called "sha1WithRSAEncryption". This is quite unfortunate.
Therefore, you must first decide whether you want confidentiality or signatures. For confidentiality, for data sent from clients to the server, the server shall own a private key, and the clients use the server public key to encrypt the data. For signatures, each client shall have his own private key and use it to sign the data, and the server verifies the signatures. From your description I cannot tell what you are really after, thanks to the confusion I allude to above.
Also, there is something called authentication which may look like digital signatures, but is weaker. The point of signatures is than anybody can verify the signature. In particular, the signature can be shown to a judge and thus serve as legal weapon against the signer (the signature is legally binding -- at least if you do it right, and in the current state of regulations over electronic signatures, this is not easy). In most situations you only need something weaker and simpler, in which the server is convinced that it talks to the right client, but cannot afterwards convince anybody else that this client was really there. Any web site with user passwords is using such authentication.
With that being said...
RSA asymmetric encryption covers only short messages. For a 1024-bit RSA key (i.e. a key where the most important part, the "RSA modulus", is a big number with a value between 2^1023 and 2^1024, and encrypted messages will be of length 128 bytes), the maximum size of an encrypted message is 117 bytes (that's the actual source of your error message). When we want to send longer messages, we use an hybrid system, in which we only encrypt a small bunch of random bits (say 128 bits) and use that bunch as a key for a symmetric encryption system (e.g. AES) which can process much longer messages (and much faster, too).
RSA signatures, similarly, can be computed only on short messages, hence the PKCS#1 standard mandates that a signature is actually computed over a hash value. The hash value is the output of a specific hash function, which is computed over the message to sign. The hash function has a fixed-sized output (e.g. 256 bits for SHA-256) but accepts input messages of (almost) arbitrary length. Hash functions are public (there is no key in them) and, for proper security, must have some special properties. SHA-256 is, right now, not a bad choice. SHA-1 (a predecessor of SHA-256) has been proven to have some weaknesses and should be avoided. MD5 has (a kind-of uncle of SHA-1) has bigger weaknesses and shall not be used.
Proper use of asymmetric encryption, especially in an hybrid scheme, and digital signatures, is trickier than what the text above may suggest. It is awfully easy to get it wrong at some point, invisibly, i.e. the code will appear to work but will leak data useful for an attacker. The right way to use asymmetric encryption or digital signatures is to rely on existing, well-thought protocols. A protocol is an assembly of cryptographic elements into a coherent system, where leaks are taken care of. The prime example is TLS, also known as SSL. It is a protocol which ensures confidential data transmission, with integrity and authentication (possibly mutual authentication). The HTTPS protocol is a mixture of HTTP and SSL. The bright side is that HTTPS has existing implementations, notably in C#. The code which is easiest to implement and debug is the code which has already been implemented and debugged. So use HTTPS and you will live longer and happier.
I understand why you are asking the question. The problem is that RSA is not used like a typical block cypher (like AES or 3DES) that encrypts 8 bytes at a time, all day long. RSA is a math operation that returns the remainder of a division (the modulus). Back in grade school, when you learned long division, remember that the remainder can never be greater than the divisor:if you are dividing 20 by 7, your remainder is 6. No matter what integer you divide by 7, the remainder cannot be greater than six.
RSA math is the same way. For example, if you are using a 1024-bit RSA public key, the remainder can never be greater than 2^1024, which is only 128 bytes. So you can only encrypt 128 bytes at a time with this key. (That's one reason we measure the size of RSA keys by the number of bits.)
Technically you could use this RSA key in a loop to encrypt 128 byte chunks of your data at a time. In reality, we almost never do this because RSA math is BIG and SLOW. Instead, we use what is called "two-phase" encryption. We use RSA to encrypt only a short "session key", and then use that session key in a fast symmetric-keyed block cypher (like AES) to encrypt the actual data.
The whole protocol is:
Obtain the RSA public key of your destination. This is often delivered embedded in a certificate; if it is, be sure to validate the certificate to make sure the key is genuine. Let's say the RSA key is 2048 bits long.
Generate a cryptographically strong pseudo-random number to use as a key for the block cypher (you need 256 bits as the key for AES-256, for example.) Note that 256 < 2048, the max that RSA-2048 can encrypt at once. We call this random number the "session key".
Encrypt the session key using the RSA 2048-bit public key. It will give you 2048 bits of encrypted session key. Note that this operation is very slow.
Encrypt all the secret data using AES-256, using the session key. Note that this is much faster than step 3.
Bundle the public key ID from the certificate, the RSA encrypted session key, and the AES encrypted data together. I'd also tag it with a format identifier and version number, so you know what format it is in and how to decrypt it.
Send the bundle to the destination.
At the destination you use the format identifier and version to take apart the bundle.
Retrieve the private key whose identity is in the public key ID field.
Use this private key in RSA to decrypt the session key.
Use the session key in AES to decrypt the data.
If you are going to do this, you should know that it is exactly what the CMS (PKCS#7) format is for. I would encourage you to learn about the standard and adopt it, rather than trying to invent your own format. Microsoft's CSP supports it, so it should be easy.
If you don't follow a standard you will have to make your own decisions about things like "what format should the AES key bits be in in the RSA encryption process?" More likely, you would almost certainly make security mistakes, weakening your system. Besides, you will find that tools such as the CSP will be very difficult to work with if you don't follow a standard.
In DecryptRSA, is "data" base 64 encoded? If it is, you have to undo that first.
Honestly I think you shouldn't implement that routine yourself to protect "sensitive financial information", unless you have a lot of experience with cryptography. There are too many ways to make errors. Better use some ready solution - maybe SSL and certificates, or just PGP or GnuPG?
RSA is primarily used to validate secure hashes of data - rather than encrypting the data itself. So, given a large blob of data, you might use SHA512 to create a hash of that data, then use RSA to sign that hash.
You'll want to use a symmetric encryption algorithm for large blocks of data - something like AES or 3DES.
Managing secure transactions isn't easy and really ought to be left to those guys that spend all day and night thinking about it. If you're exposing the service as over the web, just use SSL which already encrypts and secures your data.
First decide what you are trying to protect against. If you "encrypt" something using the private key, anyone can "decrypt" it with the public key, since the public key is - well - public.
If you actually want to sign it, you should (as Paul Alexander explains) sign a hash with the private key which can then be verified on the server.
To encrypt data using RSA you should first generate a random symmetric key (f.x. AES), encrypt the key using a public key and encrypt the data using the symmetric key. You can then transmit the encrypted key together with the encrypted data to the holder of the private key, who can then first decrypt the encrypted key with the private key and then decrypt the data with the symmetric key.
You might also consider using SSL, but remember to carefully consider the authentication. You will probably need client authentication and have to decide which certificates to trust (you should not just blindly accept any certificate issued by Verisign).
What is the difference between using Rfc2898DeriveBytes and just using Encoding.ASCII.GetBytes(string object);?
I have had relative success with either approach, the former is a more long winded approach where as the latter is simple and to the point. Both seem to allow you to do the same thing eventually but I am struggling to the see the point in using the former over the latter.
The basic concept I have been able to grasp is that you can convert string passwords into
byte arrays to be used for e.g a symmetric encryption class, AesManaged. Via the RFC class but you get to use salt values and password when creating your rfc object. I assume its more secure but still thats an uneducated guess at best! Also that it allows you to return byte arrays of a certain size, well something like that.
Here are a few examples to show you where I am coming from:
byte[] myPassinBytes = Encoding.ASCII.GetBytes("some password");
or
string password = "P#%5w0r]>";
byte[] saltArray = Encoding.ASCII.GetBytes("this is my salt");
Rfc2898DeriveBytes rfcKey = new Rfc2898DeriveBytes(password, saltArray);
The 'rfcKey' object can now be used towards setting up the the .Key or .IV properties
on a symmetric encryption algorithm class.
ie.
RijndaelManaged rj = new RijndaelManaged ();
rj.Key = rfcKey.Getbytes(rj.KeySize / 8);
rj.IV = rfcKey.Getbytes(rj.Blocksize / 8);
'rj' should be ready to go !
The confusing part ... so rather than using the 'rfcKey' object can I not just use my
'myPassInBytes' array to help set-up my 'rj' object?
I have tried doing this in VS2008 and the immediate answer is NO. But have you guys got a better educated answer as to why the RFC class is used over the other alternative I have mentioned above?
You really, really do not want to use a user password directly as a crypto key, especially with AES.
Rfc2898DeriveBytes is an implementation of PBKDF2. What it does is repeatedly hash the user password along with the salt. This has multiple benefits:
Firstly, you can use arbitrarily sized passwords - AES only supports specific key sizes.
Secondly, the addition of the salt means that you can use the same passphrase to generate multiple different keys (assuming the salt is not a constant, as it is in your example). This is important for key separation; reusing keys in different contexts is one of the most common ways cryptographic systems are broken.
The multiple iterations (1000 by default) slow down password guessing attacks. Consider someone who is trying to guess your AES key. If you just used the password, this would be straightforward - just try each possible password as the key. On the other hand, with PBKDF2, the attacker first has to perform 1000 hash iterations for each password guess. So while it slows down a user only slightly, it has a disproportionate effect on an attacker. (In fact it's quite common to use much higher iteration counts; 10000 is commonly recommended).
It also means the final output key is uniformly distributed. If you used the password, for instance, typically 16 out of 128 bits of the key would be 0 (the high ASCII bit). That right there immediately makes keysearch 65536 times easier than it should be, even ignoring the password guessing.
Finally, AES has specific vulnerabilities with related key attacks. Related key attacks are possible when an attacker knows some data encrypted with several keys, and there is some known (or guessed) relation between them. For instance, if you encrypted data with both a password-key of "My AES key sucks" (16 bytes, for AES-128) and with "MY AES KEY SUCKS", a related key attack might be possible. The currently best known attacks do not actually allow breaking the full AES in this way, but they have been getting progressively better over time - just last week a new attack was published that breaks 13 rounds (out of 14 total) of AES-256 using a related key attack. It would be profoundly unwise to rely on such attacks not getting better over time.