Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw
 
PerlMonks  

AES Interoperability between Perl and C#

by jpfarmer (Pilgrim)
on Nov 16, 2005 at 23:09 UTC ( [id://509233]=perlquestion: print w/replies, xml ) Need Help??

jpfarmer has asked for the wisdom of the Perl Monks concerning the following question:

I am trying to implement a SOAP service which will be called by a C# client and will be passed an AES-encrypted string. However, Iím running into considerable difficulty with this and Iím hoping I can find a Perl and C# guru who can help me out.

I want to start out by saying Iím not very experienced with cryptography, so that is no doubt making this more difficult.

On the Perl side, Iím using Crypt::CBC and Crypt::Rijndael. This works fine when Perl is providing data to Perl, but Crypt::CBC expects an 8-byte initialization vector, while C# requires one that is 16-byte and I donít know how to work around this. I imagine there are other problems as well, but Iíve got to get past this one first.

Any help would be greatly appreciated.

  • Comment on AES Interoperability between Perl and C#

Replies are listed 'Best First'.
Re: AES Interoperability between Perl and C#
by Thelonius (Priest) on Nov 17, 2005 at 04:06 UTC
    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)); } }

      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.

      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.

Re: AES Interoperability between Perl and C#
by ikegami (Patriarch) on Nov 16, 2005 at 23:25 UTC

    It appears that Crypt::Rijndael's set_iv (16 byte IV) should be used instead of Crypt::CBC's set_initialization_vector (8 byte IV).

    Update: That's apparently easier said than done, when using Crypt::CBC. I'm looking into it.

    Update: Sorry, I don't see how Crypt::Rijndael (from Crypt-Rijndael-0.05) can possibly be compatible with Crypt::CBC (from Crypt-CBC-2.15) as it claims. Not necessarily by choice, mind you. Crypt::CBC is fixated on 8 byte blocks.

    Crypt::Rijndael without Crypt::CBC seems to do CBC just fine, but it doesn't provide a mechanism to pad the data to encrypt to a multiple of 16 bytes, and it doesn't provide a mechanism to remove padding from decrypted data. And if you have to send/extract the IV... not handled either.

      There! Figured it out!

      There is an IV in Crypt::Rijndael and there is a different one in Crypt::CBC. Previously, I thought there was a conflict between the two. However, I finally realized the one in Crypt::Rijndael is not used when Crypt::Rijndael is used through Crypt::CBC. Crypt::CBC uses Crypt::Rijndael in ECB mode. In ECB mode, Crypt::Rijndael doesn't use its IV, leaving Crypt::CBC free to use its IV.

      So why must the Crypt::CBC IV be 8 bytes long? It turns out that Crypt::CBC works with 16 byte blocks with Crypt::Rijndael, so the IV must be 16 bytes long for Crypt::CBC too! It turns out the IV length check is bogus.

      So all you need to do is to bypass the IV length check. It's actually quite simple: Use the method iv instead of the method get_initialization_vector and set_initialization_vector, or use the iv argument to the Crypt::CBC constructor. These don't check the length of the supplied IV. For example:

      use Crypt::CBC; $cipher = Crypt::CBC->new( -cipher => 'Rijndael', ... ); $cipher->iv($iv); $ciphertext = $cipher->encrypt("This data is hush hush"); $plaintext = $cipher->decrypt($ciphertext);
      or
      use Crypt::CBC; $cipher = Crypt::CBC->new( -cipher => 'Rijndael', -iv => $iv, ... ); $ciphertext = $cipher->encrypt("This data is hush hush"); $plaintext = $cipher->decrypt($ciphertext);

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

        Looks like that got me past the IV problem, but I still can't get a clean decryption of the AES output from C#. I'm starting to wonder if Crypt::CBC is just plain broken for Crypt::Rijndael. I ran the C# code that Thelonius provided and then passed the generated string back through Crypt::CBC with the correct IV set and it still didn't decrypt properly.

        While I'd have to implement padding and IV extraction by hand if I don't use the module (which is not ideal) I don't know what else to do if the module won't do it properly.

        Found the error; Thelonius was using the key literally to encrypt his data, while I was using a hash of the key to decrypt it. When I told Crypt::CBC to use the key literally, it worked!

Re: AES Interoperability between Perl and C#
by zentara (Archbishop) on Nov 17, 2005 at 18:48 UTC
    This is a related node, where frodo72 shows how to make rc4 compatible between C and Perl. Maybe the unpacking methods he uses could be useful. perl encryptions keys vs. c

    I'm not really a human, but I play one on earth. flash japh

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://509233]
Approved by GrandFather
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others perusing the Monastery: (6)
As of 2024-07-15 08:30 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?
    erzuuli‥ 🛈The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.