6

I was looking to encrypt data between a PHP server and a Java Client. Individually the code works fine and I would like to stick with OpenSSL on the PHP server.

Do any of you see anything that I am missing here as I get an error when trying to decode the PHP encrypted string:

PHP:

<?php

$iv = 'fedcba9876543210'; #Same as in JAVA
$key = '0123456789abcdef'; #Same as in JAVA



$ciphers = openssl_get_cipher_methods(FALSE);
$ciphers_and_aliases = openssl_get_cipher_methods(true);
$cipher_aliases = array_diff($ciphers_and_aliases, $ciphers);

print_r($ciphers);

//print_r($cipher_aliases);


// DEFINE our cipher
define('AES_CBC', 'aes-128-cbc');
// Generate a 256-bit encryption key
// This should be stored somewhere instead of recreating it each time
$encryption_key = "test_key";
// Generate an initialization vector
// This *MUST* be available for decryption as well
//$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length(AES_CBC));

// Create some data to encrypt
$data = "Hello World!!!";
$data_b64= base64_encode($data);
echo "Before encryption: $data<br><br>Before Base64: $data_b64<br><br>";
// Encrypt $data using aes-256-cbc cipher with the given encryption key and
// our initialization vector. The 0 gives us the default options, but can
// be changed to OPENSSL_RAW_DATA or OPENSSL_ZERO_PADDING
$encrypted = openssl_encrypt($data_b64, AES_CBC, $encryption_key, 0, $iv);
$len = strlen($encrypted);
echo "Encrypted Len: $len  <br><br>";
$encrypted64 = base64_encode($encrypted);
echo "Encrypted b64: $encrypted64<br><br>";
// If we lose the $iv variable, we can't decrypt this, so:
// - $encrypted is already base64-encoded from openssl_encrypt
// - Append a separator that we know won't exist in base64, ":"
// - And then append a base64-encoded $iv
$encrypted = $encrypted64 . ':' . base64_encode($iv);
echo "Encrypted: $encrypted<br><br>";
// To decrypt, separate the encrypted data from the initialization vector ($iv).
$parts = explode(':', $encrypted);
// $parts[0] = encrypted data
// $parts[1] = base-64 encoded initialization vector
// Don't forget to base64-decode the $iv before feeding it back to
//openssl_decrypt
$decrypted64 = openssl_decrypt(base64_decode($parts[0]), AES_CBC, $encryption_key, 0, base64_decode($parts[1]));
$decrypted = base64_decode($decrypted64);
echo "Decrypted: $decrypted\n";
?>

PHP output:

Before encryption: Hello World!!!

Before Base64: SGVsbG8gV29ybGQhISE=

Encrypted Len: 44

Encrypted b64: U21yMVRGQTdROVc3TWJ1Wm1HUTBhMmZmenlIN2tvdWQ5SHA5ekVxUmp5az0=

Encrypted: U21yMVRGQTdROVc3TWJ1Wm1HUTBhMmZmenlIN2tvdWQ5SHA5ekVxUmp5az0=:ZmVkY2JhOTg3NjU0MzIxMA==

Decrypted: Hello World!!!

Java Code:

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import java.util.Base64;
import java.security.*;

public class Sandbox {

    public static String encrypt(String key, String initVector, String value) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

            byte[] encrypted = cipher.doFinal(Base64.getEncoder().encode(value.getBytes()));
            System.out.println("encrypted string: "
                    + Base64.getEncoder().encodeToString(encrypted));

            return Base64.getEncoder().encodeToString(encrypted);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String decrypt(String key, String initVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");


            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] temp = Base64.getDecoder().decode(encrypted);

            System.out.println((new String(temp)).length());
            byte[] original = cipher.doFinal(temp);
            original = Base64.getDecoder().decode(original);

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static void main(String[] args) {
        String key = "0123456789abcdef"; // 128 bit key
        String initVector = "fedcba9876543210"; // 16 bytes IV

//        for (Provider provider : Security.getProviders()) {
//            System.out.println(provider.getName());
//            for (String key2 : provider.stringPropertyNames()) {
//                System.out.println("\t" + key2 + "\t" + provider.getProperty(key2));
//            }
//        }
        System.out.println(decrypt(key, initVector,
                encrypt(key, initVector, "Hello World!!!")));

        System.out.println(decrypt(key, initVector, "R090NDcvclAyY2E1cmxLWG9kSGlnUktHdEI5U05sRGxNdWF4NFFjUUV0OD0="));
    }
}

