Aes Cbc Pkcs5Padding Java to Ruby

AES/CBC/PKCS5Padding encrypt in java decrypt in ruby

There's two problems with your Ruby code.

First, you're using AES 256 when you should be using AES 128. Java uses AES 128 or 256 based on the size of the key you use, and you're using a 128 bit key.

Second, you need to Hex decode your key, iv, and encrypted_string values in Ruby. OpenSSL Cipher is expecting binary, not hex strings.

require 'openssl';

key = "97128424897797a166913557a6f4cc8e";
iv = "84e8c3ea8859a0e293941d1cb00a39c3";
encrypted_string = "395f6c0e8ad27f57c4a5a8975aa633e5b26f288d37ce18c6971779951f3b3527";

de_cipher = OpenSSL::Cipher::Cipher.new("AES-128-CBC");
de_cipher.decrypt;
de_cipher.key = [key].pack('H*');
de_cipher.iv = [iv].pack('H*');

puts de_cipher.update([encrypted_string].pack('H*')) << de_cipher.final;

Output:

{"timestamp":"1377499097199"}

AES CBC PKCS5Padding Java to Ruby

The code above works just fine. The problem was in the encoding of the base64 sent through a post request.

Java to ruby AES/ECB/PKCS5Padding encryption

Using ECB mode for tamper-proofing input is very stupid.

Having said that, and knowing it's not your fault, because it was not your idea in the first place, and that you just want the code to work, let's ask an independent party to give us a reference point:

echo -n "amount=10&expiryDate=20150101151515&orderRefNum=11001&postBackURL=http://localhost:9081/local/status.php&storeId=28" | openssl enc -K 38394f5549545550524c334938483347 -aes-128-ecb -base64

Note that openssl takes the key as hexadecimal string, so 89OUITUPRL4I9H3G should be written as its ASCII sequence 38394f5549545550524c334938483347

The output is:

r7N11xE4HdbJyTByiTDifI1vifvZyNcNfKF+Jo7jEq4rN7c3EiOJxdWOUlCtVXeH
FBTdPSROSmTkUTWfAuOQnHWqe/q/Msd1ykUDIz9eP5L6X6RI0R5UtUXmaakr4klz
1kxEJOjR/WJ5xgd2clBh4iLcYi3caDrCkbD0kRDLQE4=

Let's try to replicate that in Java. To do this, we have to change a few things in your code:

  1. Your expiryDate is 20150101 151515 in the Java code, but 20150101151515 everywhere else. So let's standardize on 20150101151515
  2. Base64.encodeBase64() does not exist. Java 8 has Base64 encoding built-in, and the code should be Base64.getEncoder().encodeToString(data)
  3. The return type of that is already string so encryptedValue = new String(Base64...) is unnecessary.
  4. Furthermore, you need to declare the type of encryptedValue before you can use it.

With all that, this compiles in Java 8:

import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class encryptData {
public static void main(String[] args) throws Exception {

String data="amount=10&expiryDate=20150101151515&orderRefNum=11001&postBackURL=http://localhost:9081/local/status.php&storeId=28";
String key="89OUITUPRL3I8H3G";

Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);

byte[] plaintext = data.getBytes();
byte[] ciphertext = cipher.doFinal(plaintext);
String encryptedValue = Base64.getEncoder().encodeToString(ciphertext);

System.out.println(encryptedValue);
}
}

and prints (linebreaks added by me):

r7N11xE4HdbJyTByiTDifI1vifvZyNcNfKF+Jo7jEq4rN7c3EiOJxdWOUlCtVXeH
FBTdPSROSmTkUTWfAuOQnHWqe/q/Msd1ykUDIz9eP5L6X6RI0R5UtUXmaakr4klz
1kxEJOjR/WJ5xgd2clBh4iLcYi3caDrCkbD0kRDLQE4=

Ok so far. What about ruby?

#!/usr/bin/ruby

require 'openssl'
require 'base64'

data = "amount=10&expiryDate=20150101151515&orderRefNum=11001&postBackURL=http://localhost:9081/local/status.php&storeId=28"

key = "89OUITUPRL4I9H3G"
cipher = OpenSSL::Cipher.new("AES-128-ECB")
cipher.encrypt()
cipher.key = key
crypt = cipher.update(data) + cipher.final

crypt_string = (Base64.encode64(crypt))
puts crypt_string

This prints:

