J
Jon Larimer
Hello,
When using RijndaelManaged in CBC mode, I believe that
ICryptoTransform.TransformFinalBlock() is behaving "wrong". After a
call to TransformFinalblock is made, it seems like the class is
resetting the CBC state with the intial IV. This makes it act more
like ECB mode.
I was porting a .NET app to the Mono runtime in Linux and noticed that
network traffic encrypted using RijndaelManaged in Windows on .NET
would get decrypted wrong when the other side of the connection was
running in Mono. I figured out the problem is because I was using
TransformFinalBlock to encrypt the last block of each packet: .NET's
library would reset the CBC state with the initial IV and Mono didn't,
so Mono couldn't decrypt any packets after the first one received.
I believe Mono's behavior was more correct in this case... I don't
think the CBC state should ever be reset within a single instance of
an ICryptoTransform. Using the .NET Framework's TransformFinalBlock()
to encrypt the final block of plaintext, you can encrypt the same
plaintext over and over and it will result in the exact same
ciphertext, which shouldn't happen in CBC.
I have some code below that demonstrates the problem. If you run it,
you'll see that the same block encrypted twice in a row with
TransformFinalBlock gives the same crypto text, which is more like the
behavior of ECB than CBC transforms... it seems like it could be a
security issue, depending on how developers use the library.
Is this a bug? If not, I think the behavior should be documented in
MSDN.
NOTE: I was using version 1.1 of the Framework, I haven't tried with
2.0.
Thanks,
-jon
Jon Larimer
jlarimer /// gmail.com
CODE:
---8<---snip---8<------
// TransformTest.cs
// Jon Larimer <jlarimer /// gmail.com>
using System;
using System.Security.Cryptography;
public class TransformTest
{
static void Main(string[] args)
{
RijndaelManaged rij = new RijndaelManaged();
rij.KeySize = 128;
rij.BlockSize = 128;
rij.Mode = CipherMode.CBC;
rij.Padding = PaddingMode.None;
rij.IV = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
rij.Key = new byte[] { 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09,
0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00 };
byte[] input = new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB,
0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF };
// First perform 2 encryptions using TransformBlock() and print the
results
ICryptoTransform encrypt = rij.CreateEncryptor();
Console.WriteLine("TransformBlock test:");
TransformBlockTest(input, encrypt);
Console.WriteLine();
// With a fresh encryptor, perform 2 encryptions using
TransformFinalBlock() and print the results
encrypt = rij.CreateEncryptor();
Console.WriteLine("TransformFinalBlock test:");
TransformFinalBlockTest(input, encrypt);
}
static void TransformBlockTest(byte[] input, ICryptoTransform
encrypt) {
byte[] test1 = new byte[input.Length];
encrypt.TransformBlock(input, 0, input.Length, test1, 0);
byte[] test2 = new byte[input.Length];
encrypt.TransformBlock(input, 0, input.Length, test2, 0);
Console.WriteLine(" First round of CBC:");
Hexdump(test1);
Console.WriteLine(" Second round of CBC:");
Hexdump(test2);
}
static void TransformFinalBlockTest(byte[] input, ICryptoTransform
encrypt) {
byte[] test1 = encrypt.TransformFinalBlock(input, 0, input.Length);
byte[] test2 = encrypt.TransformFinalBlock(input, 0, input.Length);
Console.WriteLine(" First round of CBC:");
Hexdump(test1);
Console.WriteLine(" Second round of CBC:");
Hexdump(test2);
}
static void Hexdump(byte[] data) {
Console.Write(" ");
for(int i=0; i<data.Length; i++) {
Console.Write("{0:X2} ", data);
if((i+1)%16 == 0) Console.WriteLine("\n ");
}
}
}
When using RijndaelManaged in CBC mode, I believe that
ICryptoTransform.TransformFinalBlock() is behaving "wrong". After a
call to TransformFinalblock is made, it seems like the class is
resetting the CBC state with the intial IV. This makes it act more
like ECB mode.
I was porting a .NET app to the Mono runtime in Linux and noticed that
network traffic encrypted using RijndaelManaged in Windows on .NET
would get decrypted wrong when the other side of the connection was
running in Mono. I figured out the problem is because I was using
TransformFinalBlock to encrypt the last block of each packet: .NET's
library would reset the CBC state with the initial IV and Mono didn't,
so Mono couldn't decrypt any packets after the first one received.
I believe Mono's behavior was more correct in this case... I don't
think the CBC state should ever be reset within a single instance of
an ICryptoTransform. Using the .NET Framework's TransformFinalBlock()
to encrypt the final block of plaintext, you can encrypt the same
plaintext over and over and it will result in the exact same
ciphertext, which shouldn't happen in CBC.
I have some code below that demonstrates the problem. If you run it,
you'll see that the same block encrypted twice in a row with
TransformFinalBlock gives the same crypto text, which is more like the
behavior of ECB than CBC transforms... it seems like it could be a
security issue, depending on how developers use the library.
Is this a bug? If not, I think the behavior should be documented in
MSDN.
NOTE: I was using version 1.1 of the Framework, I haven't tried with
2.0.
Thanks,
-jon
Jon Larimer
jlarimer /// gmail.com
CODE:
---8<---snip---8<------
// TransformTest.cs
// Jon Larimer <jlarimer /// gmail.com>
using System;
using System.Security.Cryptography;
public class TransformTest
{
static void Main(string[] args)
{
RijndaelManaged rij = new RijndaelManaged();
rij.KeySize = 128;
rij.BlockSize = 128;
rij.Mode = CipherMode.CBC;
rij.Padding = PaddingMode.None;
rij.IV = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
rij.Key = new byte[] { 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09,
0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00 };
byte[] input = new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB,
0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF };
// First perform 2 encryptions using TransformBlock() and print the
results
ICryptoTransform encrypt = rij.CreateEncryptor();
Console.WriteLine("TransformBlock test:");
TransformBlockTest(input, encrypt);
Console.WriteLine();
// With a fresh encryptor, perform 2 encryptions using
TransformFinalBlock() and print the results
encrypt = rij.CreateEncryptor();
Console.WriteLine("TransformFinalBlock test:");
TransformFinalBlockTest(input, encrypt);
}
static void TransformBlockTest(byte[] input, ICryptoTransform
encrypt) {
byte[] test1 = new byte[input.Length];
encrypt.TransformBlock(input, 0, input.Length, test1, 0);
byte[] test2 = new byte[input.Length];
encrypt.TransformBlock(input, 0, input.Length, test2, 0);
Console.WriteLine(" First round of CBC:");
Hexdump(test1);
Console.WriteLine(" Second round of CBC:");
Hexdump(test2);
}
static void TransformFinalBlockTest(byte[] input, ICryptoTransform
encrypt) {
byte[] test1 = encrypt.TransformFinalBlock(input, 0, input.Length);
byte[] test2 = encrypt.TransformFinalBlock(input, 0, input.Length);
Console.WriteLine(" First round of CBC:");
Hexdump(test1);
Console.WriteLine(" Second round of CBC:");
Hexdump(test2);
}
static void Hexdump(byte[] data) {
Console.Write(" ");
for(int i=0; i<data.Length; i++) {
Console.Write("{0:X2} ", data);
if((i+1)%16 == 0) Console.WriteLine("\n ");
}
}
}