Java Output:

> 
30
Hello World!!!

44

null

javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:913)
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:824)
    at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:436)
    at javax.crypto.Cipher.doFinal(Cipher.java:2165)
    at Sandbox.decrypt(Sandbox.java:43)
    at Sandbox.main(Sandbox.java:67)
BUILD SUCCESSFUL (total time: 1 second)
Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
ControlAltDelete
  • 3,576
  • 5
  • 32
  • 50
  • Perhaps this SO [answer](https://stackoverflow.com/a/20770158/1889768) will help you. – Abbas May 29 '17 at 04:47
  • If you're using SSL, why are you encrypting at all? Use an https url and android will automatically do encryption/decryption. Just set up the server to use https. It is really not recommended to do anything like this yourself, its so easy to get subtle things wrong- insufficiently random values (rand is not sufficient), bad IVs, etc. – Gabe Sechan May 29 '17 at 05:07
  • @Gabe - I fully agree with your statement. It is a special design request that a client is seeking. So I need to find a solution to it. – ControlAltDelete May 29 '17 at 05:10
  • 1
    @Jib That's when its your job to educate the client. He's making a decision that is less secure, costlier, and buggier. – Gabe Sechan May 29 '17 at 05:13
  • But your problem- AES requires the input to be padded to a multiple of 16 bytes. The algorithm doesn't work if it isn't. But what to pad it with is a question- padding it with the wrong info can actually leak information and weaken the encryption. Which is an example of the subtle issues you can hit and why you really shouldn't do this. (I actually don't know what the right padding is for AES-CBC, it differs by algorithm). – Gabe Sechan May 29 '17 at 05:15
  • @GabeSechan Correct padding scheme to use for AES-CBC is nearly always PKCS#7/5 – Luke Joshua Park May 29 '17 at 06:00
  • you can have a look at this answer [Input length must be multiple of 16 when decrypting with padded cipher](https://stackoverflow.com/a/43416555/5353830) – Rai_Gaurav May 29 '17 at 06:36
  • @LukeJoshuaPark Yeah, that's specifically used so it becomes easy for attackers to perform padding oracle attacks. 128 tries per byte, utterly insecure. – Maarten Bodewes Jan 05 '20 at 21:04
  • @Maarten-reinstateMonica Is there a better padding scheme for use with AES-CBC? I agree with your statement but don't know of anyway to really avoid it. Length-prefixing with zero padding? – Luke Joshua Park Jan 05 '20 at 21:10
  • @LukeJoshuaPark No, you either use a MAC or you use an authenticated mode such as GCM. First of all, you probably need to have message integrity / authenticity anyway and two, plaintext oracle attacks can be applicable on any unauthenticated plaintext. E.g. there are plaintext attacks that are of even lower order than padding oracle attacks on XML-enc (where, unsurprisingly, they then included GCM in the modes offered). – Maarten Bodewes Jan 06 '20 at 00:18
  • All below answers at the moment of writing this are insecure. For Pete's sake, use TLS for transport mode security. – Maarten Bodewes Jan 06 '20 at 00:20
  • @Maarten-reinstateMonica So... what you actually meant is that AES-CBC with PKCS#7/5 *without a MAC* is no good, right? Not really sure what exactly about my original comment you were disagreeing with. – Luke Joshua Park Jan 06 '20 at 00:30
  • @LukeJoshuaPark Who says I'm disagreeing? You made a comment about the padding and I'm commenting that those are especially insecure in this setting. And I was @ you because that detail wasn't directly given by you and you might want to know. That's all :) – Maarten Bodewes Jan 06 '20 at 02:15
  • @Maarten-reinstateMonica Ahh right, I misunderstood. Apologies! – Luke Joshua Park Jan 06 '20 at 03:26

4 Answers4

16

A working version can be found - https://github.com/chaudhuri-ab/CrossPlatformCiphers

Some things to keep in mind is the that if you do not specify OPENSSL_RAW_DATA in PHP the data will be encrypted as base64. That was throwing me off.

PHP:

class PHP_AES_Cipher {

