I have to encrypt and decrypt a large string using RSA public key and private key. I have managed to encrypt a larger text using the following sample code
public static string Encrypt(string publicKey, string data, RsaKeyLengths length)
{
// full array of bytes to encrypt
byte[] bytesToEncrypt;
// worker byte array
byte[] block;
// encrypted bytes
byte[] encryptedBytes;
// length of bytesToEncrypt
var dataLength = 0;
// number of bytes in key
var keySize = 0;
// maximum block length to encrypt
var maxLength = 0;
// how many blocks must we encrypt to encrypt entire message?
var iterations = 0;
// the encrypted data
var encryptedData = new StringBuilder();
// instantiate the crypto provider with the correct key length
var rsaCryptoServiceProvider = new RSACryptoServiceProvider((int)length);
// initialize the RSA object from the given public key
rsaCryptoServiceProvider.FromXmlString(publicKey);
// convert data to byte array
bytesToEncrypt = Encoding.Unicode.GetBytes(data);
// get length of byte array
dataLength = bytesToEncrypt.Length;
// convert length of key from bits to bytes
keySize = (int)length / 8;
// .NET RSACryptoServiceProvider uses SHA1 Hash function
// use this to work out the maximum length to encrypt per block
maxLength = ((keySize - 2) - (2 * SHA1.Create().ComputeHash(bytesToEncrypt).Length));
// how many blocks do we need to encrypt?
iterations = dataLength / maxLength;
// encrypt block by block
for (int index = 0; index <= iterations; index++)
{
// is there more than one full block of data left to encrypt?
if ((dataLength - maxLength * index) > maxLength)
{
block = new byte[maxLength];
}
else
{
block = new byte[dataLength - maxLength * index];
}
// copy the required number of bytes from the array of bytes to encrypt to our worker array
Buffer.BlockCopy(bytesToEncrypt, maxLength * index, block, 0, block.Length);
// encrypt the current worker array block of bytes
encryptedBytes = rsaCryptoServiceProvider.Encrypt(block, true);
// RSACryptoServiceProvider reverses the order of encrypted bytesToEncrypt after encryption and before decryption.
// Undo this reversal for compatibility with other implementations
Array.Reverse(encryptedBytes);
// convert to base 64 string
encryptedData.Append(Convert.ToBase64String(encryptedBytes));
}
return encryptedData.ToString();
}
Then I tried to decrypt the larger text using the following code
/// <summary>
/// Encrypt an arbitrary string of data under the supplied public key
/// </summary>
/// <param name="publicKey">The public key to encrypt under</param>
/// <param name="data">The data to encrypt</param>
/// <param name="length">The bit length or strength of the public key: 1024, 2048 or 4096 bits. This must match the
/// value actually used to create the publicKey</param>
/// <returns></returns>
public static string Decrypt(string privateKey, string data, RsaKeyLengths length)
{
// full array of bytes to encrypt
byte[] bytesToDecrypt;
// worker byte array
byte[] block;
// encrypted bytes
byte[] decryptedBytes;
// length of bytesToEncrypt
var dataLength = 0;
// number of bytes in key
var keySize = 0;
// maximum block length to encrypt
var maxLength = 0;
// how many blocks must we encrypt to encrypt entire message?
var iterations = 0;
// the encrypted data
var decryptedData = new StringBuilder();
// instantiate the crypto provider with the correct key length
var rsaCryptoServiceProvider = new RSACryptoServiceProvider((int)length);
// initialize the RSA object from the given public key
rsaCryptoServiceProvider.FromXmlString(privateKey);
// convert data to byte array
bytesToDecrypt = Encoding.Unicode.GetBytes(data);
// get length of byte array
dataLength = bytesToDecrypt.Length;
// convert length of key from bits to bytes
keySize = (int)length / 8;
// .NET RSACryptoServiceProvider uses SHA1 Hash function
// use this to work out the maximum length to encrypt per block
//maxLength = ((keySize - 2) - (2 * SHA1.Create().ComputeHash(bytesToDecrypt).Length));
maxLength = ((keySize / 8) % 3 != 0) ?
(((keySize / 8) / 3) * 4) + 4 : ((keySize / 8) / 3) * 4; ;
// how many blocks do we need to encrypt?
iterations = dataLength / maxLength;
// encrypt block by block
for (int index = 0; index <= iterations; index++)
{
// is there more than one full block of data left to encrypt?
if ((dataLength - maxLength * index) > maxLength)
{
block = new byte[maxLength];
}
else
{
block = new byte[dataLength - maxLength * index];
}
// copy the required number of bytes from the array of bytes to encrypt to our worker array
Buffer.BlockCopy(bytesToDecrypt, maxLength * index, block, 0, block.Length);
// encrypt the current worker array block of bytes
decryptedBytes = rsaCryptoServiceProvider.Decrypt(block, true);
// RSACryptoServiceProvider reverses the order of encrypted bytesToEncrypt after encryption and before decryption.
// Undo this reversal for compatibility with other implementations
Array.Reverse(decryptedBytes);
// convert to base 64 string
decryptedData.Append(Convert.ToBase64String(decryptedBytes));
}
return decryptedData.ToString();
}
Actually the encryption is going on smoothly. No problem with it. But when I am trying to decrypt it. I am getting the following exception
Unhandled Exception:
System.Security.Cryptography.CryptographicException: Error occurred
while decoding OAEP padding.
Can anybody help me out?
Use stream cipher instead and only encrypt that key for this cipher with RSA. This may help if you need RSA because of its public-private key logic and want to use the different keys for encryption and decryption. With stream cipher you would be able encrypt and decrypt gigabytes of data no problem.
RSA is normally not used for really big amounts of data.
Maybe a bit late, but I found http://tekaris.com/blog/2013/02/08/encrypting-large-data-with-asymetric-rsacryptoserviceprovider/ helpful. The trick here obviously is to split the data, encrypt and re-join the data.
Related
I have a server (python) and a client (c#), I need to communicate between them temporarily using assymetric rsa cryptography.
When I connect to the server as the client, I send him my public key and he send me his. I use at the server the rsa library and I get there the server's public key parameters {n,e} I send those and receive them with a space between them. I seperate them and convert the modulus into a BigInteger using this function:
public static BigInteger GetBigInteger(string number)
{
BigInteger bigNum = new BigInteger();
for (int i = number.Length; i > 0; i--)
{
bigNum *= 10;
bigNum += (int) number[number.Length-i];
}
return bigNum;
}
public static void Connect(IPAddress ipAddress, int port)
{
try
{
string[] message;
byte[] data = new byte[1024];
srvr.Receive(data); //Recieve the server's public key.
int length = int.Parse(Encoding.ASCII.GetString(data.Take(4).ToArray()));
message = Encoding.ASCII.GetString(data.Skip(4).Take(length).ToArray()).Split(' ') ;
RSACryptoServiceProvider RSAserver = new RSACryptoServiceProvider(1024);
RSAParameters par = new RSAParameters();
par.Modulus = GetBigInteger(message[0]).ToByteArray(); // Saves the server's public key.
par.Exponent = new byte[] { 1, 0, 1 }; // Saves the server's public key.
RSAserver.ImportParameters(par);
addresseeKey = RSAserver.ToXmlString(false);
...
}
...
}
An exception is thrown on the ImportParameters line that says: "The parameter is incorrect" .
What's wrong?
BigInteger.ToByteArray() exports the data in little-endian order. RSAParameters wants all of its data in big-endian order.
There's also an issue that the modulus value should have its most significant bit set, and therefore BigInteger.ToByteArray() needs to add in a padding byte to prevent the number from being reinterpreted as being negative, so you'll need to trim that off.
Assuming that your python export is representing the modulus as a base-10 positive integer, your code will work (though BigInteger.Parse(string) would be more efficient) once you align the data properly.
byte[] tmp = GetBigInteger(message[0]).ToByteArray();
// Array.Resize (to a smaller number) trims off the high index values,
// so it's slightly more compact code to do this before the resize.
if (tmp.Length > 0 && tmp[tmp.Length-1] == 0)
Array.Resize(ref tmp, tmp.Length - 1);
Array.Reverse(tmp);
par.Modulus = tmp;
http://aes.online-domain-tools.com/
I am trying to replicate this encryption with c# using CBC but not getting the same results no matter what I try.
So far my code:
private byte[] hex2bytes(string s)
{
return Enumerable.Range(0, s.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(s.Substring(x, 2), 16))
.ToArray();
}
private AesCryptoServiceProvider GetProvider(byte[] key)
{
AesCryptoServiceProvider result = new AesCryptoServiceProvider();
result.BlockSize = 128;
result.KeySize = 128;
result.Mode = CipherMode.CBC;
result.Padding = PaddingMode.PKCS7;
result.GenerateIV();
//result.IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
result.IV = hex2bytes("ad77d666311839f5665aeb2e42f64542");
byte[] RealKey = GetKey(key, result);
_key = Encoding.ASCII.GetString(RealKey);
result.Key = RealKey;
// result.IV = RealKey;
return result;
}
private byte[] GetKey(byte[] suggestedKey, SymmetricAlgorithm p)
{
byte[] kRaw = suggestedKey;
List<byte> kList = new List<byte>();
for (int i = 0; i < p.LegalKeySizes[0].MinSize; i += 8)
{
kList.Add(kRaw[(i / 8) % kRaw.Length]);
}
byte[] k = kList.ToArray();
return k;
}
/// <summary>
/// Encrpyts the sourceString, returns this result as an Aes encrpyted, BASE64 encoded string
/// </summary>
/// <param name="plainSourceStringToEncrypt">a plain, Framework string (ASCII, null terminated)</param>
/// <param name="passPhrase">The pass phrase.</param>
/// <returns>
/// returns an Aes encrypted, BASE64 encoded string
/// </returns>
public string EncryptString(string plainSourceStringToEncrypt, string passPhrase)
{
//Set up the encryption objects
using (AesCryptoServiceProvider acsp = GetProvider(Encoding.UTF8.GetBytes(passPhrase)))
{
byte[] sourceBytes = Encoding.UTF8.GetBytes(plainSourceStringToEncrypt);
ICryptoTransform ictE = acsp.CreateEncryptor();
//Set up stream to contain the encryption
MemoryStream msS = new MemoryStream();
//Perform the encrpytion, storing output into the stream
CryptoStream csS = new CryptoStream(msS, ictE, CryptoStreamMode.Write);
csS.Write(sourceBytes, 0, sourceBytes.Length);
csS.FlushFinalBlock();
//sourceBytes are now encrypted as an array of secure bytes
byte[] encryptedBytes = msS.ToArray(); //.ToArray() is important, don't mess with the buffer
var x = BitConverter.ToString(encryptedBytes);
//return the encrypted bytes as a BASE64 encoded string
return Convert.ToBase64String(encryptedBytes);
}
}
/// <summary>
/// Decrypts a BASE64 encoded string of encrypted data, returns a plain string
/// </summary>
/// <param name="base64StringToDecrypt">an Aes encrypted AND base64 encoded string</param>
/// <param name="passphrase">The passphrase.</param>
/// <returns>returns a plain string</returns>
public string DecryptString(string base64StringToDecrypt, string passphrase)
{
//Set up the encryption objects
using (AesCryptoServiceProvider acsp = GetProvider(Encoding.UTF8.GetBytes(passphrase)))
{
byte[] RawBytes = Convert.FromBase64String(base64StringToDecrypt);
ICryptoTransform ictD = acsp.CreateDecryptor();
//RawBytes now contains original byte array, still in Encrypted state
//Decrypt into stream
MemoryStream msD = new MemoryStream(RawBytes, 0, RawBytes.Length);
CryptoStream csD = new CryptoStream(msD, ictD, CryptoStreamMode.Read);
//csD now contains original byte array, fully decrypted
//return the content of msD as a regular string
return (new StreamReader(csD)).ReadToEnd();
}
}
OK, so according to the description the key is padded with zero bytes. You are actually repeating key bytes. Both methods are of course completely insecure, a key should just consist of random bytes.
The IV seems to be calculated over the key bytes before it is padded, using SHA-1. I don't see anything about that in your code. Note that the IV should change each time the same key is used, and using the SHA-1 over the key is therefore insecure.
The padding is zero padding, up to the size of the block. This means that if your plaintext ends with a 00 byte that you will loose data. Padding is not mentioned, but I tested this by encrypting something and then decrypting it with the same key. It seems the padding bytes are still there. This is not insecure, but as it may lead to invalid plaintext, it is definitely wrong.
There is no authentication tag (e.g. HMAC) added, meaning that anybody can just change the ciphertext and get away with it. In the best case this will lead just to garbage on the other system. In the worst case (and this one is most likely) you will completely lose confidentiality as well. This is probably what you were trying to achieve in the first place.
I hope I have given you enough pointers to create an implementation - for learning purposes or to migrate away from the given code. If you are using random crap - no other word for this - from the Internet, you will however end up with zero security. I won't provide code, as I don't want this to proliferate.
I am encrypting my data by using RSACryptoServiceProvider() class in c#. I want to decrypt the data in ubuntu, that was encrypted in c#. Can you suggest me which mechanism I need to follow in order to decrypt. Following function is for encryption:
public static void Encrypt(String PublicKey, String plainText, out String cipherText)
{
try
{
int dwKeySize = 1024;
// TODO: Add Proper Exception Handlers
RSACryptoServiceProvider rsaCryptoServiceProvider = new RSACryptoServiceProvider(dwKeySize);
rsaCryptoServiceProvider.FromXmlString(PublicKey);
int keySize = dwKeySize / 8;
byte[] bytes = Encoding.UTF32.GetBytes(plainText);
// The hash function in use by the .NET RSACryptoServiceProvider here is SHA1
// int maxLength = ( keySize ) - 2 - ( 2 * SHA1.Create().ComputeHash( rawBytes ).Length );
int maxLength = keySize - 42;
int dataLength = bytes.Length;
int iterations = dataLength / maxLength;
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i <= iterations; i++)
{
byte[] tempBytes = new byte[(dataLength - maxLength * i > maxLength) ? maxLength : dataLength - maxLength * i];
Buffer.BlockCopy(bytes, maxLength * i, tempBytes, 0, tempBytes.Length);
byte[] encryptedBytes = rsaCryptoServiceProvider.Encrypt(tempBytes, true);
// Be aware the RSACryptoServiceProvider reverses the order
// of encrypted bytes after encryption and before decryption.
// If you do not require compatibility with Microsoft Cryptographic API
// (CAPI) and/or other vendors.
// Comment out the next line and the corresponding one in the
// DecryptString function.
Array.Reverse(encryptedBytes);
// Why convert to base 64?
// Because it is the largest power-of-two base printable using only ASCII characters
stringBuilder.Append(Convert.ToBase64String(encryptedBytes));
}
cipherText = stringBuilder.ToString();
}
catch (Exception e)
{
cipherText = "ERROR_STRING";
Console.WriteLine("Exception in RSA Encrypt: " + e.Message);
//throw new Exception("Exception occured while RSA Encryption" + e.Message);
}
}
Don't use RSA like that. It's not meant to be used that way and it's way too slow.
The right way is to use a symmetric algorithm, e.g. AES, and encrypt the key you used with RSA. See my old blog entry for C# code doing just that.
You need the same mechanisms, but in reverse. Try first, ask later.
I've been trying to encrypt a password with a public RSA key that is sent to me by the server.
var csp = new CspParameters(1, "Microsoft Strong Cryptographic Provider");
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(1280, csp);
byte[] key = ByteUtils.HexToBytes(client.RSAKey);
RSA.ImportCspBlob(key);
byte[] encrypted = RSA.Encrypt(Encoding.ASCII.GetBytes(password), true);
The hex key is provided in such format:
string key = "30819D300D06092A864886F70D010101050003818B0030818702818100C7BD672D8C634D443840AD809790852770D3A2E99F456D6516329E0205D0645C23FD001D4D070CEE368A20526FEB2402358C915D7E86102B1659AA8651C449C344599F72BE904B8E338E7002E9978453C5BBCCA51AC165AA265069E0EAB1411D11A2FFDD35E5A8296A6A2AF238945874E8206979B0A16E2E4260A161CAB5C905020111";
As the string is 320-bytes long in hex format, I assume the key is 160 bytes (RSA 1280)
Using this method, the provider keeps saying "Bad Version of provider.\r\n".
I've tried several methods, convert it to Base64, simply import it as ASCII / Unicode. Nothing worked so far.
EDIT: My HexToBytes function (which works afaik, it returns me correct 160-b array):
public static byte[] HexToBytes(string pValue)
{
// FIRST. Use StringBuilder.
StringBuilder builder = new StringBuilder();
// SECOND... USE STRINGBUILDER!... and LINQ.
foreach (char c in pValue.Where(IsHexDigit).Select(Char.ToUpper))
{
builder.Append(c);
}
// THIRD. If you have an odd number of characters, something is very wrong.
string hexString = builder.ToString();
if (hexString.Length % 2 == 1)
{
//throw new InvalidOperationException("There is an odd number of hexadecimal digits in this string.");
// I will just add a zero to the end, who cares (0 padding)
Log.WriteLine(LogLevel.Debug, "Hexstring had an odd number of hexadecimal digits.");
hexString += '0';
}
byte[] bytes = new byte[hexString.Length / 2];
// FOURTH. Use the for-loop like a pro :D
for (int i = 0, j = 0; i < bytes.Length; i++, j += 2)
{
string byteString = String.Concat(hexString[j], hexString[j + 1]);
bytes[i] = HexToByte(byteString);
}
return bytes;
}
Your public key is not in the correct format. It is not a CSP blob. It is a DER encoded SubjectPublicKeyInfo structure. You can find source code to parse it or you can write your own. Here is one example of such code.
I am doing RSA encryption and I have to split my long string into small byte[] and encrypt them. I then combine the arrays and convert to string and write to a secure file.
Then encryption creates byte[128]
I use this the following to combine:
public static byte[] Combine(params byte[][] arrays)
{
byte[] ret = new byte[arrays.Sum(x => x.Length)];
int offset = 0;
foreach (byte[] data in arrays)
{
Buffer.BlockCopy(data, 0, ret, offset, data.Length);
offset += data.Length;
}
return ret;
}
When I decrypt I take the string, convert it to a byte[] array and now need to split it to decode the chunks and then convert to string.
Any ideas?
Thanks
EDIT:
I think I have the split working now however the decryption fails. Is this because of RSA keys etc? At TimePointA it encrypts it, then at TimePointB it tries to decrypt and it fails. The public keys are different so not sure if that is the issue.
When you decrypt, you can create one array for your decrypt buffer and reuse it:
Also, normally RSA gets used to encrypt a symmetric key for something like AES, and the symmetric algorithm is used to encrypt the actual data. This is enormously faster for anything longer than 1 cipher block. To decrypt the data, you decrypt the symmetric key with RSA, followed by decrypting the data with that key.
byte[] buffer = new byte[BlockLength];
// ASSUMES SOURCE IS padded to BlockLength
for (int i = 0; i < source.Length; i += BlockLength)
{
Buffer.BlockCopy(source, i, buffer, 0, BlockLength);
// ... decode buffer and copy the result somewhere else
}
Edit 2: If you are storing the data as strings and not as raw bytes, use Convert.ToBase64String() and Convert.FromBase64String() as the safest conversion solution.
Edit 3: From his edit:
private static List<byte[]> splitByteArray(string longString)
{
byte[] source = Convert.FromBase64String(longString);
List<byte[]> result = new List<byte[]>();
for (int i = 0; i < source.Length; i += 128)
{
byte[] buffer = new byte[128];
Buffer.BlockCopy(source, i, buffer, 0, 128);
result.Add(buffer);
}
return result;
}
I'd say something like this would do it:
byte[] text = Encoding.UTF8.GetBytes(longString);
int len = 128;
for (int i = 0; i < text.Length; )
{
int j = 0;
byte[] chunk = new byte[len];
while (++j < chunk.Length && i < text.Length)
{
chunk[j] = text[i++];
}
Convert(chunk); //do something with the chunk
}
Why do you need to break the string into variable length chunks? Fixed-length chunks, or no chunks at all, would simplify this a lot.
why not use a framework instead of doing the byte-stuff yourself?
http://www.codinghorror.com/blog/archives/001275.html
"the public keys are different"?
You encrypt with a private key, and decrypt with the public key that corresponds to the private key.
Anything else will give you gibberish.