Unable to decrypt data using PHP which is encrypted with Java - Stack Overflow

admin2025-04-17  5

We are using the following code to encrypt data in Java, and trying to convert the logic to PHP. The data encrypted with one language cannot be decrypted with the other language. Is there any difference?

My Java class

public class EncYes {
    
    private static final char[] hexDigits = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        
    public static void main(String[] args) {
        try {
            String encString=null;
            
            EncYes enc = new EncYes();
            
            switch(args[0]){
               case "e":
                 System.out.println(enc.encrypt(args[1],args[2])); 
               break;  
               case "d":
                 System.out.println(enc.decrypt(args[1],args[2])); 
               break; 
             }
        }  catch (Exception e) {
            System.out.println(e);
        }
    }
     
    public String encrypt(String json, String key) throws NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
        Cipher cipher = null;
        EncYes enc = new EncYes();
        SecretKeySpec skeySpec = new SecretKeySpec(enc.hexfromString(key), "AES");
        byte[] ivSrc = new byte[12];
        GCMParameterSpec ivSpec = new GCMParameterSpec(128, ivSrc);
        cipher = Cipher.getInstance("AES/GCM/NoPadding");
        cipher.init(1, skeySpec, ivSpec);
        byte[] encstr = cipher.doFinal(json.getBytes());
        return enc.hextoString(encstr);
    }
      
    public String decrypt(String json, String key) throws NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
        Cipher cipher = null;
        EncYes enc = new EncYes();
        SecretKeySpec skeySpec = new SecretKeySpec(enc.hexfromString(key), "AES");
        byte[] ivSrc = new byte[12];
        GCMParameterSpec ivSpec = new GCMParameterSpec(128, ivSrc);
        cipher = Cipher.getInstance("AES/GCM/NoPadding");
        cipher.init(2, skeySpec, ivSpec);
      
        byte[] encstr = cipher.doFinal(enc.hexfromString(json));
        return new String(encstr);
    }
      
      
    public static byte[] hexfromString(String s) {
        int i = s.length();
        byte[] abyte0 = new byte[(i + 1) / 2];
        int j = 0;
        int k = 0;
        if (i % 2 == 1) {
            abyte0[k++] = (byte)HexfromDigit(s.charAt(j++));
        }
        while(j < i) {
            abyte0[k++] = (byte)(HexfromDigit(s.charAt(j++)) << 4 | HexfromDigit(s.charAt(j++)));
        }
        return abyte0;
    }

    public static int HexfromDigit(char c) {
        if (c >= '0' && c <= '9')
            return c - 48;
        if (c >= 'A' && c <= 'F')
            return (c - 65) + 10;
        if (c >= 'a' && c <= 'f')
            return (c - 97) + 10;
        else
            throw new IllegalArgumentException("invalid hex digit: ");
    }

    public static String asHex(byte[] buf) {
        StringBuffer strbuf = new StringBuffer(buf.length * 2);

        for(int i = 0; i < buf.length; ++i) {
            if ((buf[i] & 255) < 16) {
                strbuf.append("0");
            }

            strbuf.append(Long.toString((long)(buf[i] & 255), 16));
        }

        return strbuf.toString();
    }

    public static String HextoString(byte abyte0[], int i, int j) {
        char ac[] = new char[j * 2];
        int k = 0;
        for (int l = i; l < i + j; l++) {
             byte byte0 = abyte0[l];
             ac[k++] = hexDigits[byte0 >>> 4 & 0xf];
             ac[k++] = hexDigits[byte0 & 0xf];
        }
        return new String(ac);
    }

    public static String hextoString(byte[] abyte0) {
        return HextoString(abyte0, 0, abyte0.length);
    }

    public static String generateIv() {
        UUID uId = UUID.randomUUID();
        return uId.toString().replace("-", "");
    }
                 
}

My PHP class

class EncYes {