    private static $OPENSSL_CIPHER_NAME = "aes-128-cbc"; //Name of OpenSSL Cipher 
    private static $CIPHER_KEY_LEN = 16; //128 bits

    /**
     * Encrypt data using AES Cipher (CBC) with 128 bit key
     * 
     * @param type $key - key to use should be 16 bytes long (128 bits)
     * @param type $iv - initialization vector
     * @param type $data - data to encrypt
     * @return encrypted data in base64 encoding with iv attached at end after a :
     */

    static function encrypt($key, $iv, $data) {
        if (strlen($key) < PHP_AES_Cipher::$CIPHER_KEY_LEN) {
            $key = str_pad("$key", PHP_AES_Cipher::$CIPHER_KEY_LEN, "0"); //0 pad to len 16
        } else if (strlen($key) > PHP_AES_Cipher::$CIPHER_KEY_LEN) {
            $key = substr($str, 0, PHP_AES_Cipher::$CIPHER_KEY_LEN); //truncate to 16 bytes
        }

        $encodedEncryptedData = base64_encode(openssl_encrypt($data, PHP_AES_Cipher::$OPENSSL_CIPHER_NAME, $key, OPENSSL_RAW_DATA, $iv));
        $encodedIV = base64_encode($iv);
        $encryptedPayload = $encodedEncryptedData.":".$encodedIV;

        return $encryptedPayload;

    }

    /**
     * Decrypt data using AES Cipher (CBC) with 128 bit key
     * 
     * @param type $key - key to use should be 16 bytes long (128 bits)
     * @param type $data - data to be decrypted in base64 encoding with iv attached at the end after a :
     * @return decrypted data
     */
    static function decrypt($key, $data) {
        if (strlen($key) < PHP_AES_Cipher::$CIPHER_KEY_LEN) {
            $key = str_pad("$key", PHP_AES_Cipher::$CIPHER_KEY_LEN, "0"); //0 pad to len 16
        } else if (strlen($key) > PHP_AES_Cipher::$CIPHER_KEY_LEN) {
            $key = substr($str, 0, PHP_AES_Cipher::$CIPHER_KEY_LEN); //truncate to 16 bytes
        }

        $parts = explode(':', $data); //Separate Encrypted data from iv.
        $decryptedData = openssl_decrypt(base64_decode($parts[0]), PHP_AES_Cipher::$OPENSSL_CIPHER_NAME, $key, OPENSSL_RAW_DATA, base64_decode($parts[1]));

        return $decryptedData;
    }

}

Java:

package ciphers;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import java.util.Base64;

public class Java_AES_Cipher {

    private static String CIPHER_NAME = "AES/CBC/PKCS5PADDING";
    private static int CIPHER_KEY_LEN = 16; //128 bits

