I am trying to replicate the following PHP class in Java to decrypt a string that was encrypted using openssl_encrypt in PHP.
class Encryption {
/* Encryption method */
protected $method = 'aes-128-ctr';
/* Encryption key */
private $key;
/* Property constructor */
public function __construct($key = FALSE, $method = FALSE){
/* Check if custom key provided */
if(!$key) {
/* Set default encryption if none provided */
$key = '1234567891234567';
}
/* Check for control characters in key */
if(ctype_print($key)) {
/* Convert ASCII keys to binary format */
$this->key = openssl_digest($key, 'SHA256', true);
} else {
$this->key = $key;
}
$this->key = $key;
/* Check for custom method */
if($method) {
/* If it is a valid openssl cipher method, use it */
if(in_array(strtolower($method), openssl_get_cipher_methods())) {
$this->method = $method;
/* If it is not, unrecognised method */
} else {
die(__METHOD__ . ": unrecognised cipher method: {$method}");
}
}
}
/* Get iv bytes length */
protected function iv_bytes(){
/* Get length of encryption cipher initialisation vector */
return openssl_cipher_iv_length($this->method);
}
/* Encryption method */
public function encrypt($data) {
/* Get initialisation vector binary */
$iv = openssl_random_pseudo_bytes($this->iv_bytes());
/* Return IV hex & encryption string */
return bin2hex($iv) . openssl_encrypt($data, $this->method, $this->key, OPENSSL_ZERO_PADDING, $iv);
}
/* Decrypt encrypted string */
public function decrypt($data){
/* Get IV string length */
$iv_strlen = 2 * $this->iv_bytes();
/* Parse out the encryption string and unpack the IV and encrypted string. $regs is passed by reference in preg_match() */
if(preg_match("/^(.{" . $iv_strlen . "})(.+)$/", $data, $regs)) {
list(, $iv, $crypted_string) = $regs;
print_R($regs);
/* If there are character representing a hex and the IV string length is divisible by 2 */
if(ctype_xdigit($iv) && (strlen($iv) % 2 == 0)) {
/* Decrypt the unpacked encrypted string */
return openssl_decrypt($crypted_string, $this->method, $this->key, OPENSSL_ZERO_PADDING, hex2bin($iv));
}
}
return FALSE; // failed to decrypt
}
}
Here is my current sandbox class for the decryptor in Java
public class PHPDecryptor {
private static String removeIVHash(String data) {
/* IV string length is 128 bit (16 bytes) * 2 */
int iv_strlen = 2 * 16;
/* Remove IV hash */
String encrypted_string = data.substring(iv_strlen);
return encrypted_string;
}
public static String decrypt(String data) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
String encrypted_string = removeIVHash(data);
/* Get bytes for string and key */
byte[] text_to_decrypt = encrypted_string.getBytes(StandardCharsets.UTF_8);
String key_string = "1234567891234567";
byte[] key = key_string.getBytes(StandardCharsets.UTF_8);
/* Get secret key and iv */
SecretKeySpec secret_key = new SecretKeySpec(key, "AES");
IvParameterSpec iv = new IvParameterSpec(new byte[16]);
/* Init cipher */
Cipher cipher = Cipher.getInstance("AES/CTR/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret_key, iv);
/* Decrypt and cast to string */
byte[] decrypted = cipher.doFinal(text_to_decrypt);
String result = new String(decrypted, StandardCharsets.UTF_8);
return result;
}
public static void main(String[] arg) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
String text_to_decrypt = "203790144a345320d98fb773795d518e/ioQTApeVMV/4g=="; // Encrypted by PHP
System.out.println(decrypt(text_to_decrypt));
}
}
However, I am not getting an accurate decryption - in fact the return result from the Java class is utter nonsense with invalid UTF-8 characters.
Are there any obvious errors here?
It seems like you're not doing anything with the IV, just removing it.
Create an instance of IVParameterSpec from the IV that you removed.
public static IvParameterSpec getIv(String iv) {
return new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
}
You can retrieve the IV from the string better if you use some delimiter (I like to use ":") to separate the iv and the rest of the ciphertext, that way you don't need to worry about the hexed IV length and retrieve it like this:
private static String decodeIvString(String data) {
String[] parts = data.split(":");
String iv = decodeHex(parts[0]);
return iv;
}
Obviously you'll have to add the delimiter in the encrypt function in php.
You can then pass the IV back to the getIv()
function.
/* Encryption method */
public function encrypt($data) {
/* Get initialisation vector binary */
$iv = openssl_random_pseudo_bytes($this->iv_bytes());
/* Return IV hex & encryption string */
return bin2hex($iv) . ":" . openssl_encrypt($data, $this->method, $this->key, OPENSSL_ZERO_PADDING, $iv);
}
And as the comments above mentioned, you should replace PKCS5Padding
with NoPadding
Here's a utility piece of code that I use to decrypt:
public static byte[] decryptSymmetric(Algorithm algorithm, Key key, IvParameterSpec iv, byte[] data) throws CryptoFailure {
try {
Security.addProvider(new BouncyCastleProvider());
Cipher aes = Cipher.getInstance(algorithm.getMode(), BouncyCastleProvider.PROVIDER_NAME);
aes.init(Cipher.DECRYPT_MODE, key, iv);
return aes.doFinal(data);
} catch (BadPaddingException | IllegalBlockSizeException e) {
throw new CryptoFailure(e);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
throw new CryptoError(e);
}
}