Search code examples
javaphpencryptionphp-openssl

How to decrypt string from openssl_encrypt (PHP) in Java?


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?


Solution

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