1

I am writing a program using Java 1.6 that should generate a message of the format:

"Your invoice #123 for 100.00 is at https://my.site.com/documents/invoice?p=xxxxxxxxxxx"

with xxxxx containing an encrypted JSON string. The website at my.site.com runs PHP. It would open the URL with the invoice info:

<?php
$cipher = "AES-128-CTR";
$encryption_iv = "1234567891011121";
$encryption_key = "MyPassword";
$encryption_options = 0;
$enrypted_parm = urldecode( $this->input->get('p') );
$iv_length = openssl_cipher_iv_length( $cipher );
$decrypted_parm = openssl_decrypt( $enrypted_parm, $cipher, $encryption_key, $encryption_options, $encryption_iv );
$parms = json_decode($decrypted_parm);

echo "id=" . $parms->id . "&db=" . $parms->db . "&archived=" . $parms->archived;
?>

To generate the message from Java, I have tried this:

encryption_key = "MyPassword";
JsonObject joParms = new JsonObject();
joParms.addProperty("id", invoiceId);
joParms.addProperty("db", db);
joParms.addProperty("archived", isArchive);

SecretKeySpec secretKeySpec = new SecretKeySpec(encryption_key.concat("           ").substring(0,16).getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] byteEncryptedString = cipher.doFinal(joParms.toString().getBytes());
String encodedString = Base64.encodeBase64String(byteEncryptedString);

String message = messageTemplate.replace("{INVNO}", invNo).replace("{AMOUNT}", amount).concat(" ").concat(siteUrl).concat("?p=").concat(encodedString);

The encrypted string generated thus does not get decrypted by PHP. I think the two don't quite match in specs. Can someone help?

6
  • 2
    You have to specify mode and padding on the Java side in getInstance(), e.g. "AES/CTR/NoPadding", otherwise (platform-dependent) default values will be used. Commented Jan 1 at 13:56
  • I was just going to suggest trying this as well, trying to use a more specific encryption mode. Commented Jan 1 at 13:59
  • 1
    Also missing in the Java code is the determination of the automatically generated IV with cipher.getIV() (or the specification of a user-defined IV for encryption) as well as a padding of the key that is compatible with the PHP code (with 0x00 values). Commented Jan 1 at 14:25
  • 1
    @user2023577 We have to play the hand we're dealt. In this case, this is a major system used by the government. So, it's not up to me to change it. Commented Jan 1 at 17:01
  • 1
    You must pass the IV as IvParameterSpec instance in the third parameter of cipher.init(). Commented Jan 1 at 17:06

2 Answers 2

4

For compatibility with the PHP code, in the Java code:

  • mode and padding must be specified. Otherwise, platform-dependent default values are used.
  • the IV must be passed with IvParameterSpec (or if no user-defined IV is applied, the automatically generated IV must be retrieved with cipher.getIV(), as this is required for decryption).
  • for AES-128 a too short key must be padded with 0x00 values to 16 bytes and a too long key must be truncated after 16 bytes (as PHP/OpenSSL does).
  • a URL encoding must be carried out, since in the PHP code a URL decoding is done.

In addition, for security reasons:

  • an encoding should be specified in getBytes(). Otherwise, a platform-dependent default encoding is used (at least in earlier Java versions).
  • no static IV should be applied, but a randomly generated IV for each encryption, which is passed along with the ciphertext (usually concatenated). Note that the IV is not a secret.
  • no password/text should be used directly as key. Instead, a key derivation function (like e.g. PBKDF2) should be applied in conjunction with a random salt.
  • a supported Java version should be used whenever possible.

Sample code for Java 1.6:

import java.net.URLEncoder;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;

...

String encryption_key = "MyPa§sword";
String iv = "1234567891011121"; 
String plaintext = "{\"key1\":\"value1\",\"key2\":\"value2\"}"; // some JSON string
String utf8 = "UTF8";

SecretKeySpec secretKeySpec = new SecretKeySpec(Arrays.copyOf(encryption_key.getBytes(utf8), 16), "AES"); // pad/truncate key 
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); // specify mode and padding 
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, new IvParameterSpec(iv.getBytes(utf8))); // specify IV
byte[] byteEncryptedString = cipher.doFinal(plaintext.getBytes(utf8));
String encodedString = URLEncoder.encode(Base64.encodeBase64String(byteEncryptedString), utf8); // add Url encoding

System.out.println(encodedString); // JxTY7z6mhpSTP46W0bkoJ%2BfDes0%2Fts7HWK5BBlwjTLZH

which can be decrypted with the following PHP code:

<?php
$ciphertext = "JxTY7z6mhpSTP46W0bkoJ%2BfDes0%2Fts7HWK5BBlwjTLZH";
$cipher = "AES-128-CTR";
$encryption_iv = "1234567891011121";
$encryption_key = "MyPa§sword";
$encryption_options = 0;
$enrypted_parm = urldecode($ciphertext);
$iv_length = openssl_cipher_iv_length( $cipher );
$decrypted_parm = openssl_decrypt( $enrypted_parm, $cipher, $encryption_key, $encryption_options, $encryption_iv );
$parms = json_decode($decrypted_parm);
print_r($parms); /* stdClass Object
                    (
                        [key1] => value1
                        [key2] => value2
                    )
                 */
?>
Sign up to request clarification or add additional context in comments.

1 Comment

Worked perfectly. Thanks!
0

One common approach is using AES encryption with a shared secret key. Below is an example (Ecnrypt Java):

import javax.crypto.Cipher; import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class Encryptor {
private static final String ALGORITHM = "AES";

public static String encrypt(String data, String key) throws Exception {
    SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), ALGORITHM);
    Cipher cipher = Cipher.getInstance(ALGORITHM);
    cipher.init(Cipher.ENCRYPT_MODE, secretKey);
    byte[] encryptedData = cipher.doFinal(data.getBytes());
    return Base64.getEncoder().encodeToString(encryptedData);
}

public static void main(String[] args) throws Exception {
    String originalString = "Hello, PHP!";
    String key = "1234567890123456"; // 16-byte key (128-bit)
    
    String encryptedString = encrypt(originalString, key);
    System.out.println("Encrypted: " + encryptedString);
}
}

Decrypt PHP example :

    <?php
function decrypt($encrypted, $key) {
    $cipher = "aes-128-ecb"; // Must match the Java AES mode
    $decoded = base64_decode($encrypted);
    $decrypted = openssl_decrypt($decoded, $cipher, $key, OPENSSL_RAW_DATA);
    return $decrypted;
}

$encryptedString = "EncryptedStringFromJava"; // Replace with actual encrypted string
$key = "1234567890123456"; // 16-byte key (same as used in Java)

$decryptedString = decrypt($encryptedString, $key);
echo "Decrypted: " . $decryptedString;
?>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.