I am trying to write a Python module that will encrypt text that our existing .NET classes can decrypt. As far as I can tell, my code lines, up but it isn't decrypting (I get an 'Invalid padding length' error on the C# side). My pkcs7 code looks good, but research indicates that invalid keys could cause this same problem.
What's different between these two setups?
Python:
derived_key = PBKDF2(crm_key, salt, 256 / 8, iterations)
iv = PBKDF2(crm_key, salt, 128 / 8, iterations)
encoder = pkcs7.PKCS7Encoder()
cipher = AES.new(derived_key, AES.MODE_CBC, iv)
decoded = cipher.decrypt(encoded_secret)
#encode - just stepped so i could debug.
padded_secret = encoder.encode(secret) # 1
encodedtext = cipher.encrypt(padded_secret) # 2
based_secret = base64.b64encode(encodedtext) # 3
I thought that based_secret could get passed up to C# and decoded there. But it fails. The same encrypting c# code is:
var rfc = new Rfc2898DeriveBytes(key, saltBytes);
// create provider & encryptor
using (var cryptoProvider = new AesManaged())
{
// Set cryptoProvider parameters
cryptoProvider.BlockSize = cryptoProvider.LegalBlockSizes[0].MaxSize;
cryptoProvider.KeySize = cryptoProvider.LegalKeySizes[0].MaxSize;
cryptoProvider.Key = rfc.GetBytes(cryptoProvider.KeySize / 8);
cryptoProvider.IV = rfc.GetBytes(cryptoProvider.BlockSize / 8);
using (var encryptor = cryptoProvider.CreateEncryptor())
{
// Create a MemoryStream.
using (var memoryStream = new MemoryStream())
{
// Create a CryptoStream using the MemoryStream and the encryptor.
using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{
// Convert the passed string to a byte array.
var valueBytes = Encoding.UTF8.GetBytes(plainValue);
// Write the byte array to the crypto stream and flush it.
cryptoStream.Write(valueBytes, 0, valueBytes.Length);
cryptoStream.FlushFinalBlock();
// Get an array of bytes from the
// MemoryStream that holds the
// encrypted data.
var encryptBytes = memoryStream.ToArray();
// Close the streams.
cryptoStream.Close();
memoryStream.Close();
// Return the encrypted buffer.
return Convert.ToBase64String(encryptBytes);
}
}
}
The Python pkcs7 implementation I'm using is:
https://gist.github.com/chrix2/4171336
First off, I verified that Rfc2898 and PBKDF2 are the same thing. Then, as stated above, the problem appears to be a .net ism. I found on msdn
that the implementation of GetBytes inside of Rfc2898DeriveBytes changes on each call, ie. it holds state. (see the remarks about halfway down the page)
Example in Python (pseudo output):
derived_key = PBKDF2(key, salt, 32, 1000)
iv = PBKDF2(key, salt, 16, 1000)
print(base64.b64encode(derived_key))
print(base64.b64encode(iv))
$123456789101112134==
$12345678==
Same(ish) code in .NET (again, pseudo output):
var rfc = new Rfc2898DeriveBytes(key, saltBytes);
using (var cryptoProvider = new AesManaged())
{
// Set cryptoProvider parameters
cryptoProvider.BlockSize = cryptoProvider.LegalBlockSizes[0].MaxSize;
cryptoProvider.KeySize = cryptoProvider.LegalKeySizes[0].MaxSize;
cryptoProvider.Key = rfc.GetBytes(cryptoProvider.KeySize / 8);
cryptoProvider.IV = rfc.GetBytes(cryptoProvider.BlockSize / 8);
}
Console.Writeline(Convert.ToBase64(cryptoProvider.Key));
Console.Writeline(Convert.ToBase64(cryptoProvider.IV));
$123456789101112134==
$600200300==
Subsequent calls to rfc.GetBytes always produces different results. MSDN says it compounds the key sizes on the calls. So if you call GetBytes(20), twice, it's the same as calling GetBytes(20+20) or GetBytes(40). Theoretically, this should just increase the size of the key, not completely change it.
There are some solutions to get around this issue, which could be generating a longer key on the first call, then slicing it into both a derived key AND an IV, or randomly generating an IV, appending it to the encoded message and peeling it off before decrypting it.
Slicing the python output produces the same results as .NET. It looks like this:
derived_key = PBKDF2(key, salt, 32, 1000)
iv = PBKDF2(key, salt, 32 + 16, 1000) # We need 16, but we're compensating for .NETs 'already called' awesomeness on the GetBytes method
split_key = iv[32:]
print(base64.b64encode(derived_key))
print(base64.b64encode(iv))
print(base64.b64encode(split_key))
$ 123456789101112134== # matches our derived key
$ 12345678== # doesn't match
$ 600200300== # matches. this is the base 64 encoded version of the tailing 16 bytes.
Enjoy,
Related
I'm a newbie in cryptography and to learn it I tried to encrypt/decrypt with AES in C#.
Sadly I realized, that it isn't as easy as I thought.
So I was looking for a simpler solution.
Later I found a couple of code snippets including some explanation.
I copied the code and tried to implement it into a small application.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace aes
{
class Program
{
public static string passwd = null;
public static string content = null;
public static string encryptedcontent = null;
public static byte[] IV = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
public static int BlockSize = 128;
static void Encrypt()
{
if (passwd == "") return;
//Content to Byte Array
byte[] bytes = Encoding.Unicode.GetBytes(content);
//Encrypt
//Init AES
SymmetricAlgorithm crypt = Aes.Create();
//Init md5 hash
HashAlgorithm hash = MD5.Create();
//AES blocksize (AES 192 etc.) (min 128)
crypt.BlockSize = BlockSize;
//Generating Key
crypt.Key = hash.ComputeHash(Encoding.Unicode.GetBytes(passwd));
//Initialize Vectors
crypt.IV = IV;
//CryptoStram is used for encryption
//The required Encryptor is based on the algorithm above
//Cryptostream sends data of the encrypted byte array to Memorystream
//The memory stream is then converted into a Base64 string and made readable
using (MemoryStream memoryStream = new MemoryStream())
{
using (CryptoStream cryptoStream =
new CryptoStream(memoryStream, crypt.CreateEncryptor(), CryptoStreamMode.Write))
{
cryptoStream.Write(bytes, 0, bytes.Length);
}
encryptedcontent = Convert.ToBase64String(memoryStream.ToArray());
}
}
static void Main(string[] args)
{
//Set Password
Console.WriteLine("Passwort angeben");
Console.Write("> ");
passwd = Console.ReadLine();
//Set content to encrypt (String)
Console.WriteLine("Zu verschlüsselner Text angeben");
Console.Write("> ");
content = Console.ReadLine();
Encrypt();
Console.WriteLine(encryptedcontent);
Console.ReadLine();
}
}
}
Subsequently I wanted to try the programm with some testdata.
I actually got a seemingly encrypted string.
PW: supersecretpassword Content: I like to keep my secrets Result: SEEc1sLMIyfVFsoHPFRIcl437+yjUC5uFMgco3iO+oWSgJWQOwKhoDhUbFJREeqiIvaY2DBR+Ih4OJeGAc6JZQ==
I tried to use some online tools to decrypt and check my result.
Sadly most of the Webtools were not able to decrypt my result.
And if I encrypt the sentence I like to keep my secrets with that online tools I get results like:
7IWuebm0T8HdrGdtkBjt5zgjbdEqYfidNZVvfgtOjH4=
My result SEEc1sLMIyfVFsoHPFRIcl437+yjUC5uFMgco3iO+oWSgJWQOwKhoDhUbFJREeqiIvaY2DBR+Ih4OJeGAc6JZQ==
As you can see, the two results are different.
Unfortunately I have no idea why this could be the case.
Thanks for you help
Jonas
P.S Somehow I deleted some of rows written in this question. I hope the new words can clarify what my problem is.
You don't say what online tools did, or did not, succeed in replicating your results, so this is a general answer, instead of specific.
//AES blocksize (AES 192 etc.) (min 128)
crypt.BlockSize = BlockSize;
The BlockSize of AES is 128. Always (contrast with the original algorithm, Rijndael, which allows the BlockSize to change).
AES-128/AES-192/AES-256 are about the KeySize, not the BlockSize.
crypt.Key = hash.ComputeHash(Encoding.Unicode.GetBytes(passwd));
You're using MD5(UTF16(password)) as your Key Deriviation Function (KDF). Maybe you can find an online sample that is using this, but they're more likely to be using MD5(UTF8(password)) (which would come from Encoding.UTF8, vs Encoding.Unicode). A better answer would be to use a proper password-based Key Derivation Function, like PBKDF2 (which is called Rfc2898DeriveBytes in .NET for... reasons).
[When I encrypt I like to keep my secrets I get an answer that is twice as long as online tools.]
You're encrypting the UTF-16 representation of that string. The string is comprised of 25 Unicode codepoint values, all from the US-ASCII range. Therefore the UTF-16 representation is just the codepoint length * 2 (50 bytes).
50 bytes breaks down into 3 16-byte (128-bit) blocks, plus 2 bytes left over. Add padding, that becomes 4 blocks of AES-CBC-PKCS#7 output (64 bytes). 64 bytes converts to Base64 as 21 full values (of 3 bytes -> 4 chars) with 1 byte remaining, so the Base64 value ends in 2 = padding characters with a total length of 88 characters. This matches your description, hooray :).
If, on the other hand, you used the UTF-8 encoding, you'd have 25 bytes into encryption, which becomes 2 blocks of output (32 bytes) which turns into 10 full base64 conversions with 2 bytes remaining, so one = at a total of 44 characters... which looks a lot like what the online tools are using.
You also should produce a new IV for every time you encrypt with the same key. The IV isn't a key, but changing the IV causes the same secret input to get encrypted differently, so someone who can see your encrypted data can't tell that you sent the same message that you just sent. (At least, that's the purpose in CBC block mode, in other block modes it has sometimes more important purposes). The IV can be transmitted with the message... in fact it should be, unless you have some other way of both sides agreeing (without hard-coding it).
And, of course, you should dispose all of your disposable objects. Changing your encoding to UTF-8, but not changing your KDF, would better be
private static string Encrypt(string content, string password)
{
byte[] bytes = Encoding.UTF8.GetBytes(content);
using (SymmetricAlgorithm crypt = Aes.Create())
using (HashAlgorithm hash = MD5.Create())
using (MemoryStream memoryStream = new MemoryStream())
{
crypt.Key = hash.ComputeHash(Encoding.UTF8.GetBytes(password));
// This is really only needed before you call CreateEncryptor the second time,
// since it starts out random. But it's here just to show it exists.
crypt.GenerateIV();
using (CryptoStream cryptoStream = new CryptoStream(
memoryStream, crypt.CreateEncryptor(), CryptoStreamMode.Write))
{
cryptoStream.Write(bytes, 0, bytes.Length);
}
string base64IV = Convert.ToBase64String(crypt.IV);
string base64Ciphertext = Convert.ToBase64String(memoryStream.ToArray());
return base64IV + "!" + base64Ciphertext;
}
}
Some issues that I see is a self defined IV and odd blocksize, edit: and you probably have the wrong value for the password in mind when comparing to online tools where you do have to fill in the password as calculated by the ComputeHash function.
Check out this simple MSDN Example
I am working on a project for secure file transfer which encrypts files using c# client on the customer side. i need to decrypt the files on server side using php and maybe phpseclib. The code here i copied from a msdn example. But i cant work out the decrypt function in php.
public static byte[] AES_Encrypt(byte[] bytesToBeEncrypted, byte[] passwordBytes)
{
byte[] encryptedBytes = null;
byte[] saltBytes = passwordBytes;
using (MemoryStream ms = new MemoryStream())
{
using (RijndaelManaged AES = new RijndaelManaged())
{
var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 1000);
AES.KeySize = 256;
AES.BlockSize = 256;
AES.Mode = CipherMode.CBC;
AES.Padding = PaddingMode.Zeros;
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = key.GetBytes(AES.BlockSize / 8);
using (CryptoStream cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length);
cs.Close();
}
encryptedBytes = ms.ToArray();
}
}
return encryptedBytes;
}
This is the php code which doesnt work:
$pw = "this_is_my_pw";
$aes = new Crypt_AES(CRYPT_AES_MODE_CBC);
$aes->setKey($pw);
$aes->setKeyLength(256);
$aes->disablePadding();
$file = "enc.txt";
$fh = fopen($file, "r");
$contents = trim(fread($fh, filesize($file)));
fclose($fh);
//echo "Encoded: \n\n" . $contents;
$contents = $aes->decrypt($contents);
#$block = mcrypt_get_block_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC);
#$padding = $block - (strlen($clear) % $block);
#$dec = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $pw, base64_decode($contents), MCRYPT_MODE_CBC, $pw);
echo "Decoded: \n\n" . $contents;
Can someone help me fixing this or give me a hint what i do wrong?
No initialization vector used when decrypting. You need to send the initialization vector (IV) along with the data - your PHP code is never calling $aes->setIV from phpseclib, so it will never be able to decrypt the text because phpseclib uses an IV of all zeros if one is not set according to the docs. I would personally recommend generating a secure random IV from C# using RijndaelManaged.GenerateIV, but apparently it's considered acceptable to derive the IV from a PBKDF2 key. PBKDF2 (specified in RFC 2898) is the key-stretching algorithm Rfc2898DeriveBytes implements. Regardless, you need to re-produce the IV on the PHP side, whether that means transmitting the IV with the encrypted data (which is completely fine) or re-deriving the IV on the PHP side.
Using the password as the salt is a REALLY BAD IDEA. The salt needs to be of sufficient length and cryptographically randomly generated. Using the password as the salt completely defeats the point of having a salt. MSDN has some sample code that shows how to generate a cryptographically random salt in conjunction with using Rfc2898DeriveBytes, but the important part is here:
byte[] saltBytes = new byte[8];
using (RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider())
{
// Fill the array with a random value.
rngCsp.GetBytes(salt1);
}
The salt must be transmitted with the encrypted data. You need to send the PBKDF2 salt bytes along with the IV bytes and encrypted data. phpseclib will need all of those to properly initialize itself and decrypt the data. You'll probably want to use phpseclib's setPassword to do this, like so:
$salt = ...; // get the salt to your PHP code somehow
$iv = ...; // get the IV to your PHP code
$pw = "this_is_my_pw";
$aes = new Crypt_AES(CRYPT_AES_MODE_CBC);
$aes->setPassword($pw, 'pbkdf2' /* key extension algorithm */,
'sha1' /* hash algorithm */, $salt /* generated salt from C# */,
1000 /* number of iterations, must be same as C# code */,
256 / 8 /* key size in bytes, 256 bit key / 8 bits per byte */
);
$aes->setIV($iv);
Keep the other answers in mind about blocksize. 128 bits is the standard AES blocksize, so make sure both C# and phpseclib can function correctly with a larger blocksize, or just use the AES standard for both.
If you are trying to use AES set the block size to 128-bits, that is the only block size that is supported. Using a different block size means you are using Rijndael encryption which is not well supported cross platform.
AES supports multiple key sizes of 128, 192 and 256 bits. Sometimes there is confusion when using a Rijndael implementation to use AES encryption.
In the Java code I see AES.BlockSize = 256;. Technically, AES has a fixed block size of 128 bits. Rijndael supports variable block sizes but AES doesn't. If you want to make use of variable block sizes in PHP with phpseclib you'd need to do this:
$pw = "this_is_my_pw";
$aes = new Crypt_Rijndael(CRYPT_RIJNDAEL_MODE_CBC);
$aes->setKey($pw);
$aes->setKeyLength(256);
$aes->setBlockLength(256);
$aes->disablePadding();
Also, your key is 13 bytes long. AES keys need to be either 16 bytes (128 bits) long, 24 bytes (192 bits) long or 32 bytes (256 bits) long. idk what js lib you're using but phpseclib 1.0/2.0 null pads keys if they're not long enough. The newest version of phpseclib - currently under development - throws exceptions.
Or maybe you mean to be using a password based key derivation function? phpseclib provides two that can be utilized via setPassword() but if that were the case you'd need to know what method and parameters were being utilized by the js lib.
I try to encrypt a string (json) with Rijndael in C# and come up with a string, which I can offer to a PHP web service. This web service in turn decodes the string using the IV and masterkey (known to them). I have to write the C# code that can talk to the PHP service, I do not control/own the PHP service.
The PHP code for encrypting is as follows:
function encrypt($plaintext) {
$masterkey = 'masterKeyOfLength29Characters';
$td = mcrypt_module_open(MCRYPT_RIJNDAEL_256, '', MCRYPT_MODE_CBC, '');
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
mcrypt_generic_init($td, $masterkey, $iv);
$crypttext = mcrypt_generic($td, $plaintext);
mcrypt_generic_deinit($td);
return base64_encode($iv.$crypttext);
}
$param = array("key" => "value");
$encryptedString = rawurlencode(encrypt(json_encode($param)))
The code above I'll have to convert to C#, so I can encrypt my JSON and offer it to the PHP web service.
There are two problems. The first was with the masterkey length, the second (might be related) is with the rawurlencode of the encrypted data (hard for me to test at this point).
var masterkey = "masterKeyOfLength29Characters";
var data = EncryptData(json, masterkey);
// Some code to URL Encode the data, I haven't gotten as far to test this
// since I can't encrypt with the key used in PHP, so I can't call the service
// to test the encoded string from my C# code.
data = HttpUtility.UrlEncode(data);
data = data.Replace("+", "%20");
public static string EncryptData(string json, string encryptionKey) {
Rijndael rj = Rijndael.Create();
rj.Mode = CipherMode.CBC;
rj.Padding = PaddingMode.PKCS7;
rj.BlockSize = 256;
rj.KeySize = 256;
rj.Key = Encoding.UTF8.GetBytes(encryptionKey); // ERROR here
rj.GenerateIV();
var encryptedJSON = EncryptStringToBytes(json, rj.Key, rj.IV);
var r1 = Convert.ToBase64String(rj.IV);
var r2 = Convert.ToBase64String(encryptedJSON);
return r1 + r2;
}
The EncryptStringToBytes does some checks and uses this code (plucked from the many examples on the internet):
using (Rijndael rijAlg = Rijndael.Create()) {
// Basically I do the same here as above, and I could also generate
// the IV here, but then I'd had to return it too. I know I can clean this
// code up quite a bit, but I'd rather focus on getting it to work first ;)
rijAlg.Mode = CipherMode.CBC;
rijAlg.Padding = PaddingMode.PKCS7;
rijAlg.BlockSize = 256;
rijAlg.KeySize = 256;
rijAlg.Key = Key;
rijAlg.IV = IV;
ICryptoTransform encryptor = rijAlg.CreateEncryptor(rijAlg.Key, rijAlg.IV);
using (MemoryStream msEncrypt = new MemoryStream()) {
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) {
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt)) {
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
The error I'll get:
Specified key is not a valid size for this algorithm.
So, the problems in short:
1) How come the PHP code accepts the key of length 29 in the Rijndael 256 (CBC mode), and my C# doesn't? I've played around with the Mode, added the Padding later, set the KeySize (was 256 default already), and I just can't see what I'm doing wrong here.
2) When I use a key of length 32, this one is accepted and my code works. I can also decrypt it in C# (but can't test this in PHP). I would like to solve problem 1, and then continue on problem 2, but maybe someone can give me some understanding here. The encrypted string contains 1 '=' in the IV, and 2x '==' (at the end) in the encrypted json. I've read about padding and such, but I was wondering why no '=' signs are visible in the PHP examples I received. Again, maybe after fixing problem 1 this won't be an issue.
Many thanks for reading and I hope I'm not being too stupid here. After a day of trying yesterday I kind of get the feeling I've tried many different approaches and non seem to work.
Just thought I'd add a tiny bit to what #artjom-b has said.
Firstly, it does work :-)
But in addition you need to change your
rj.Padding = PaddingMode.PKCS7
to use
rj.Padding = PaddingMode.Zeros
Also, technically, your two functions aren't returning the same thing. The PHP returns base 64 of two concatenated bits of binary data whereas the C# returns a concatenation of separate b64 strings. The result will be different in the second half of the returned string.
EDIT: The rough and ready decryption routine:
public string DecryptRijndael(byte[] cipherText, string password, byte[] iv)
{
var key = new byte[32];
Encoding.UTF8.GetBytes(password).CopyTo(key, 0);
var cipher = new RijndaelManaged();
cipher.Mode = CipherMode.CBC;
cipher.Padding = PaddingMode.None;
cipher.KeySize = 256;
cipher.BlockSize = 256;
cipher.Key = key;
cipher.IV = iv;
byte[] plain;
using (var decryptor = cipher.CreateDecryptor())
{
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Write))
{
cs.Write(cipherText, 0, cipherText.Length);
cs.FlushFinalBlock();
plain = ms.ToArray();
}
}
}
return Encoding.UTF8.GetString(plain);
}
NB: All the caveats and warnings from Artjom B still apply.
You're using an old version of PHP which happily accepts keys that have an invalid length. Rijndael supports key sizes of 16, 24 and 32 bytes and nothing inbetween. The mcrypt extension in PHP silently pads the key with 0x00 bytes up to the next valid key size which is 32 bytes. You will have to do the same thing in C#:
byte[] key = new byte[32];
byte[] password = Encoding.UTF8.GetBytes(encryptionKey);
Array.Copy(password, key, password.Length);
rj.Key = key;
Keep in mind that in order to provide some security a key must have high entropy. A password is not a key and therefore doesn't provide much entropy, because of the limited character set and possible use words. Always derive a key from the password with available derivation functions such as Argon2, scrypt, bcrypt or PBKDF2 with a high cost factor/iteration count and a random salt.
You should also add authentication to your ciphertexts. Otherwise, an attacker might change the ciphertext without you knowing it. This either done by using an authenticated mode like GCM/EAX or running HMAC over the ciphertext to produce the authentication tag.
I am trying to test my cryptography code and keep getting the CyrptographicException "Padding is invalid and cannot be removed" when ever decrypt is disposed. I get a similar error if I try to read past the end of the decrypt stream, even though the CryptoStream.Read documentation indicates that this should not be an issue.
A simplified example:
const int DATA_SET_SIZES = 102399;
byte[] iv,
key,
startingData = new byte[DATA_SET_SIZES],
encryptedData = new byte[((DATA_SET_SIZES - 1) / 16 + 2) * 16],
endingData = new byte[DATA_SET_SIZES];//[((DATA_SET_SIZES - 1) / 16 + 1) * 16];
Random rand = new Random();
rand.NextBytes(startingData); //Get test data.
using (Rijndael cryptAlg = Rijndael.Create())
{
cryptAlg.Mode = CipherMode.CBC;
cryptAlg.Padding = PaddingMode.ISO10126;
iv = cryptAlg.IV; //Use random IV during test.
key = cryptAlg.Key; //Use random Key during test.
using (CryptoStream encrypt = new CryptoStream(new MemoryStream(encryptedData), cryptAlg.CreateEncryptor(key, iv), CryptoStreamMode.Write))
{
encrypt.Write(startingData, 0, startingData.Length);
encrypt.FlushFinalBlock();
}
using (CryptoStream decrypt = new CryptoStream(new MemoryStream(encryptedData), cryptAlg.CreateDecryptor(key, iv), CryptoStreamMode.Read))
{
int dataRecieved = decrypt.Read(endingData, 0, endingData.Length);
}
}
If I do one of the following then the exception goes away:
Change cryptAlg.Padding = PaddingMode.None after performing the encryption but before creating decrypt.
Change const int DATA_SET_SIZES = 102400 or any other multiple of the block size.
Do not read any data from the last block.
Am I doing something wrong or does the .NET implementation not recognize the end of the stream correctly?
Also, does anyone know why the encrypted data is 1 block longer then would be needed to store the encrypted data? What is in that block?
Your encryptedData buffer is too large. Take input 15 bytes, then you get 32 blocks of buffer back. Since you give the full buffer to the MemoryStream constructor, it will read up to the end of the stream. Block decryption will never fail, so the only thing that will fail is the padding. The last block probably only contains zero's, so the decrypted value is random instead of matching the padding format (most of the time).
Try: new byte[(DATA_SET_SIZES / 16 +1) * 16]
C#
string keystr = "0123456789abcdef0123456789abcdef";
string plainText = "www.bouncycastle.org";
RijndaelManaged crypto = new RijndaelManaged();
crypto.KeySize = 128;
crypto.Mode = CipherMode.CBC;
crypto.Padding = PaddingMode.PKCS7;
crypto.Key = keystr.ToCharArray().Select(c=>(byte)c).ToArray();
// get the IV and key for writing to a file
byte[] iv = crypto.IV;
byte[] key = crypto.Key;
// turn the message into bytes
// use UTF8 encoding to ensure that Java can read in the file properly
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText.ToCharArray());
// Encrypt the Text Message using AES (Rijndael) (Symmetric algorithm)
ICryptoTransform sse = crypto.CreateEncryptor();
MemoryStream encryptedFs = new MemoryStream();
CryptoStream cs = new CryptoStream(encryptedFs, sse, CryptoStreamMode.Write);
try
{
cs.Write(plainBytes, 0, plainBytes.Length);
cs.FlushFinalBlock();
encryptedFs.Position = 0;
string result = string.Empty;
for (int i = 0; i < encryptedFs.Length; i++)
{
int read = encryptedFs.ReadByte();
result += read.ToString("x2");
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
finally
{
encryptedFs.Close();
cs.Close();
}
}
Java:
private String key = "0123456789abcdef0123456789abcdef";
private String plainText = "www.bouncycastle.org";
cipherText = performEncrypt(Hex.decode(key.getBytes()), plainText);
private byte[] performEncrypt(byte[] key, String plainText)
{
byte[] ptBytes = plainText.getBytes();
final RijndaelEngine rijndaelEngine = new RijndaelEngine();
cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(rijndaelEngine));
String name = cipher.getUnderlyingCipher().getAlgorithmName();
message("Using " + name);
byte[]iv = new byte[16];
final KeyParameter keyParameter = new KeyParameter(key);
cipher.init(true, keyParameter);
byte[] rv = new byte[cipher.getOutputSize(ptBytes.length)];
int oLen = cipher.processBytes(ptBytes, 0, ptBytes.length, rv, 0);
try
{
cipher.doFinal(rv, oLen);
}
catch (CryptoException ce)
{
message("Ooops, encrypt exception");
status(ce.toString());
}
return rv;
}
C# produces: ff53bc51c0caf5de53ba850f7ba08b58345a89a51356d0e030ce1367606c5f08
java produces: 375c52fd202696dba679e57f612ee95e707ccb05aff368b62b2802d5fb685403
Can somebody help me to fix my code?
In the Java code, you do not use the IV.
I am not savvy enough in C# to help you directly, but I can give some information.
Rijndael, aka "the AES", encrypts blocks of 16 bytes. To encrypt a long message (e.g. your test message, when encoding, is 20 bytes long), Rijndael must be invoked several times, with some way to chain the invocations together (also, there is some "padding" to make sure that the input length is a multiple of 16). The CBC mode performs such chaining.
In CBC, each block of data is combined (bitwise XOR) with the previous encrypted block prior to being itself encrypted. Since the first block of data has no previous block, we add a new conventional "zero-th block" called the IV. The IV should be chosen as 16 random bytes. The decrypting party will need the IV. The IV needs not be secret (that's the difference between the IV and the key) so it is often transmitted along the message.
In your Java code, you do not specify the IV, you just create a variable called iv and do not use it. So the Rijndael implementation is on its own for that. Chances are that it generated a random IV. Similarly, you do not give an IV to the Rijndael implementation in the C# code. So it is quite plausible that there again a random IV was selected. But not the same than the one in the Java code, hence the distinct results.
(Note: you 20-byte input string is padded to 32 bytes. You give two "results" in hexadecimal, of length 32 bytes each. This is coherent but means that those results do not include the IV -- otherwise they would be 48-byte long.)
I think the algorithm is built in slighty different way and/or the salt key is interpered in different way.