    private static $hexDigits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];

    public static function main($args) {
        try {
            $enc = new EncYes();

            switch ($args[0]) {
                case "e":
                    return $enc->encrypt($args[1], $args[2]) . "\n";
                    break;
                case "d":
                    return $enc->decrypt($args[1], $args[2]) . "\n";
                    break;
            }
        } catch (Exception $e) {
            echo $e->getMessage() . "\n";
        }
    }

    public function encrypt($json, $key) {
        $iv = str_repeat("\0", 12);
        $cipher = "aes-128-gcm";
        $tag = "";

        $encrypted = openssl_encrypt($json, $cipher, $this->hexfromString($key), OPENSSL_RAW_DATA, $iv, $tag,"",12);
        if ($encrypted === false) {
            throw new Exception("Encryption failed");
        }

        return $this->hextoString($encrypted . $tag);
    }

    public function decrypt($json, $key) {
        $iv = str_repeat("\0", 12); // 12-byte IV filled with zeros
        $cipher = "aes-128-gcm";

        $data = $this->hexfromString($json);
        $encrypted = substr($data, 0, -12);
        $tag = substr($data, -12);

        $decrypted = openssl_decrypt($encrypted, $cipher, $this->hexfromString($key), OPENSSL_RAW_DATA, $iv, $tag);
        if ($decrypted === false) {
            throw new Exception("Decryption failed");
        }

        return $decrypted;
    }

    public static function hexfromString($s) {
        $i = strlen($s);
        $abyte0 = array_fill(0, (int)(($i + 1) / 2), 0);
        $j = 0;
        $k = 0;

        if ($i % 2 == 1) {
            $abyte0[$k++] = self::HexfromDigit($s[$j++]);
        }

        while ($j < $i) {
            $abyte0[$k++] = (self::HexfromDigit($s[$j++]) << 4) | self::HexfromDigit($s[$j++]);
        }

        return implode(array_map("chr", $abyte0));
    }

    public static function HexfromDigit($c) {
        if ($c >= '0' && $c <= '9') {
            return ord($c) - ord('0');
        }
        if ($c >= 'A' && $c <= 'F') {
            return (ord($c) - ord('A')) + 10;
        }
        if ($c >= 'a' && $c <= 'f') {
            return (ord($c) - ord('a')) + 10;
        }
        throw new InvalidArgumentException("invalid hex digit: " . $c);
    }

    public static function asHex($buf) {
        $strbuf = "";

        for ($i = 0; $i < strlen($buf); $i++) {
            $byte = ord($buf[$i]);
            if (($byte & 255) < 16) {
                $strbuf .= "0";
            }
            $strbuf .= dechex($byte & 255);
        }

        return $strbuf;
    }

    public static function HextoString2($abyte0, $i, $j) {
        $ac = array_fill(0, $j * 2, '0');
        $k = 0;

        for ($l = $i; $l < $i + $j; $l++) {
            $byte0 = ord($abyte0[$l]);
            $ac[$k++] = self::$hexDigits[$byte0 >> 4 & 0xf];
            $ac[$k++] = self::$hexDigits[$byte0 & 0xf];
        }

        return implode("", $ac);
    }

    public static function hextoString($abyte0) {
        return self::HextoString2($abyte0, 0, strlen($abyte0));
    }

    public static function generateIv() {
        return str_replace("-", "", uuid_create());
    }
}

Sample data for the Java code:

Encryption ------------------- 
java EncYes "e" "Hello" "a59d23ac19020c989cd8566a4ea16646ad4e02f67516cedc3bd7d833efda516a" 
B062024E2B9C4D4FFB204AB3622459CEFEAEFF969D 

Decryption ------------------ 
java EncYes "d" "B062024E2B9C4D4FFB204AB3622459CEFEAEFF969D" "a59d23ac19020c989cd8566a4ea16646ad4e02f67516cedc3bd7d833efda516a" 
Hello

We are using the following code to encrypt data in Java, and trying to convert the logic to PHP. The data encrypted with one language cannot be decrypted with the other language. Is there any difference?

My Java class

public class EncYes {
    
    private static final char[] hexDigits = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        
    public static void main(String[] args) {
        try {
            String encString=null;
            
            EncYes enc = new EncYes();
            
            switch(args[0]){
               case "e":
                 System.out.println(enc.encrypt(args[1],args[2])); 
               break;  
               case "d":
                 System.out.println(enc.decrypt(args[1],args[2])); 
               break; 
             }
        }  catch (Exception e) {
            System.out.println(e);
        }
    }
     
