5

I'm having trouble encrypting a value from a third party vendor I am using.

Their instructions are as follows:

1) Convert the encryption password to a byte array.
2) Convert the value to be encrypted to a byte array.
3) The entire length of the array is inserted as the first four bytes onto the front 
   of the first block of the resultant byte array before encryption.
4) Encrypt the value using AES with:
        1. 256-bit key size,
        2. 256-bit block size, 
        3. Encryption Mode ECB, and
        4. an EMPTY initialization vector.
5) After encryption, you should now have a byte array that holds the encrypted value. 
6) Convert each byte to a HEX format and string all the HEX values together.
7) The final result is a string of HEX values. This is the final encrypted value to be passed. 
   The length of the final value will always be an even number.

EXAMPLE:
Given the following input values:
plainText: 2017/02/07 22:46
secretKey: ABCD1234FGHI5678
The following string will be produced:
D6281D5BE6CD6E79BB41C039F4DD020FBEC9D290AD631B2598A6DFF55C68AD04

What I've tried so far...

plain_text = "2017/02/07 22:46"
secret_key = "ABCD1234FGHI5678"

plain_text_byte_array = plain_text.bytes
plain_text_byte_array.unshift(0).unshift(0).unshift(0).unshift(16) # I found a Java example in their documentation and this is what they do. They prepend their byte array with 16, 0, 0, 0
secret_byte_array = secret_key.bytes
secret_byte_array = secret_byte_array.concat([0, 0, 0,...]) # also from their java example, they append the secret_byte array with 16 0's in order to get its length to 32

cipher = OpenSSL::Cipher::AES256.new(:ECB)
cipher.key = secret_byte_array.pack("C*")
encrypted = cipher.update(plain_text_byte_array.pack("C*")) + cipher.final

p encrypted.unpack("H*").first.to_s.upcase

# Result is: 
#    "84A0E5DCA7D704C41332F86E707DDAC244A1A87C38A906145DE4060D2BC5C8F4"

As you can see my result is off from the actual result which should be "D6281D5BE6CD6E79BB41C039F4DD020FBEC9D290AD631B2598A6DFF55C68AD04"

Does anyone know if I am missing something or doing something strange. Their instructions were difficult for me to parse through so maybe I'm missing something. Thank you for any help anyone can provide! (I've tried a ton of different variations on what you see above). I just need some guidance or at least someone to tell me I'm not crazy for not understanding their instructions.

Gabriel
  • 621
  • 7
  • 19
  • 1
    As a way to do this without byte mashing: `plain_text << "\x00\x00\x00\x10"` and `secret_key << ("\x00" * 16)`. – tadman Dec 20 '18 at 23:57
  • 6
    The people who wrote those instructions have a very limited understanding of Cryptography in general. It's like reading a Cake recipe written by a Rabbit. – Luke Joshua Park Dec 21 '18 at 00:49
  • 2
    More to the point, AES with a 256-bit block size is nonsensical. The actual algorithm that this service provider is using is called *Rijndael* - AES is a subset of this. You won't be able to get the correct result using an AES implementation. Instead, look for an implementation of Rijndael that you can use, and set the block size to 256 bits. AES **always** has a block-size of 128 bits. – Luke Joshua Park Dec 21 '18 at 00:53

2 Answers2

5

I managed to reproduce their result - the process they've used is extremely convoluted and as far from elegant as it could possibly be. I've attached a far more descriptive explanation of the steps required to achieve their result, and the C# source code I used to do so.

  1. Convert the password to a byte array. The byte array must be 32 bytes in length, and, if the password is not long enough, should be right-padded with 0 bytes. Thus their password, hex-encoded, becomes 4142434431323334464748493536373800000000000000000000000000000000.

  2. Convert the value to be encrypted to a byte array. This one is simple enough, just encode with UTF-8.

  3. The entire length of the array is inserted as the first four bytes onto the front of the first block of the resultant byte array before encryption. This is stupid and serves no purpose, but take the length of the byte array from step 2 as an unsigned 32-bit integer and convert to a little endian byte array. Prefix this to the array from step 2.

  4. Encrypt the value using AES. Uhm. No don't do that. Encrypt the value with Rijndael, using a 256-bit block size, 256-bit key size, ECB mode and zero's for padding.

  5. The rest is easy, just convert the result of encryption to hex.

