8

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)
14
  • Perhaps this SO answer will help you. Commented May 29, 2017 at 4: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. Commented May 29, 2017 at 5: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. Commented May 29, 2017 at 5:10
  • 2
    @Jib That's when its your job to educate the client. He's making a decision that is less secure, costlier, and buggier. Commented May 29, 2017 at 5:13
  • 1
    All below answers at the moment of writing this are insecure. For Pete's sake, use TLS for transport mode security. Commented Jan 6, 2020 at 0:20

5 Answers 5

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;
    }

}
Sign up to request clarification or add additional context in comments.

5 Comments

This is OK but with API >=26 and I can't find similar solution for lower API levels can you help please?
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.
I cannot find where you've defined the variable $str in your PHP Code
@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
I think you should replace $str with $key
6

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

4 Comments

Does it work for you with OpenSSL on the php side vs Mcrypt, which is a no longer actively developed library? Also, if you used an Iv does it work for you? Thx
I did got it working. OpenSSL options made a difference. Understanding that by default OpenSSL in PHP will produces a Base64 encoded string. Answer Above. Thx and +1 for your reply.
Okay brother (y)
solution looks good but mcrypt_decrypt() is deprecated need solution that works in 2022 Aslo if possible with android.util.Base64
2

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);
}

Comments

0

In my case, everything i got from google request "java php aes encryption" - or work partially (locally), or did not work at all. So i collect everything i found in internet and made this, hope it helps.

Note: this example uses standard libraries, on both sides, that why there is no AES-256 encryption used, regardless of PHP native support, because Java does not support AES-256 nor PKCS7Padding without external libs, such as Apache Crypto etc.

PHP

/**
 * Encrypt data using AES Cipher (CBC) with 128 bit key
 * 
 * @param $userPassword - password provided by user, which will be transformed to 16 bytes strings no matter what (required length by AES-128-CBC/CFB algorithm)
 * @param $iv           - initialization vector (16 bytes long (128 bits))
 * @param $data         - data to encrypt
 * @return              - encrypted data with iv attached at end (last 16 bytes of data) and encoded in Base64 string
 */
function java_aes_encrypt($userPassword, $data) {
    $key = prepareAES128bitsKey($userPassword);
    $iv = generateRandomVI();
    return base64_encode(hex2bin(bin2hex(openssl_encrypt($data, "AES-128-CBC", $key, OPENSSL_RAW_DATA, $iv)).bin2hex($iv)));
}

/**
 * Decrypt data using AES Cipher (CBC) with 128 bit key
 * 
 * @param $userPassword - password provided by user, which will be transformed to 16 bytes strings no matter what (required length by AES-128-CBC/CFB algorithm)
 * @param $data         - data to be decrypted with iv attached at the end as last 16 bytes and encoded in Base64 string
 * @return              - decrypted data
 */
function java_aes_decrypt($userPassword, $data) {
    $key = prepareAES128bitsKey($userPassword);

    // Convert binary data to a hexadecimal string
    $hexData = bin2hex(base64_decode($data));

    // Get the length of the received data
    $dataLength = strlen($hexData);

    // Check if the data length is at least 32 characters (16 bytes in hexadecimal)
    if ($dataLength >= 32) {
        // Extract the last 16 bytes in hexadecimal
        $last16Bytes = substr($hexData, -32);

        // Extract everything except the last 16 bytes in hexadecimal
        $everythingExceptLast16 = substr($hexData, 0, $dataLength - 32);

        // Convert the extracted data back to binary if needed
        $last16BytesBinary = hex2bin($last16Bytes);
        $everythingExceptLast16Binary = hex2bin($everythingExceptLast16);
        return openssl_decrypt($everythingExceptLast16Binary, "AES-128-CBC", $key, OPENSSL_RAW_DATA, $last16BytesBinary);
    }
    return FALSE;
}

/**
 * Transform user provided password to fixed length (16 symbols) passphrase
 * Must be the same as on Java side!
 * @param $userPassword    - password provided by user, which will be transformed to 16 bytes strings no matter what (required length by AES-128-CBC/CFB algorithm)
 * @param $transformLength - length of return string
 * @return $outPass key passphrase generated from user provided
 */
function prepareAES128bitsKey($userPassword, $transformLength = 16) {
    $hash = hash('sha512', $userPassword);
    $characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    $chars_num = strlen($characters);
    $acc = 0; $outPass = "";
    for ($i = 0; $i < 16; $i++) { $acc += ord($hash[$i]); }
    for ($ci = 0, $hi = 16; $ci < $transformLength; $ci += 1, $hi += 1) {
        if($hi >= strlen($hash)) { $hi = 0; }
        $acc += ord($hash[$hi]);
        $outPass .= $characters[($acc % $chars_num)];
    }
    return $outPass;
}

