OpenSSL error "data greater than mod len" - c#

I have a client/server setup that consists of a server written in C++ using OpenSSL and a client written in C# using Aes/RSACryptoServiceProvider. I generate an RSA key pair on both sides and send each side the public key. Then, when I'm ready to send a message I generate an Aes key/iv and encrypt the message with this, and then encrypt the Aes key (and the iv too? I've tried both encrypting it and not encrypting it, but both give me the same error, which I will mention in a bit) with the public key of the recipient and then send the Aes encrypted key, the iv and the encrypted message. However, when I try to send from the client to the server, I get an OpenSSL error that reads "data greater than mod len" when using EVP_OpenInit.
Here is how I generate the data in C#:
var keyPair = new RSACryptoServiceProvider(2048); //server uses 2048 too (added on Edit)
var aes = new AesCryptoServiceProvider();
//OpenSSL uses the EVP_CIPHER* EVP_aes_256_cbc()
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.KeySize = 256; //bits
aes.GenerateKey();
aes.GenerateIV();
var message = Encoding.Default.GetBytes("test data");
var eMessage = new byte[4096];
using (var stream = new MemoryStream(eMessage))
{
var encryptor = aes.CreateEncryptor();
using (var cryptoStream = new CryptoStream(stream, encryptor, CryptoStreamMode.Write))
{
await cryptoStream.WriteAsync(message, 0, message.Length);
}
}
string eMessageString = null;
for (int i = 0; i < eMessage.Length; i++)
{
if (eMessage[i] == '\0')
{
eMessageString = Convert.ToBase64String(eMessage, 0, i-1);
}
}
var eKey = Convert.ToBase64String(keyPair.Encrypt(aes.Key, false));
var eIV = Convert.ToBase64String(aes.IV); //may not need to encrypt
I know my C++ implementation works as OpenSSL correctly reads in the client public key and I can encrypt/decrypt data using the EVP_Seal/EVP_Open functions when using a different key generated through OpenSSL on the server. So, I'm not sure what's causing this error, but I think I have an idea. Could it be the way that the key/iv/encrypted message is encoded when I'm sending the data to the server? Or could it be the differences in implementation between OpenSSL and C#? Or maybe something I'm not catching altogether?
EDIT: Here is the requested code for how I use EVP_OpenInit.
BlueSOD::Encryption::DecryptionData BlueSOD::Encryption::EncryptionFactory::Decrypt2(DecryptionWork2 && work)
{
DecryptionData data;
EVP_PKEY* privateKey = work.privateKey.get();
auto encryptionKey = (unsigned char*)work.info.key.c_str();
auto encryptionIV = (unsigned char*)work.info.iv.c_str();
EVP_CIPHER_CTX_ptr cipherCtxPtr{ AcquireCipherCtx() };
EVP_CIPHER_CTX* cipher = cipherCtxPtr.get();
int status;
//ERROR HAPPENS HERE
status = EVP_OpenInit(cipher, m_Cipher, encryptionKey, work.info.key.size(), encryptionIV, privateKey);
cout << ERR_error_string(ERR_get_error(), nullptr) << endl;
CheckForError(status, "EVP_OpenInit failed.");
int bufferLength = work.cipherText.size() + EVP_MAX_BLOCK_LENGTH;
auto buffer = make_unique<unsigned char[]>(bufferLength);
auto cipherTemp = (unsigned char*)work.cipherText.c_str();
status = EVP_OpenUpdate(cipher, buffer.get(), &bufferLength, cipherTemp, work.cipherText.size());
CheckForError(status, "EVP_OpenUpdate failed.");
status = EVP_OpenFinal(cipher, buffer.get(), &bufferLength);
CheckForError(status, "EVP_OpenFinal failed.");
data.plainText = CreateSecureString(buffer.get(), bufferLength);
return move(data);
}

Encoding.Default.GetString won't work. The IV, wrapped key and ciphertext are all binary, and (as good as) indistinguishable from random. That means that the encoding may go wrong as not all bytes will map to characters. This means that information is lost. Try and use base 64 encoding instead.
Your IV, wrapped key and ciphertext should also be distinguishable from each other. This is however not hard as the IV has the block size of the underlying cipher (16 bytes for AES/CBC), the wrapped key has the same size in bytes of the modulus (or the RSA key size), and the ciphertext, well, is the rest. In other words you might as well simply concatenate them all.
So your hunch was right.

