in reply to AES Interoperability between Perl and C#

I have hardly used C# at all, so this is not great code. I did it just now as a learning experience, cobbling it together from the MSDN examples. I piped the out of the C# program below to the input of this Perl program:
use MIME::Base64; use Crypt::Rijndael; use bytes; use strict; use warnings; $_ = <>; my $in = decode_base64($_); my $key = pack("H*", "01020304050607080910111213141516"); my $cipher = new Crypt::Rijndael $key, Crypt::Rijndael::MODE_CBC or die "Error: $!"; $cipher->set_iv(substr($in, 0, 16)); print "out = '", unpack("N/A", $cipher->decrypt(substr($in, 16))), "'\n";
And here is the C# program:
using System; using System.IO; using System.Text; using System.Security.Cryptography; using System.Net; public class main { public static void Main(string[] args) { byte[] key = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16}; byte[] IV; string original = "This is a test."; ASCIIEncoding textConverter = new ASCIIEncoding(); RijndaelManaged myRijndael = new RijndaelManaged(); byte[] encrypted; byte[] toEncrypt; byte[] encLen; myRijndael.Mode = CipherMode.CBC; //Create a new initialization vector. myRijndael.GenerateIV(); IV = myRijndael.IV; //Get an encryptor. ICryptoTransform encryptor = myRijndael.CreateEncryptor(key, IV); //Encrypt the data. MemoryStream msEncrypt = new MemoryStream(); // I'm using base64 encoding CryptoStream b64 = new CryptoStream(msEncrypt, new ToBase64Transform(), CryptoStreamMode.Write); b64.Write(IV, 0, IV.Length); // Write IV before encryption CryptoStream csEncrypt = new CryptoStream(b64, encryptor, CryptoStreamMode.Write); //Convert the data to a byte array. toEncrypt = textConverter.GetBytes(original); // Get the string length and convert to bytes in network order encLen = BitConverter.GetBytes( IPAddress.HostToNetworkOrder(toEncrypt.Length)); csEncrypt.Write(encLen, 0, encLen.Length); //Write all data to the crypto stream and flush it. csEncrypt.Write(toEncrypt, 0, toEncrypt.Length); csEncrypt.FlushFinalBlock(); //Get encrypted array of bytes. encrypted = msEncrypt.ToArray(); // Here we write to console, but of course you // may want to include it in an XML response Console.WriteLine(textConverter.GetString(encrypted)); } }

Replies are listed 'Best First'.
Re^2: AES Interoperability between Perl and C#
by ikegami (Patriarch) on Nov 17, 2005 at 07:39 UTC

    Your code doesn't work if the cleartext is not an exact multiple of 16 bytes in length (and maybe even if it is), because Crypt::Rijndael doesn't handle padding. Crypt::CBC handles that.

    Your Perl code manually prepends/extracts the IV to/from the encrypted text. Crypt::CBC handles that.

    Crypt::CBC has other features too, such as generating a random IV when so desired.

    That's why Crypt::Rijndael should be in conjunction with Crypt::CBC instead of using Crypt::Rijndael's CBC mode.

    The fix (and simplification) is:

    use strict; use warnings; use MIME::Base64; use Crypt::CBC; my $key = pack("H*", "01020304050607080910111213141516"); my $cipher = Crypt::CBC->new(-cipher => 'Rijndael'); my $in = decode_base64(<>); print "out = '", unpack("N/A", $cipher->decrypt($in)), "'\n";

    By the way, I removed use bytes since it was useless since you didn't use any numbers except the constants 0 and 16.

    Also, I don't think Crypt::Rijndael's new sets $! (or ever returns false).

    (Untested. I don't have these modules.)

      My program works fine for strings that are not multiples of 16 bytes.

      I could not get Crypt:CBC to work in a compatible way. I might have gotten in to work by experimenting with padding and the IV on both sides, but since my method works, there didn't seem to be any advantage.

        Oh I see! You (redundantly) include the length of the plaintext, and require extra code on the decryption side to remove the padding. If you used Crypt::CBC, you wouldn't need to include the length of the plaintext in the ciphertext.

        Caveat: I'm assuming FlushFinalBlock uses a padding method compatible with Crypt::CBC's. Well, Crypt::CBC since Crypt::CBC can handle any padding, so I guess I meant "compatible with the default Crypt::CBC padding method".

Re^2: AES Interoperability between Perl and C#
by jpfarmer (Pilgrim) on Nov 17, 2005 at 22:03 UTC

    Thank you for the code sample. One part I'm curious about in the C# segment. Do you understand the significance of this block?

    encLen = BitConverter.GetBytes( IPAddress.HostToNetworkOrder(toEncrypt.Length)); csEncrypt.Write(encLen, 0, encLen.Length);

    When I comment it out, the encrypted string becomes much shorter, but it's still decryptable.

      There's two common ways of storing a string.

      1) NUL-terminated.

      The end of the string is marked by the first occurance of a NUL character.

      Pro: The string can be of any length.
      Con: The string cannot contain NULs.

      2) PASCAL string.

      Named after the programming language. The length of the string is stored along with the string itself.

      Pro: The string can contain any character.
      Con: The size of the length field limits the size of the string.

      Thelonius chose the latter. And in this case, the limit on the string length is (2^32)-1 (over 2 billion) since he's using a 32 bit signed int for the length field.

        So by commenting that out, I'm forcing the NUL-terminated mode?