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:
- Your expiryDate is
20150101 151515
in the Java code, but20150101151515
everywhere else. So let's standardize on20150101151515
Base64.encodeBase64()
does not exist. Java 8 has Base64 encoding built-in, and the code should beBase64.getEncoder().encodeToString(data)
- The return type of that is already string so
encryptedValue = new String(Base64...)
is unnecessary. - 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 fromfalse
totrue
. - 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
Calling a Java Servlet from JavaScript
How to Combine One Android Studio Project into Another Android Studio Project
Fileuriexposedexception in Android
How to Install Various Python Libraries in Jython
Save JSON Outputted from a Url to a File
Advanced Java-Like Enums in Ruby
Rselenium Unknownerror - Java.Lang.Illegalstateexception with Google Chrome
How to Encode String (Use Encrypt Messagedigest in Java) to Base64 String in Swift
Simple Calculator with Jsp/Servlet and Ajax
Language Detection with Data in Postgresql
How to Script the Java Debugger Command-Line Tool (Jdb)
How to Instantiate Non Static Inner Class Within a Static Method