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