mp8WVhyUHFDqvJKaRXbYKbZT1920TNboRpFLUdPaYsWTkiQ2fhN/tCL6wvtI
B9/Mu08McaKTVIWYeQAfVR5XcUKdeQ+CBcJJRs5krLBjtjiMNlBUq9JpCUaC
0eclfDMaGTE+Z4XSafjPictWzTG/Ye+vkJWC23yxW1zSjBnYBfg=

Why is the ruby code not working? Well i suspect that ruby wants the key in the same way as openssl, because ruby crypto usually uses openssl under the hood. So change the key definition to

key = "38394f5549545550524c334938483347"
key = [key].pack('H*')

This now prints:

r7N11xE4HdbJyTByiTDifI1vifvZyNcNfKF+Jo7jEq4rN7c3EiOJxdWOUlCt
VXeHFBTdPSROSmTkUTWfAuOQnHWqe/q/Msd1ykUDIz9eP5L6X6RI0R5UtUXm
aakr4klz1kxEJOjR/WJ5xgd2clBh4iLcYi3caDrCkbD0kRDLQE4=

which apart from linebreak positions is identical to the output of the other two. Hope you'll be able to get communication with the other side right, and remember:

Using ECB mode for tamper-proofing input is very stupid.

AES/CBC/PKCS5Padding implementation in Ruby (for rails)

The Ruby code in my first post is correct, the problem was this AES/CBC/PKCS5Padding used by Java part.

Java program should not use this scheme for AES-CBC-256. PKCS5 pads to a 64 bit (8 byte) block size, but AES-256-CBC uses 16 byte blocks. Therefore, PKCS7 must be used.

Java encryption / decryption to Ruby

You need to use the IV and Key from your Java Example, not a new/random IV/Key:

require "openssl"
require "base64"
require 'byebug'

include Base64

plain_text = "abceeffslaj"

key = 'Bar12345Bar12345'
iv = 'RandomInitVector'

cipher = OpenSSL::Cipher::AES128.new(:CBC)
cipher.encrypt
cipher.key = key
cipher.iv = iv
cipher_text = cipher.update(plain_text) + cipher.final

cipher = OpenSSL::Cipher::AES128.new(:CBC)
cipher.decrypt
cipher.key = key
cipher.iv = iv
decrypted_plain_text = cipher.update(cipher_text) + cipher.final

puts "AES128 in CBC mode"
puts "Key: " + urlsafe_encode64(key)
puts "Iv: " + urlsafe_encode64(iv)
puts "Plain text: " + plain_text
puts "Cipher text: " + urlsafe_encode64(cipher_text)
puts "Decrypted plain text: " + decrypted_plain_text

PHP AES-256-CBC encrypted data is different from JAVA AES/CBC/PKCS5PADDING

There are the following issues:

  • In the PHP code, the key is currently returned hex encoded. Instead, it must be returned as bytes string. To do this, the third parameter in hash() must be switched from false to true.
  • In the Java code a 192 bits key is used, i.e. AES-192. Accordingly, in the PHP code "AES-192-CBC" must be applied (and not "AES-256-CBC").
  • The utf8_encode() call in the PHP code is to be removed, as this corrupts the key.

With these changes, both codes provide the same ciphertext.

Security:

Using SHA256 as key derivation is insecure. Instead apply a dedicated algorithm like Argon2 or PBKDF2. Also, using the key (or a part of it) as IV is insecure as it results in the reuse of key/IV pairs. Instead, a randomly generated IV should be applied for each encryption.

AES/CBC/PKCS5Padding encryption with fixed IV (or without one)

Yes, generally an all zero IV is used as default, if it is not randomized by default. The initialization vector is XOR'ed with the first plaintext block for CBC, so if all bytes are set to value zero then the plaintext is simply kept.

This can be easily checked by performing both ECB and CBC on a full block of plaintext. E.g. encryption of ASCII Aes Cbc Pkcs5Padding Java to RubyAes Cbc Pkcs5Padding Java to Ruby, i.e. 16 a characters will result in the following ciphertext for ECB:

mExSanM1tVyEV1hjSqBlTZcuxSr1ybN1rpCtwYiyIYg=

and this one for CBC:

mExSanM1tVyEV1hjSqBlTcZkEjmYpQWV7Nmnr0thwhw=

Quite clearly the first blocks must be identical, as ECB doesn't use an IV. So the first block encrypt is directly over the plaintext, just as you expect with an all zero IV.

Note that CBC only provides semantic security if the IV is fully unpredictable to an adversary, i.e. all bits in the IV must appear random to an adversary.

For Java you can just create an IvParameterSpec like this:

new IvParameterSpec(new byte[cipher.getBlockSize()]);


Related Topics



Leave a reply



Submit