RSA 4096 w/ OAEP can only encrypt 446 bytes of data (see 7.1 of RSA RFC 2437), and RSA 2048 w/ OAEP can only encrypt 245 bytes (still should be plenty of room for 16 + 32 bytes for IV and symmetric key). I don't see anywhere that you set the key length for the RSA provider, so it may be failing for some reason to encrypt the AES key.
Can you provide at least the line at which the server code throws the exception? What are you providing for the eki parameter (symmetric secret key length) in EVP_OpenInit? Are you performing the Base64 decoding of the symmetric key before attempting to decrypt it using RSA on the server?
And for the record, you do not need to encrypt the IV before transmitting, but it has no negative impact (other than computation cost) to do so.
Update:
It is always helpful when debugging crypto issues to reduce the number of steps in each statement so you can find where the error is occurring. I'd recommend breaking out the last few statements of your client code into individual steps and walking through them (i.e. RSA encryption and Base64-encoding on separate lines).
So you can now compare the following values on client and server and they are byte-for-byte equal (no extra 0x00, etc.)?
Reference | Client | Server
------------------------------------------------------------------
A | keyPair.Encrypt(aes.Key, false) | ek
Base64E(A) | eKey | ??
len(A) | len(A) | len(ek)
You mentioned in another comment that you compared the hex-encoded value of the Base64-decoded, encrypted key on both client and server and it was identical? Can you try just using the client & server to encrypt and decrypt an arbitrary plaintext message (<< 245 bytes in order to ensure that OAEP or PKCS#1 v1.5 padding does not then exceed 245 bytes) with that key pair to ensure everything is correct?
I'm not particularly familiar with the C# implementation -- is there something additional you need to do to replicate EVP_SealInit on the client?

My issue lied in how the message, key, and iv were encoded on the client and decoded on the server. The client encoded them in base64, so it had to be decoded on the server in order for OpenSSL to understand them (OpenSSL uses raw bytes rather than an encoding scheme).
EDIT: There also seems to be a conflict between the C# implementation and the OpenSSL implementation. The C# Aes implementation does not seem to match the OpenSSL exactly. I get the correct plain text when decrypting in OpenSSL, but there is a bunch of garbage data that follows and EVP_OpenFinal causes an error that says "bad decrypt". If anyone knows a way around this, please let me know! I have tried the openssl.net API, but it throws an error if I try to use BIO.

Related

AES decryption in C# uniqUE

I am using the AES to encrypt and decrypt passwords on a website. Anyways; the encrypting works just fine. But I have some problems with the decrypting. On the line:
byte[] decrypted = DecryptStringFromBytes_Aes(encrypted, key, iv);
I recieve this error: Cannot implicity convert type 'string' to 'byte[]'. I have tried lots of things, but nothing seem to work.
You can see the rest of the code below.
string original = txtEncrypt.Text;
byte[] key = new byte[] { 3,122,23,189,15,2,55,82,97,17,255,45,1,65,41,200 };
byte[] iv = new byte[16];
Aes myAes = Aes.Create();
byte[] encrypted = EncryptStringToBytes_Aes(original, key, iv);
byte[] decrypted = DecryptStringFromBytes_Aes(encrypted, key, iv);
Sincerely,
Adrian
The sample code that you used returns the ciphertext as byte array. Modern ciphers, like AES in CBC mode as you're using, operate on bytes, not strings.
So if you need a string then you need to convert to a string and then back again. For this you could use an encoding such as base 64 encoding. So encode to base 64 after encryption and then decode before decryption.
If you just directly interpret the bytes as a string (e.g. UTF-8) then you will experience data loss as not every byte is a valid / printable UTF-8 character.
Don't forget to include all required information that needs to be shared, such as the IV. The example code conveniently forgets about that.
Note that CBC is not secure for transport mode security; only use for data at rest.

PHP openssl_encrypt c# implementation

I am developing an integration in C# which syncs Office365 distribution lists to Sendy (sendy.co), written in PHP.
In the PHP application some ID's are being encrypted and I want to achieve the same in C# so that I can communicate using their API without having to look up the 'secret' ID.
This is the code in PHP (I replaced the password) in their application that calculates these ID's:
$encrypted = openssl_encrypt($in, 'AES-256-CBC', 'API_KEY', 0, 'SECRET_PASSWORD');
$encrypted = str_replace('/', '892', $encrypted);
$encrypted = str_replace('+', '763', $encrypted);
$encrypted = str_replace('=', '', $encrypted);
I overcame this 'issue' by hosting the PHP script somewhere and calling it from my C# application, but I want to make it opensource so I would like this to be integrated in the application.
I suppose I would have to start with .NET's AesCryptoServiceProvider, but I don't seem to be able to get it right (I get exceptions about the key length and stuff).
So far I tried this:
public static string Execute()
{
// openssl_encrypt ( string $data , string $method , string $password [, int $options = 0 [, string $iv = "" ]] )
var aes = new AesCryptoServiceProvider();
aes.KeySize = 256;
// Fixed password in code
aes.Key = Encoding.UTF8.GetBytes("FIXED PASSWORD");
// API = IV
aes.IV = Encoding.UTF8.GetBytes("SENDY API KEY");
aes.Mode = CipherMode.CBC;
// Trying to encrypt "36" in this case
byte[] src = Encoding.Unicode.GetBytes("36");
// Actual encryption
using (var encrypt = aes.CreateEncryptor())
{
byte[] dest = encrypt.TransformFinalBlock(src, 0, src.Length);
// Convert byte array to Base64 strings
return Convert.ToBase64String(dest);
}
}
However this throws an exception saying the IV doesn't match the block size of the algorithm.
I suppose the openssl_encrypt method in PHP derivatives the actual IV from the given API KEY in the sample (so the $password parameter), but I can't find much documentation on it to be able to achieve the same in C#.
The size of the iv must be equal to the block size which for AES is 16-bytes, "SENDY API KEY" is only 13-bytes.
The iv should be a different random value for each encryption, just prepend it to the encrypted data for use during decryption.
Next you have specified a key size of 256-bits but the key "FIXED PASSWORD" is only 14-bytes, make them the same, 128, 192 or 256 bits (16, 24 or 32 bytes). There is no need to use a key over 128-bits.
A password should not be used directly for a key, the key should be derived from the password with a function such as PBKFD2.
AES is a block cipher and it's. Input needs to be a multiple of the block size in length. To accomplish this padding of the input is needed. Typically PKCS#7 (née PKCS#5) padding is used, it is probably the default for OpenSSL. The encryption/decryptions should automatically add/remove this padding. But this does mean that the encrypted data length will be from 1-byte to 16-bytes longer than the input. e.g. If you encrypt "36" the output length will be 16-bytes.
Getting all this together correctly can be difficult to get correct, consider using an overall solution such as RNCryptor-php or defuse

c# "Bad Data" exception while decrypting - Using Base64 Encoding for transport

Ive been having some Crypto troubles cant see find what I've done wrong. I'm trying to encrypt an AESkey using RSA on Android and Decrypt it server side using C#, but keep getting a "Bad Data" exception.
I used Base64encoding to move the encrypted key from client to server and noticed that after moving it from the client(Android App) using a JSON POST request there were a number of "\u000a" in the key making the encrypted data length 941 which led to a "Data to large for decryption" when removed in brought the length to 920 which allowed for 80 8 byte iterations and got me to where I am now with the Bad Data problem.
I have checked that the key Length and Algorithm are correct and both are set for 2048 bit key and using PKCS1Padding.
"Bad Data" Exception
This exception will be thrown in the following scenarios.
a) The RSA private key used for decryption does not match with the RSA public key that is used for encryption.
b) The binary data passed in to Decrypt() method is incorrect. This could happen if the application code made assumptions about the length of encrypted data or the data passed in does not match the exact bytes that is returned from Encrypt() method.
I get the public key on android by pulling it from the server with a GET which returns RSACryptoServiceProvider.ToXMLString(false); And use the same keystore for the private key so cant see it being 1.
And as far as I know the c# decrypter Isn't making any assumptions about the size of the encrypted data. Possibly my setting block size to 8 but thats after i knew the size of the encrypted AESkey.
I've been looking around for a solution and couldn't find one so would be grateful for any assistance. Apologies if I'm being stupid and missed something simple but I have my blinkers on if I am and just cant see it.
Java Encryption
private byte[] encryptRSA(byte [] data) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException{
//instance of singleton PublicKey
AppPublicKey currKey = AppPublicKey.getInstance();
Log.d("ENCRYPT.MOD: ", currKey.getModBytes().toString());
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(new BigInteger(1,currKey.getModBytes()), new BigInteger(1,currKey.getExpBytes()));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey pubKey = keyFactory.generatePublic(keySpec);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
byte[] cipherData = cipher.doFinal(data);
Log.d("RSAENCRYPTION: ",Base64.encodeToString(cipherData, 1));
return cipherData;
}
C# Decrypt
public string DecryptString(string inputString, int dwKeySize)
{
// TODO: Add Proper Exception Handlers
CspParameters cp = new CspParameters();
cp.KeyContainerName = "real_Keystore";
RSACryptoServiceProvider rsaCryptoServiceProvider
= new RSACryptoServiceProvider(dwKeySize,cp);
int base64BlockSize = 8;
int iterations = inputString.Length / base64BlockSize;
ArrayList arrayList = new ArrayList();
for (int i = 0; i < iterations; i++)
{
byte[] encryptedBytes = Convert.FromBase64String(
inputString.Substring(base64BlockSize * i, base64BlockSize));
//Array.Reverse(encryptedBytes);
arrayList.AddRange(rsaCryptoServiceProvider.Decrypt(
encryptedBytes, false));
}
return Encoding.UTF32.GetString(arrayList.ToArray(
Type.GetType("System.Byte")) as byte[]);
}
It is not possible to feed a few bytes at a time to the RSA operation.
Furthermore it seems unlikely that the code performs the right amount of base 64 iterations (as you defined NO_PADDING for base 64 in your android, using 1 instead of the constant). Normally the output of RSA encryption won't be a multiple of 3 bytes, so you are at least one block off the mark.
You may want to take a closer look at the API functions you are using and take some time to study RSA examples on .NET. Normally RSA is only used to encrypt small amounts of data (such as symmetric data encryption keys) so you should be able to decode all of the base64 data in one go.
Please test your input and output in a debugger. Encryption/decryption problems normally require that the exact input and output of the encryption/decryption algorithms are compared .