    public String encrypt(String json, String key) throws NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
        Cipher cipher = null;
        EncYes enc = new EncYes();
        SecretKeySpec skeySpec = new SecretKeySpec(enc.hexfromString(key), "AES");
        byte[] ivSrc = new byte[12];
        GCMParameterSpec ivSpec = new GCMParameterSpec(128, ivSrc);
        cipher = Cipher.getInstance("AES/GCM/NoPadding");
        cipher.init(1, skeySpec, ivSpec);
        byte[] encstr = cipher.doFinal(json.getBytes());
        return enc.hextoString(encstr);
    }
      
    public String decrypt(String json, String key) throws NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
        Cipher cipher = null;
        EncYes enc = new EncYes();
        SecretKeySpec skeySpec = new SecretKeySpec(enc.hexfromString(key), "AES");
        byte[] ivSrc = new byte[12];
        GCMParameterSpec ivSpec = new GCMParameterSpec(128, ivSrc);
        cipher = Cipher.getInstance("AES/GCM/NoPadding");
        cipher.init(2, skeySpec, ivSpec);
      
        byte[] encstr = cipher.doFinal(enc.hexfromString(json));
        return new String(encstr);
    }
      
      
    public static byte[] hexfromString(String s) {
        int i = s.length();
        byte[] abyte0 = new byte[(i + 1) / 2];
        int j = 0;
        int k = 0;
        if (i % 2 == 1) {
            abyte0[k++] = (byte)HexfromDigit(s.charAt(j++));
        }
        while(j < i) {
            abyte0[k++] = (byte)(HexfromDigit(s.charAt(j++)) << 4 | HexfromDigit(s.charAt(j++)));
        }
        return abyte0;
    }

    public static int HexfromDigit(char c) {
        if (c >= '0' && c <= '9')
            return c - 48;
        if (c >= 'A' && c <= 'F')
            return (c - 65) + 10;
        if (c >= 'a' && c <= 'f')
            return (c - 97) + 10;
        else
            throw new IllegalArgumentException("invalid hex digit: ");
    }

    public static String asHex(byte[] buf) {
        StringBuffer strbuf = new StringBuffer(buf.length * 2);

        for(int i = 0; i < buf.length; ++i) {
            if ((buf[i] & 255) < 16) {
                strbuf.append("0");
            }

            strbuf.append(Long.toString((long)(buf[i] & 255), 16));
        }

        return strbuf.toString();
    }

    public static String HextoString(byte abyte0[], int i, int j) {
        char ac[] = new char[j * 2];
        int k = 0;
        for (int l = i; l < i + j; l++) {
             byte byte0 = abyte0[l];
             ac[k++] = hexDigits[byte0 >>> 4 & 0xf];
             ac[k++] = hexDigits[byte0 & 0xf];
        }
        return new String(ac);
    }

    public static String hextoString(byte[] abyte0) {
        return HextoString(abyte0, 0, abyte0.length);
    }

    public static String generateIv() {
        UUID uId = UUID.randomUUID();
        return uId.toString().replace("-", "");
    }
                 
}

My PHP class

class EncYes {

