Search code examples
javaphpencryption

Java encryption/decryption code conversion to PHP does not wok


I have a Java code snippet for encryption/decryption using AES, and I need to convert it to PHP for use in my web application. I've tried a few approaches but haven't been successful. Below is the Java code I'm trying to convert:

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

public class Main {
    public static void main(String[] args) {
        
        String encryptedData = "lmncpiedimdofipgnokfeppgfncimkac";
        String secretKey = "0123456789ABCDEF"; // 16-character key
        String iv = "0123456789ABCDEF"; // 16-character initialization vector


        try {
            String decryptedData = decrypt(encryptedData, secretKey, iv);
            System.out.println("Decrypted data: " + decryptedData);
        } catch (Exception e) {
            System.err.println("Error while decrypting: " + e.getMessage());
            e.printStackTrace();
        }
    }

    public static String decrypt(String decData, String secretKey, String vector) throws Exception {
        byte[] raw = secretKey.getBytes("utf-8");
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        IvParameterSpec iv = new IvParameterSpec(vector.getBytes("utf-8"));
        cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
        byte[] encrypted1 = decodeBytes(decData);
        byte[] original = cipher.doFinal(encrypted1);
        return new String(original, "utf-8");
    }

    public static String encodeBytes(byte[] bytes) {
        StringBuffer strBuf = new StringBuffer();
        for (int i = 0; i < bytes.length; i++) {
            strBuf.append((char) (((bytes[i] >> 4) & 0xF) + ((int) 'a')));
            strBuf.append((char) (((bytes[i]) & 0xF) + ((int) 'a')));
        }
        return strBuf.toString();
    }

    public static byte[] decodeBytes(String str) {
        byte[] bytes = new byte[str.length() / 2];
        for (int i = 0; i < str.length(); i += 2) {
            char c = str.charAt(i);
            bytes[i / 2] = (byte) ((c - 'a') << 4);
            c = str.charAt(i + 1);
            bytes[i / 2] += (c - 'a');
        }
        return bytes;
    }
}

I've attempted to convert this Java code to PHP, but I'm encountering issues, particularly with the decryption logic. Below is the PHP code I've tried:

<?php
         $encryptedData = "lmncpiedimdofipgnokfeppgfncimkac";
         $secretKey = "0123456789ABCDEF"; // 16-character key
         $iv = "0123456789ABCDEF"; // 16-character initialization 
try {
    $decryptedData = decrypt($encryptedData, $secretKey, $iv);
    echo "Decrypted data: " . $decryptedData;
} catch (Exception $e) {
    echo "Error while decrypting: " . $e->getMessage();
    $e->getTrace();
}

function decrypt($decData, $secretKey, $vector) {
    $cipher = "AES-128-CBC";
    $options = 0;
    $decData = hex2bin($decData);
    $vector = hex2bin($vector);
    $decryptedData = openssl_decrypt($decData, $cipher, $secretKey, $options, $vector);
    if ($decryptedData === false) {
        throw new Exception('Decryption failed: ' . openssl_error_string());
    }
    return $decryptedData;
}
?>

It does not return any value.


Solution

  • In your PHP code there are several bugs:

    • The default Base64 decoding must be disabled by using OPENSSL_RAW DATA.
    • The raw IV must be applied (i.e. without hex2bin()).
    • decodeBytes() does not implement a simple hex decoding, i.e. hex2bin($decData) is wrong. You need to port decodeBytes() from Java to PHP.

    Your PHP decryption code with possible fixes:

    <?php
    $encryptedData = "lmncpiedimdofipgnokfeppgfncimkac";
    $secretKey = "0123456789ABCDEF"; // 16-character key
    $iv = "0123456789ABCDEF"; // 16-character initialization 
    
    try {
        $decryptedData = decrypt($encryptedData, $secretKey, $iv);
        echo "Decrypted data: " . $decryptedData;
    } catch (Exception $e) {
        echo "Error while decrypting: " . $e->getMessage();
        $e->getTrace();
    }
    
    function decodeBytes($data) {
        $res = '';
        for ($i = 0; $i < strlen($data); $i+=2){
            $high = (ord($data[$i]) - ord('a')) << 4;
            $low = ord($data[$i + 1]) - ord('a');
            $res .= chr($high + $low);
        }
        return $res;
    }
    
    function decrypt($decData, $secretKey, $vector) {
        $cipher = "AES-128-CBC";
        $options = OPENSSL_RAW_DATA;        // Fix 1
        //$vector = hex2bin($vector);       // Fix 2
        $decData = decodeBytes($decData);   // Fix 3
        $decryptedData = openssl_decrypt($decData, $cipher, $secretKey, $options, $vector);
        if ($decryptedData === false) {
            throw new Exception('Decryption failed: ' . openssl_error_string());
        }
        return $decryptedData;
    }
    ?>
    

    With these changes, decryption works and returns the plaintext hello world for your test data.


    Security:

    • A custom encoding is not necessary. It makes more sense to use a proven standard such as Base64 (or hex).
    • Note that deriving a key directly from a password/string is a vulnerability. Instead, it is better to use a reliable key derivation function (like PBKDF2) when using a password.
    • The reuse of key/IV pairs is also a vulnerability. This can be prevented, e.g. by using a random IV for each encryption, which is passed to the decrypting side along with the ciphertext.