The code I used to achieve this result is below, in C#. I don't know Ruby all that well sorry.

    // 1. Convert the encryption password to a byte array.
    byte[] passwordBytesOriginal = Encoding.UTF8.GetBytes("ABCD1234FGHI5678");
    byte[] passwordBytes = new byte[32];
    Array.Copy(passwordBytesOriginal, 0, passwordBytes, 0, passwordBytesOriginal.Length);


    // 2. Convert the value to be encrypted to a byte array.
    byte[] valueBytes = Encoding.UTF8.GetBytes("2017/02/07 22:46");

    // 3. The entire length of the array is inserted as the first four bytes onto the front 
    // of the first block of the resultant byte array before encryption.
    byte[] valueLengthAsBytes = BitConverter.GetBytes((uint)valueBytes.Length);
    byte[] finalPlaintext = new byte[valueBytes.Length + valueLengthAsBytes.Length];
    Array.Copy(valueLengthAsBytes, 0, finalPlaintext, 0, valueLengthAsBytes.Length);
    Array.Copy(valueBytes, 0, finalPlaintext, valueLengthAsBytes.Length, valueBytes.Length);

    // 4. Encrypt the value using AES...
    byte[] ciphertext;
    using (RijndaelManaged rijn = new RijndaelManaged())
    {
        rijn.BlockSize = 256;
        rijn.KeySize = 256;
        rijn.Key = passwordBytes;
        rijn.Mode = CipherMode.ECB;
        rijn.Padding = PaddingMode.Zeros;

        var encryptor = rijn.CreateEncryptor();
        ciphertext = encryptor.TransformFinalBlock(finalPlaintext, 0, finalPlaintext.Length);
    }

    // 5., 6., 7...
    string result = BitConverter.ToString(ciphertext).Replace("-", "").ToUpper();
    Console.WriteLine(result); // D6281D5BE6CD6E79BB41C039F4DD020FBEC9D290AD631B2598A6DFF55C68AD04
Luke Joshua Park
  • 9,527
  • 5
  • 27
  • 44
  • Thanks for the answer! I haven't been successful in implementing this in Ruby so far. What I've determined is that OpenSSL, the library I was using to encrypt doesn't use the 256-bit block size, it only does the 256 bit key size. I have been unsuccessful at finding a Ruby library equivalent of RijndaelManaged. I believe I would have to implement Rijndael myself in ruby in order to get this to work for the Ruby side of things. – Gabriel Dec 26 '18 at 16:03
  • 1
    You could always just use a different language and read/write stdin/stdout? How critical is the speed behind it? Can't be too bad if you're using Ruby? – Luke Joshua Park Dec 26 '18 at 18:27
  • Thanks for the suggestion. That crossed my mind as well. Speed is pretty important but I think we can find tradeoffs to accomplish this task in order for speed to not be important. I think that route is a last resort for us though. It is pretty frustrating that the third party vendor is using an encryption method that isn't really supported anymore or secure as I can't find a library/gem to accomplish this. I'll talk it over with my team and see what we can do. Thanks for your help! Your answer helped me understand why what I was doing wasn't working. – Gabriel Dec 26 '18 at 20:05
  • 1
    Yeah the 3rd party just have no idea what they are doing. Just to clarify your understanding, Rijndael in itself is a perfectly secure algorithm - the way they've used it is the problem (ECB mode, zero padding etc.). Rijndael is definitely supported, it's just uncommon to use it with a 256 bit block size since AES is so incredibly well known and common-place (the 128-bit block size of Rijndael). – Luke Joshua Park Dec 26 '18 at 20:09
4

Based on Luke's excellent answer here is the Ruby version. I had to use the ruby-mcrypt gem and install the mcrypt library locally using brew install libmcrypt.

It is as Luke's answer points out that the secret key should be right padded with 0's. Here is my code:

   plain_text = "2017/02/07 22:46"
   secret_text = "ABCD1234FGHI5678"
   answer = "D6281D5BE6CD6E79BB41C039F4DD020FBEC9D290AD631B2598A6DFF55C68AD04"

   def format_byte_arrays(plain, secret)
     zero_byte_array = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
     length_array = [16, 0, 0, 0]

     plain_bytes = length_array.concat(plain.bytes)
     secret_bytes = secret.bytes.concat(zero_byte_array)

     [plain_bytes, secret_bytes]
   end

   plain_bytes, secret_bytes = format_byte_arrays(plain_text, secret_text)
   final_plain, final_secret = [plain_bytes.pack("C*"), secret_bytes.pack("C*")]

   cipher = Mcrypt.new("rijndael-256", :ecb, final_secret, nil, :zeros)
   encrypted = cipher.encrypt(final_plain)
   result = encrypted.unpack("H*").first.to_s.upcase

The result will be the correct answer.

Gabriel
  • 621
  • 7
  • 19