C# TripleDES Provider without an Initialization Vector?

I have a set of encrypted documents encoded with TripleDES coming from a remote system. I need to decode the data in C# and I have no control over the key or encoding algorithm. All I have is the key and the mode (CBC) and the data located in a file.
The TripleDESCryptoServiceProvider is easy enough to use, but I can't figure out how to use the Decryptor without an Initialization Vector.
We have a have 24 byte (192bit) key to decrypt with, but nothing else.
string key = "1468697320656E6372797174696F6E206973737265206933";
byte[] keyData = ParseHex(key); // key is OK at 24 bytes
TripleDESCryptoServiceProvider des = new TripleDESCryptoServiceProvider();
des.Mode = CipherMode.CBC;
des.GenerateIV();
var decryptor = des.CreateDecryptor(keyData,null); // des.IV
var encoded = File.ReadAllBytes(#"..\..\..\..\test.tdes");
byte[] output = decryptor.TransformFinalBlock(encoded, 0, encoded.Length);
This fails outright with Bad data. If I switch to TransformBlock the code at least runs but produces just gibberish:
byte[] output = new byte[10000];
var count = decryptor.TransformBlock(encoded, 0, encoded.Length, output, 0);
So the questions are:
If I only have a key is the InitializationVector required?
If not is null the right thing to pass?
What else would I possibly need to set beyond the key and mode?
Why does TransformBlock at least work and TransformFinalBlock just fails?
Update - found the problem
It turns out the decoding problem was caused, not by the missing Initialization Vector, but by incorrect information from the provider of the encrypted data. The updated working code looks like this:
// Read the test data
byte[] encoded = File.ReadAllBytes(#"..\..\..\..\test.tdes");
// Get the key into a byte array
string key = "1468697320656E6372797174696F6E206973737265206933";
byte[] keyData = ParseHex(key);
TripleDESCryptoServiceProvider des = new TripleDESCryptoServiceProvider();
des.Mode = CipherMode.ECB; // Make sure this is correct!!!
des.Padding = PaddingMode.Zeros; // Make sure this is correct!!!
des.Key = keyData;
var decryptor = des.CreateDecryptor();
byte[] output = decryptor.TransformFinalBlock(encoded, 0, encoded.Length);
string dataString = Encoding.Default.GetString(encoded);
Console.WriteLine(dataString);
Console.WriteLine("\r\n\r\nDecoded:");
string result = Encoding.Default.GetString(output);
Console.WriteLine(result);
Console.Read();
The key in our case was using the proper CipherMode and Padding. Fixing the padding made TransformFinalBlock() work without Bad Data errors. Fixing the CipherMode made properly unencrypted the data.
Moral of the story: In CipherMode.ECB mode at least an Initialization Vector you don't need to provide an initialization vector. If no IV is provided the provider will auto-generate one, but the decryption still works (at least with ECB).
In the end it's CRUCIAL to make sure you have all the information from the provider that encrypted the data.
Trying to answer each point:
The Initialization Vector is required in CBC mode. It is not required to be a secret (unlike the key) so it should be sent from the remote system.
Since you need the IV, null is not the right thing to pass.
Padding mode. You need to know which padding mode is used.
TransformFinalBlock probably fails because the Padding mode is wrong.
Edit
The difference between ECB (Electronic Code Book) and CBC (Cipher Block Chaining) is illustrated below:
As you can see no IV is used in ECB mode. So even if you provide one it will be ignored.

C#, how to check if value is encrypted using MD5 passphrase?

I have the following code to encrypt a value (listed below). Now I would like to write a bool isEncrypted() method. Is there a fool proof and reliable way to check if a value has been encrypted using this function. I have the decrypt routine and can control the pass phrase, but not sure if that will help.
The reason is - when the app first runs, values in a configuration file are not encrypted, in this case the app should auto encrypt these values. On 2nd run I don't want to encrypt again because obviously that would cause havoc. Lastly I don't want to have to add an isEncrypted attribute to the config value. I want it to work and look as dynamic as possible.
So far I am leaning towards using the len (128) as deciding factor, but there is always a remote chance of the unencrypted value also being this length.
Thanks in advance.
public static string encrypt(string text)
{
// Locals
var passphrase = "5ab394ed-3920-4932-8d70-9c1b08f4ba4e";
byte[] results;
var utf8 = new UTF8Encoding();
// Step 1. We hash the passphrase using MD5
// We use the MD5 hash generator as the result is a 128 bit byte array
// which is a valid length for the TripleDES encoder we use below
var hashProvider = new MD5CryptoServiceProvider();
var tdesKey = hashProvider.ComputeHash(utf8.GetBytes(passphrase));
// Step 2. Create a new TripleDESCryptoServiceProvider object
// Step 3. Setup the encoder
var tdesAlgorithm = new TripleDESCryptoServiceProvider
{
Key = tdesKey,
Mode = CipherMode.ECB,
Padding = PaddingMode.PKCS7
};
// Step 4. Convert the input string to a byte[]
var dataToEncrypt = utf8.GetBytes(text);
// Step 5. Attempt to encrypt the string
try
{
var encryptor = tdesAlgorithm.CreateEncryptor();
results = encryptor.TransformFinalBlock(dataToEncrypt, 0, dataToEncrypt.Length);
}
finally
{
// Clear the TripleDes and Hashprovider services of any sensitive information
tdesAlgorithm.Clear();
hashProvider.Clear();
}
// Step 6. Return the encrypted string as a base64 encoded string
return Convert.ToBase64String(results);
}
What you could do in the isEncrypted method is to try to decrypt the message.
Since you are using PKCS7 padding most likely an unencrypted message will fail to decrypt since the padding does not conform to the set padding mode.
The decryption will throw an exception and you'll have to catch this and return false in this case.
There is a remote chance that the decryption will go through (when the message is not encrypted) if the data conforms to the padding mode. This is however most unlikely.
What I would do in this case would be to add some kind of flag in the encrypted data or append some data to encrypted message since I can then remove it in the decryption. This would be the most foolproof way.
First, as a serious issue, it's an exceedingly poor idea to use cryptographic primitives on your own. You've chosen to use the Electronic Codebook mode of encryption, which has the property that identical plaintext blocks produce identical cyphertext blocks. Check out the example at Wikipedia.
That said, a simple solution is to prepend a token such as 'ENC:' to the encrypted password. If you need to worry about malicious tampering with the config file, you should proceed to use a message authentication code, such as HMAC.
As your function returns a string there's no reason you can't add a plaintext code to the beginning of the encrypted data that the IsEncrypted function can look for, say "MD5ENC"+ [ciphertext].
The disadvantage of this is that it will let anyone who has the raw string know what algorithm was used for encryption. But as we keep getting reminded security through obscurity is no security at all. Anyone should be allowed to know how something was encrypted and have no easy way of breaking that encryption.
Note my use of the word should.
Anyhow, to return to my original suggestion. The advantage of this is that the longer your introductory code on the string the more vanishingly tiny the chances of it being generated by accident in another unrelated Base64 encrypted string becomes.
Should the ciphertext need decrypting just snip off your standard length encryption ident code and away you go...

Categories