function generateRandomVI() {
    return generateOpenSSLRandomString(16);
}

function generateOpenSSLRandomString($length = 8) {
    $characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    $chars_num = strlen($characters);
    $randomString = "";
    $b = openssl_random_pseudo_bytes($length * $length);
    for ($ci = 0, $bi = 0; $ci < $length; $ci += 1, $bi += 2) {
        $randomString .= $characters[(ord($b[$bi]) % $chars_num)];
    }
    return $randomString;
}

Java:

import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
import java.util.Base64.Decoder;
import java.util.Base64.Encoder;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class main {

    public static void main(String[] args) {
        
        String userPasswordWhichCanBeAnyLength = "yhqzEgtGquYCHpUlJi0BpSCIVBdGxMl";
        
        String userProvidedDataAsString = 
        "Lorem Ipsum is simply dummy text of the printing and typesetting industry. " +
        "Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, " +
        "when an unknown printer took a galley of type and scrambled it to make a " +
        "type specimen book. It has survived not only five centuries, but also the " +
        "leap into electronic typesetting, remaining essentially unchanged. It was popularized in the " +
        "1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more " +
        "recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";
        
        AES.testStringToStringEncryption(userPasswordWhichCanBeAnyLength, userProvidedDataAsString);
    }
    
}

class AES {
    
    //private final SecureRandom random; // need JDK v17+
    private final Random random;
    
    // base64 encoding/decoding
    private final Encoder instance_Base64enc;
    private final Decoder instance_Base64dec;
    
    // AES Encryption variables
    private final char[] allowedChars;
    private String passwordKey;
    private SecretKeySpec keySpec;
    private final Cipher instance_AES_Cipher;