    private static $hexDigits = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];

    public static function main($args) {
        try {
            $enc = new EncYes();

            switch ($args[0]) {
                case "e":
                    return $enc->encrypt($args[1], $args[2]) . "\n";
                    break;
                case "d":
                    return $enc->decrypt($args[1], $args[2]) . "\n";
                    break;
            }
        } catch (Exception $e) {
            echo $e->getMessage() . "\n";
        }
    }

    public function encrypt($json, $key) {
        $iv = str_repeat("\0", 12);
        $cipher = "aes-128-gcm";
        $tag = "";

        $encrypted = openssl_encrypt($json, $cipher, $this->hexfromString($key), OPENSSL_RAW_DATA, $iv, $tag,"",12);
        if ($encrypted === false) {
            throw new Exception("Encryption failed");
        }

        return $this->hextoString($encrypted . $tag);
    }

    public function decrypt($json, $key) {
        $iv = str_repeat("\0", 12); // 12-byte IV filled with zeros
        $cipher = "aes-128-gcm";

        $data = $this->hexfromString($json);
        $encrypted = substr($data, 0, -12);
        $tag = substr($data, -12);

        $decrypted = openssl_decrypt($encrypted, $cipher, $this->hexfromString($key), OPENSSL_RAW_DATA, $iv, $tag);
        if ($decrypted === false) {
            throw new Exception("Decryption failed");
        }

        return $decrypted;
    }

    public static function hexfromString($s) {
        $i = strlen($s);
        $abyte0 = array_fill(0, (int)(($i + 1) / 2), 0);
        $j = 0;
        $k = 0;

        if ($i % 2 == 1) {
            $abyte0[$k++] = self::HexfromDigit($s[$j++]);
        }

        while ($j < $i) {
            $abyte0[$k++] = (self::HexfromDigit($s[$j++]) << 4) | self::HexfromDigit($s[$j++]);
        }

        return implode(array_map("chr", $abyte0));
    }

    public static function HexfromDigit($c) {
        if ($c >= '0' && $c <= '9') {
            return ord($c) - ord('0');
        }
        if ($c >= 'A' && $c <= 'F') {
            return (ord($c) - ord('A')) + 10;
        }
        if ($c >= 'a' && $c <= 'f') {
            return (ord($c) - ord('a')) + 10;
        }
        throw new InvalidArgumentException("invalid hex digit: " . $c);
    }

    public static function asHex($buf) {
        $strbuf = "";

        for ($i = 0; $i < strlen($buf); $i++) {
            $byte = ord($buf[$i]);
            if (($byte & 255) < 16) {
                $strbuf .= "0";
            }
            $strbuf .= dechex($byte & 255);
        }

        return $strbuf;
    }

    public static function HextoString2($abyte0, $i, $j) {
        $ac = array_fill(0, $j * 2, '0');
        $k = 0;

        for ($l = $i; $l < $i + $j; $l++) {
            $byte0 = ord($abyte0[$l]);
            $ac[$k++] = self::$hexDigits[$byte0 >> 4 & 0xf];
            $ac[$k++] = self::$hexDigits[$byte0 & 0xf];
        }

        return implode("", $ac);
    }

    public static function hextoString($abyte0) {
        return self::HextoString2($abyte0, 0, strlen($abyte0));
    }

    public static function generateIv() {
        return str_replace("-", "", uuid_create());
    }
}

Sample data for the Java code:

Encryption ------------------- 
java EncYes "e" "Hello" "a59d23ac19020c989cd8566a4ea16646ad4e02f67516cedc3bd7d833efda516a" 
B062024E2B9C4D4FFB204AB3622459CEFEAEFF969D 

Decryption ------------------ 
java EncYes "d" "B062024E2B9C4D4FFB204AB3622459CEFEAEFF969D" "a59d23ac19020c989cd8566a4ea16646ad4e02f67516cedc3bd7d833efda516a" 
Hello
Share Improve this question edited Feb 3 at 10:54 Topaco 49.8k4 gold badges45 silver badges80 bronze badges asked Feb 1 at 7:40 Shijin TRShijin TR 7,78811 gold badges63 silver badges125 bronze badges 7
  • Please make visible how your question is different to existing, similar questions. Additionally take a look at existing questions for how to provide example data and code that shows and allows to reproduce both your expected behaviour and the actual results. Please edit all relevant details into your question, not into comments. Thanks! – hakre Commented Feb 1 at 7:59
  • 2 This post is not a duplicate of the linked post. The problem here is related to the GCM mode, which is not used at all in the linked post. – Topaco Commented Feb 1 at 8:56
  • 1 Did you check that aes-128-gcm is supported by your PHP server, e.g. printing result from openssl_get_cipher_methods() ? – Livio Commented Feb 1 at 9:32
  • @Topaco: Oh, then this perhaps was a mistake relating to similar questions in general. Can you please briefly check stackoverflow.com/q/79403081/367456 , I'm interested in your assessment, I fear it's a similar thing. – hakre Commented Feb 1 at 19:54
  • @hakre - The post you referred to doesn't seem to me to be a duplicate. The current implementation follows the encrypt-then-MAC scheme and is OK in principle. An alternative approach would be direct JWE. – Topaco Commented Feb 2 at 7:52
 |  Show 2 more comments

