ICryptoTransform.TransformFinalBlock behavior (bug?)

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 ");
}
}
}
 
R

Rob Teixeira

The behaviour you described is correct. TransformFinalBlock does indeed
reset the feedback.
I believe this is consistent with the intent of the class. The
CanReuseTransform property returns true, which means the feedback should at
some point be reset so the class can be reused.
You have to look at this from the perspective of individual messages. Once
you end a message, you must call TransformFinalBlock, and this resets the
feedback register to the IV for the next message. You have to remember that
message reception may or may not be synchronous, so for example, you can
encrypt various files and they will simply sit somewhere until you decrypt
one of them. You can't assume that you are shipping a bunch of message
around and they will all be dealt with in the same order. If the feedback
isn't reset, that's the only way you could ever decrypt them.

Also, CBC mode induces feedback. This means that subsequent strings of
similar plain text will be affected by previous blocks of cipher text. So,
for example, if you had repeating sets of the letter "E" in your message,
the blocks containing that repetition couldn't be detected from the cipher
text because of the CBC feedback. CBC doesn't guarantee that entirely
similar messages won't look the same after decryption, especially if you are
using the same IV. If you have a scenario like this (where you are sending
the same message over and over using the same IV), you should salt the
message - add a block of random bits to the beginning of the message. On the
receiving end, you can safly ignore the first block, and that guarantees
each message will be different due to the CBC feedback.

-Rob Teixeira

Jon Larimer said:
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 ");
}
}
}
 
Top