    /**
     * The keeper of local Instances of AES cipher and Random objects, for less identical results
     */
    public AES() {
        this.instance_Base64enc = java.util.Base64.getEncoder();
        this.instance_Base64dec = java.util.Base64.getDecoder();
        
        //this.random = new SecureRandom(); // need JDK v17+
        this.random = new Random();
        
        this.allowedChars = new String("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray();
        
        this.instance_AES_Cipher = getInstanceAESCP();
    }
    
    /**
     * Encapsulated String to String (Base64) Encrypting Function
     * @param  data - Supplied user data as String
     * @return encoded as Base64 Encrypted data
     */
    public String encrypt(String data) {
        try {
            return this.Base64enc(encryptRaw(data.getBytes("UTF-8")));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    /**
     * Plain data to encrypted data Encryption.
     * May be used for internal purposes, or for files encryption.
     * @param  data - any data provided as byte[] array
     * @return encrypted bytes as byte[] array
     */
    public byte[] encryptRaw(byte[] data) {

        // Exceptions handling is good for future integration and error handling,
        // but not possible in current example, so let it be here
        try {
            return AES_encrypt(data);
        } catch (InvalidKeyException e)                {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e)           {
            e.printStackTrace();
        } catch (NoSuchPaddingException e)             {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e)       {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e)          {
            e.printStackTrace();
        } catch (BadPaddingException e)                {
            e.printStackTrace();
        }
        
        return data;
    }
    
    /**
     * Encapsulated String (Base64) to plain String (with possible useful information) Encrypting Function
     * @param  data - Supplied encoded Base64 and encrypted String
     * @return plain decrypted data as String
     */
    public String decrypt(String data) {
        try {
            return new String(decryptRaw(Base64dec(data.replace("\\", ""))), "UTF-8").trim();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    /**
     * Expects encrypted byte[] array
     * @param  data - encrypted data provided as byte[] array
     * @return decrypted bytes as byte[] array
     */
    public byte[] decryptRaw(byte[] data) {
        
        // Exceptions handling is good for future integration and error handling,
        // but not possible in current example, so let it be here
        try {
            return AES_decrypt(data);
        } catch (InvalidKeyException e)                {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e)           {
            e.printStackTrace();
        } catch (NoSuchPaddingException e)             {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e)       {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e)          {
            e.printStackTrace();
        } catch (BadPaddingException e)                {
            e.printStackTrace();
        }
        
        return data;
    }
    
    
    /**
     * This function sets password key used to encrypt\decrypt data with AES
     * Must be identical on both sides!
     * @param  password - password provided by user
     * @return instance of current AES object
     */
    public AES setPasswordKey(String password) {
        try {
            byte[] key = prepareAES128bitsKey(password);
            this.keySpec = new SecretKeySpec(key, "AES");
        }
        catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        this.passwordKey = password;
        return this;
    }
    
    /**
     * User provided password
     * @return stored users password used to encrypt\decrypt data with AES algorithm as String
     */
    public String getPasswordKey() {
        return this.passwordKey;
    }
    
    /**
     * Test of AES Encryption module,
     * @param userPassword - user provided password. Can be any length
     * @param userData     - user provided data. Can be any length
     */
    public static void testStringToStringEncryption(String userPassword, String userData) {
        
        // Make instance
        AES AES = new AES();
        
        // Necessary part! Need to set password for future encrypting/decrypting steps!
        AES.setPasswordKey(userPassword);
        
        // encrypting step
        String encoded_Base64_encrypted_aes_string = AES.encrypt(userData);
        
        // decrypting step
        String decrypted_data = AES.decrypt(encoded_Base64_encrypted_aes_string);
        
        System.out.println("User provided plain String: " + userData);
        System.out.println("Encrypted String: " + encoded_Base64_encrypted_aes_string);
        System.out.println("Decrypted String: " + decrypted_data);
        
        if(userData.equals(decrypted_data)) {
            System.out.println("\nAES Test succeed!");
        }
        else {
            System.out.println("\nAES Test FAIL!");
        }
        
        System.out.println("AES encryption-decryption Test Done! Exiting.");
        System.exit(77);
    }
    
    /**
    * 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 encrypted data with iv attached at end as 16 last bytes
    * @throws NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException */
    private byte[] AES_encrypt(byte[] data) throws NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        byte[] iv = new byte[16]; this.random.nextBytes(iv);
        IvParameterSpec initVector = new IvParameterSpec(iv);
        this.instance_AES_Cipher.init(Cipher.ENCRYPT_MODE, this.keySpec, initVector);
        byte[] encryptedData = this.instance_AES_Cipher.doFinal(data);
        int dataLength = encryptedData.length;
        byte[] combo = new byte[dataLength + 16];
        for (int i = 0; i < dataLength; i++)   { combo[i] = encryptedData[i]; }
        for (int i = 0, o = dataLength; i < 16; i++, o++) { combo[o] = iv[i]; }
        return combo;
    }

    /**
    * 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 as 16 last bytes
    * @return decrypted data
    * @throws NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException */
    private byte[] AES_decrypt(byte[] dataPlusIV) throws NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        int fullLength = dataPlusIV.length;
        int dataLength = (fullLength - 16);
        byte[] iv   = new byte[16];
        byte[] data = new byte[dataLength];
        for (int fi = 0, di = 0, vi = 0; fi < fullLength; fi++) {
            if(fi < dataLength) { data[di++] = dataPlusIV[fi]; } else { iv[vi++] = dataPlusIV[fi]; }
        }
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        this.instance_AES_Cipher.init(Cipher.DECRYPT_MODE, this.keySpec, ivSpec);
        byte[] original = this.instance_AES_Cipher.doFinal(data);
        return original;
    }
    
    /**
     * The idea behind this function to generate consistent 128 bits key, on both sides (PHP and Java),
     * no matter what, without paddings or string clamping.
     * 
     * Functionality similar to:
     *    PBEKeySpec pbeKeySpec = new PBEKeySpec(password, salt, 1000, keySize);
     *    SecretKey key = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256").generateSecret(pbeKeySpec);
     * 
     * @param  userPassword - password provided by user, length is irrelevant
     * @return returnKey    - 16 bytes array of generated chars from user provided SHA512 hashed password
     * @throws NoSuchAlgorithmException
     * @throws UnsupportedEncodingException
     */
    private byte[] prepareAES128bitsKey(String userPassword) throws NoSuchAlgorithmException, UnsupportedEncodingException {
        char[] hash = hash(userPassword).toCharArray();
        int hashLength = hash.length;
        byte[] returnKey = new byte[16];
        int charsNum = this.allowedChars.length; int acc = 0;
        for (int i = 0; i < 16; i++) { acc += ((int) hash[i]);}
        for(int ci = 0, hi = 16; ci < 16; ci++, hi++) {
            if (hi >= hashLength) { hi = 0; }
            acc += ((int) hash[hi]);
            returnKey[ci] = ((byte) this.allowedChars[(acc % charsNum)]);
        }
        return returnKey;
    }
    
    /**
     * String hashing function
     * @param  str - input string
     * @return SHA512 hash of provided string
     * @throws NoSuchAlgorithmException
     * @throws UnsupportedEncodingException
     */
    public String hash(String str) throws NoSuchAlgorithmException, UnsupportedEncodingException {
        MessageDigest sha512 = MessageDigest.getInstance("SHA-512");
        byte[] hashBytes = sha512.digest(str.getBytes("UTF-8"));
        StringBuilder hexString = new StringBuilder();
        int size = hashBytes.length;
        for (int i = 0; i < size; i++) {
            hexString.append(String.format("%02x", hashBytes[i]));
        }
        return hexString.toString();
    }
    
    /**
     * Retrieve instance of AES Cipher, to reduce system calls
     * @return instance of AES Cipher
     */
    private static Cipher getInstanceAESCP() {
        try { return Cipher.getInstance("AES/CBC/PKCS5PADDING"); }
        catch (NoSuchAlgorithmException e)     {}
        catch (NoSuchPaddingException e)       {}
        return null;
    }
    
    
    /**
     * Local Base64 encoder
     * @param  data - to encode
     * @return encoded data in Base64 String
     */
    public String Base64enc(byte[] data) {
        return instance_Base64enc.encodeToString(data);
    }

    /**
     * Local Base64 decoder
     * @param  base64str - String of encoded data to decode
     * @return decoded data in byte[] array, may need future processing/decoding
     */
    public byte[] Base64dec(String base64str) {
        return instance_Base64dec.decode(base64str);
    }

}

Sending to PHP can be done in this way:

public void sendToPHP(String userPassword, String userData) {
    // Make instance
    AES AES = new AES();
    
    // Necessary part! Need to set password for future encrypting/decrypting steps!
    AES.setPasswordKey(userPassword);
    
    // encrypting step
    String encoded_Base64_encrypted_aes_string = AES.encrypt(userData);
    
    try {
        // Open a connection to the URL
        HttpURLConnection connection = (HttpURLConnection) new URL("http://ursite.com/api/").openConnection();
        
        // Set the connection timeout (in milliseconds)
        connection.setConnectTimeout(2000);

        // Set the request method
        connection.setRequestMethod("POST");

        // set headers and send data
        connection.setRequestProperty("dataType", "text/plain");
        connection.setRequestProperty("body", encoded_Base64_encrypted_aes_string);

        // Get the response code
        int responseCode = connection.getResponseCode();

        // Read the response from the API
        if (responseCode == HttpURLConnection.HTTP_OK) {
            // process response from PHP here, for example
            
            InputStreamReader isr = new InputStreamReader(connection.getInputStream());
            BufferedReader    bfr = new BufferedReader(isr);
            
            String inputLine;
            StringBuilder response = new StringBuilder();

            while ((inputLine = bfr.readLine()) != null) {
                response.append(inputLine);
            }
            
            // decrypting using password provided at start with 'AES.setPasswordKey(userPassword)' function
            // be aware, this received data encrypted in Base64 string
            // passwords must be the same on both (Java and PHP) sides!
            String decrypted_PHP_response = AES.decrypt(response.toString());
            
            System.out.println("Decrypted PHP answer: " + decrypted_PHP_response);
        }

        // Close the connection
        connection.disconnect();
    } catch (IOException e) {
        e.printStackTrace();
        String exceptionMessage = e.getMessage();
        if(exceptionMessage.contains("Connect timed out")) {
            // time out error here
        }
        else {
            // other type errors here
        }
    }
}

Receive and send on PHP side:

/* Assuming You transfer from Java to PHP users password in some (secure) way */
$userPassword = "yhqzEgtGquYCHpUlJi0BpSCIVBdGxMl";

// data sent from Java
$bodyRequest = file_get_contents('php://input');

// decrypt data on PHP side
$remote_decrypted_aes_text = java_aes_decrypt($userPassword, $bodyRequest);

// locally process it here
$transformed_plain_text = $remote_decrypted_aes_text." - WOHO! LIKE IT! APPROVED BY PHP!";

// encrypt it by PHP
$localy_encrypted_aes_text = java_aes_encrypt($userPassword, $transformed_plain_text);

// send it back
echo $localy_encrypted_aes_text; exit();

Comments

-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.

6 Comments

Warning: this code uses ECB mode and no password derivation method, and is therefore insecure.
Hi Maarten, what could be a better way? Yours, Albert van Harten
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.
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
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.
|

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.