    /**
     * Encrypt data using AES Cipher (CBC) with 128 bit key
     * 
     * 
     * @param key  - key to use should be 16 bytes long (128 bits)
     * @param iv - initialization vector
     * @param data - data to encrypt
     * @return encryptedData data in base64 encoding with iv attached at end after a :
     */
    public static String encrypt(String key, String iv, String data) {
        try {
            if (key.length() < Java_AES_Cipher.CIPHER_KEY_LEN) {
                int numPad = Java_AES_Cipher.CIPHER_KEY_LEN - key.length();

                for(int i = 0; i < numPad; i++){
                    key += "0"; //0 pad to len 16 bytes
                }

            } else if (key.length() > Java_AES_Cipher.CIPHER_KEY_LEN) {
                key = key.substring(0, CIPHER_KEY_LEN); //truncate to 16 bytes
            }


            IvParameterSpec initVector = new IvParameterSpec(iv.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance(Java_AES_Cipher.CIPHER_NAME);
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, initVector);

            byte[] encryptedData = cipher.doFinal((data.getBytes()));

            String base64_EncryptedData = Base64.getEncoder().encodeToString(encryptedData);
            String base64_IV = Base64.getEncoder().encodeToString(iv.getBytes("UTF-8"));

            return base64_EncryptedData + ":" + base64_IV;

        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    /**
     * Decrypt data using AES Cipher (CBC) with 128 bit key
     * 
     * @param key - key to use should be 16 bytes long (128 bits)
     * @param data - encrypted data with iv at the end separate by :
     * @return decrypted data string
     */

    public static String decrypt(String key, String data) {
        try {

            String[] parts = data.split(":");

            IvParameterSpec iv = new IvParameterSpec(Base64.getDecoder().decode(parts[1]));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance(Java_AES_Cipher.CIPHER_NAME);
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] decodedEncryptedData = Base64.getDecoder().decode(parts[0]);

            byte[] original = cipher.doFinal(decodedEncryptedData);

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

}
ControlAltDelete
  • 3,576
  • 5
  • 32
  • 50
  • 2
    This is OK but with API >=26 and I can't find similar solution for lower API levels can you help please? – PHP User Mar 01 '19 at 13:55
  • A key is not a string, you need to use algorithms like PBKDF2 to encrypt with a password. And CBC mode is completely insecure for transporting messages, read up on plaintext / padding oracle attacks. – Maarten Bodewes Jan 05 '20 at 21:03
  • I cannot find where you've defined the variable `$str` in your PHP Code – hoomb Feb 19 '21 at 19:06
  • @hoomb just replace $str with $key and one thing I've noticed on the if(key.lengh()) instead of using else if just use else for both java and php – Nasz Njoka Sr. Jun 08 '22 at 13:10
5

I can't figure out why your method fails. By the way, Here's how i did it,

Java

import com.sun.org.apache.xml.internal.security.utils.Base64;

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

public class MyClass {

    public static void main(String[] args) {
        String data = "Arnab C";
        final String enc = DarKnight.getEncrypted(data);
        System.out.println("Encrypted : " + enc);
        System.out.println("Decrypted : " + DarKnight.getDecrypted(enc));
    }

    static class DarKnight {

        private static final String ALGORITHM = "AES";

        private static final byte[] SALT = "tHeApAcHe6410111".getBytes();// THE KEY MUST BE SAME
        private static final String X = DarKnight.class.getSimpleName();

        static String getEncrypted(String plainText) {

            if (plainText == null) {
                return null;
            }

            Key salt = getSalt();

            try {
                Cipher cipher = Cipher.getInstance(ALGORITHM);
                cipher.init(Cipher.ENCRYPT_MODE, salt);
                byte[] encodedValue = cipher.doFinal(plainText.getBytes());
                return Base64.encode(encodedValue);
            } catch (Exception e) {
                e.printStackTrace();
            }

            throw new IllegalArgumentException("Failed to encrypt data");
        }

        public static String getDecrypted(String encodedText) {

            if (encodedText == null) {
                return null;
            }

            Key salt = getSalt();
            try {
                Cipher cipher = Cipher.getInstance(ALGORITHM);
                cipher.init(Cipher.DECRYPT_MODE, salt);
                byte[] decodedValue = Base64.decode(encodedText);
                byte[] decValue = cipher.doFinal(decodedValue);
                return new String(decValue);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }

        static Key getSalt() {
            return new SecretKeySpec(SALT, ALGORITHM);
        }

    }
}

PHP

<?php

$key = "tHeApAcHe6410111";

function encrypt($text,$key){
     $block = mcrypt_get_block_size('rijndael_128', 'ecb');
     $pad = $block - (strlen($text) % $block);
     $text .= str_repeat(chr($pad), $pad);
     return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $text, MCRYPT_MODE_ECB));
}

function decrypt($str, $key){ 
     $str = base64_decode($str);
     $str = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $str, MCRYPT_MODE_ECB);
     $block = mcrypt_get_block_size('rijndael_128', 'ecb');
     $pad = ord($str[($len = strlen($str)) - 1]);
     $len = strlen($str);
     $pad = ord($str[$len-1]);
     return substr($str, 0, strlen($str) - $pad);
}

$enc =  encrypt("Arnab C",$GLOBALS['key']);
echo "Encrypted : ".$enc."</br>";
$dec = decrypt($enc,$GLOBALS['key']);
echo "Decrypted : ".$dec;

Java Output

Encrypted : PJG1Uu6SjJuuVGf7ApuHAw==

Decrypted : Arnab C

PHP Output

Encrypted : PJG1Uu6SjJuuVGf7ApuHAw==

Decrypted : Arnab C

theapache64
  • 10,926
  • 9
  • 65
  • 108
1

I used the solution from theapache64, but in PHP 7.2 it stopped working since Mcrypt has been deprecated and later removed. So I changed the code and it works:

function encrypt($data, $key) {
    return base64_encode(openssl_encrypt($data, "aes-128-ecb", $key, OPENSSL_RAW_DATA));
}

function decrypt($data, $key) {
    return openssl_decrypt(base64_decode($data), "aes-128-ecb", $key, OPENSSL_RAW_DATA);
}
petrnohejl
  • 7,581
  • 3
  • 51
  • 63
-1

In Java 8, you cannot use

import com.sun.org.apache.xml.internal.security.utils.Base64;

Instead, you can use

import android.util.Base64;

Then you also need to change the Base64.decode and Base64.encode lines.

The complete code will be as follows (the comment from petrnohejl, about MCRYPT that has been deprecated in PHP7, taken into account):

Java:

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

public class MyClass {
    public static void main(String[] args) {
        String data = "Arnab C";
        final String enc = DarKnight.getEncrypted(data);
        System.out.println("Encrypted : " + enc);
        System.out.println("Decrypted : " + DarKnight.getDecrypted(enc));
    }

