Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails Encryption using AES, overcomplicated

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.

like image 360
Gabriel Avatar asked Dec 20 '18 23:12

Gabriel


2 Answers

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
like image 84
Luke Joshua Park Avatar answered Nov 03 '22 09:11

Luke Joshua Park


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.

like image 31
Gabriel Avatar answered Nov 03 '22 11:11

Gabriel