1 Answer 1

Reset to default 4

The main issue is that the Java code uses a tag length of 128 bit (1st parameter in new GCMParameterSpec(128, ivSrc)), while the PHP code applies 12 byte = 96 bit (last parameter in openssl_encrypt($json, $cipher, $this->hexfromString($key), OPENSSL_RAW_DATA, $iv, $tag,"",12) and when separating ciphertext and tag during decryption).

128 bit guarantees the greatest security, so this tag length should be used (unless there are valid reasons for a smaller tag length).


Other possible problems, vulnerabilities or inefficiencies are:

  • The algorithm specified in the PHP code is aes-128-gcm, i.e. the two codes are only compatible if a 16 byte key, i.e. AES-128, is used. For a different key length or AES variant, the specification in the PHP code must be adapted accordingly. This is not required on the Java side, as the AES variant is implicitly derived from the key length.
  • A zero IV is used, which is a critical vulnerability for CTR based modes like GCM (at least with a fixed key), see here. A possible solution is to generate a random IV for each encryption. The IV is not secret and is passed to the decrypting side along with the ciphertext and tag (usually concatenated: IV|ciphertext|tag).
  • In the Java code, a charset encoding should be explicitly specified in getBytes() or new String(), as otherwise platform-dependent default values are applied (at least in older Java versions). In addition, hex encoding has been supported since Java 17, s. HexFormat.

Update regarding your test data:
Your test data shows that you are using a 32 bytes key, i.e. AES-256. Therefore, in the PHP code aes-256-gcm must be used instead of aes-128-gcm.
In addition, the tag size in the PHP code must be changed from 12 bytes to 16 bytes so that the PHP code is compatible with the Java code.
Furthermore, the PHP code can be shortened considerably if the built-in bin2hex() and hex2bin() are used instead of hextoString() and hexfromString() respectively.

Complete code (without main($args)):

class EncYes {
    const TAG_SIZE = 16;            // Fix 1: Set the tag length to 16 bytes (note that 16 bytes is the PHP/OpenSSL default).
    const CIPHER =  "aes-256-gcm";  // Fix 2: set AES-256 as the algorithm, as a 32 bytes key is used.
    public function encrypt($json, $key) {
        $iv = str_repeat("\0", 12); // Keep in mind: A static IV is a serious vulnerability for GCM with a fixed key.
        $tag = "";
        $encrypted = openssl_encrypt($json, self::CIPHER, hex2bin($key), OPENSSL_RAW_DATA, $iv, $tag, "", self::TAG_SIZE); 
        if ($encrypted === false) {
            throw new Exception("Encryption failed");
        }
        return bin2hex($encrypted . $tag);
    }
    public function decrypt($json, $key) {
        $iv = str_repeat("\0", 12); // Keep in mind: A static IV is a serious vulnerability for GCM with a fixed key.
        $data = hex2bin($json);
        $encrypted = substr($data, 0, -self::TAG_SIZE); 
        $tag = substr($data, -self::TAG_SIZE);
        $decrypted = openssl_decrypt($encrypted, self::CIPHER, hex2bin($key), OPENSSL_RAW_DATA, $iv, $tag);
        if ($decrypted === false) {
            throw new Exception("Decryption failed");
        }
        return $decrypted;
    }
}

Test:

// decryption of your sample data
$aesenc = new EncYes();
$dt = $aesenc->decrypt("B062024E2B9C4D4FFB204AB3622459CEFEAEFF969D", "a59d23ac19020c989cd8566a4ea16646ad4e02f67516cedc3bd7d833efda516a");
print($dt . PHP_EOL); // Hello

// complete encryption/decryption cycle
$ct = $aesenc->encrypt("The quick brown fox jumps over the lazy dog", "a59d23ac19020c989cd8566a4ea16646ad4e02f67516cedc3bd7d833efda516a");
$dt = $aesenc->decrypt($ct, "a59d23ac19020c989cd8566a4ea16646ad4e02f67516cedc3bd7d833efda516a");
print($dt . PHP_EOL); // The quick brown fox jumps over the lazy dog
转载请注明原文地址:http://www.anycun.com/QandA/1744835393a88283.html