    static class DarKnight {
        private static final String ALGORITHM = "AES";
        private static final byte[] SALT = "tHeApAcHe6410111".getBytes();// THE KEY MUST BE SAME
        private static final String X = DarKnight.class.getSimpleName();
        static String getEncrypted(String plainText) {
            if (plainText == null) {
                return null;
            }

            Key salt = getSalt();

            try {
                Cipher cipher = Cipher.getInstance(ALGORITHM);
                cipher.init(Cipher.ENCRYPT_MODE, salt);
                byte[] encodedValue = cipher.doFinal(plainText.getBytes());
                return Base64.encodeToString(encodedValue,Base64.DEFAULT);
            } catch (Exception e) {
                e.printStackTrace();
            }
            throw new IllegalArgumentException("Failed to encrypt data");
        }

        public static String getDecrypted(String encodedText) {
            if (encodedText == null) {
                return null;
            }

            Key salt = getSalt();
            try {
                Cipher cipher = Cipher.getInstance(ALGORITHM);
                cipher.init(Cipher.DECRYPT_MODE, salt);
                byte[] decodedValue = Base64.decode(encodedText, Base64.DEFAULT);
                byte[] decValue = cipher.doFinal(decodedValue);
                return new String(decValue);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }

        static Key getSalt() {
            return new SecretKeySpec(SALT, ALGORITHM);
        }
    }
}

PHP:

<?php

$key = "tHeApAcHe6410111";

function encrypt($data, $key) {
    return base64_encode(openssl_encrypt($data, "aes-128-ecb", $key, OPENSSL_RAW_DATA));
}

function decrypt($data, $key) {
    return openssl_decrypt(base64_decode($data), "aes-128-ecb", $key, OPENSSL_RAW_DATA);
}

$enc =  encrypt("Arnab C",$GLOBALS['key']);
echo "Encrypted : ".$enc."</br>";
$dec = decrypt($enc,$GLOBALS['key']);
echo "Decrypted : ".$dec;

?>

Don't give me credits, I am just the messenger, who combined a few posts.

Kairos
  • 147
  • 1
  • 12
  • 1
    Warning: this code uses ECB mode and no password derivation method, and is therefore insecure. – Maarten Bodewes Jan 05 '20 at 23:20
  • Hi Maarten, what could be a better way? Yours, Albert van Harten – Kairos Jan 13 '20 at 09:41
  • 1
    A password based key derivation method like PBKDF2 or Argon2 and e.g. GCM mode. But for transport mode security you really need a secure transport mode (TLS, SSH, Signal etc.) - there is no real way around that. – Maarten Bodewes Jan 13 '20 at 11:00
  • Thanks, Maarten. I am using TLS for transporting data, no doubt about that :) I do use a password derivation method - but probably not a method you meant :) Albert van Harten – Kairos Jan 25 '20 at 20:49
  • You are not using any key derivation at all, you're just using a salt as a key, all stringified. If you cannot distinguish between a salt and a key, you may not want to post code for encryption. Your code is utterly insecure and as providing security is the goal of encryption, I personally don't see any use for it. – Maarten Bodewes Jan 25 '20 at 20:59
  • I apologize for the fact that I haven't posted my entire code ... – Kairos Jan 26 '20 at 21:33