javaphpencryptionaes-gcmlanguage-interoperability

Decrypt AES/GCM/PKCS5Padding in PHP from Java payload


I want to do a simple encryption from SecureGMC java to be decrypted in PHP using AES/GCM/PKCS5Padding. The data I'm suppose to be decrypting derives from the local bank, which emphasizes on IV_SIZE=96 and TAG_BIT_LENGTH=128; they recommended me this link as reference. From what I read, openssl_decrypt() seems to be the best option, however, I can't find any $tag variable available for me to send over when decrypting it Java. Is openssl_decrypt() even recommended for this ?

cipher.java

public class SecuredGCMUsage {

    public static int AES_KEY_SIZE = 256 ;
    public static int IV_SIZE = 96 ;
    public static int TAG_BIT_LENGTH = 128 ;
    public static String ALGO_TRANSFORMATION_STRING = "AES/GCM/PKCS5Padding" ;

    public static void main(String args[]) {
            String messageToEncrypt = "Testing message to decrypt" ;
            
            byte[] aadData = "".getBytes() ; // Any random data can be used as tag. Some common examples could be domain name...

            // Use different key+IV pair for encrypting/decrypting different parameters

            // Generating Key
            SecretKey aesKey = null ;
            try {
                KeyGenerator keygen = KeyGenerator.getInstance("AES") ; // Specifying algorithm key will be used for 
                keygen.init(AES_KEY_SIZE) ; // Specifying Key size to be used, Note: This would need JCE Unlimited Strength to be installed explicitly 
                aesKey = keygen.generateKey() ;
            } catch(NoSuchAlgorithmException noSuchAlgoExc) { System.out.println("Key being request is for AES algorithm, but this cryptographic algorithm is not available in the environment "  + noSuchAlgoExc) ; System.exit(1) ; }

            // Generating IV
            byte iv[] = new byte[IV_SIZE];
            SecureRandom secRandom = new SecureRandom() ;
            secRandom.nextBytes(iv); // SecureRandom initialized using self-seeding
            

            // Initialize GCM Parameters
            GCMParameterSpec gcmParamSpec = new GCMParameterSpec(TAG_BIT_LENGTH, iv) ;      
            
            byte[] encryptedText = aesEncrypt(messageToEncrypt, aesKey,  gcmParamSpec, aadData) ;          
            
            System.out.println("Encrypted Text = " + Base64.getEncoder().encodeToString(encryptedText) ) ;

            byte[] decryptedText = aesDecrypt(encryptedText, aesKey, gcmParamSpec, aadData) ; // Same key, IV and GCM Specs for decryption as used for encryption.

            System.out.println("Decrypted text " + new String(decryptedText)) ;

            // Make sure not to repeat Key + IV pair, for encrypting more than one plaintext.
            secRandom.nextBytes(iv);
    }


    public static byte[] aesEncrypt(String message, SecretKey aesKey, GCMParameterSpec gcmParamSpec, byte[] aadData) {
            Cipher c = null ;

            try {
                    c = Cipher.getInstance(ALGO_TRANSFORMATION_STRING); // Transformation specifies algortihm, mode of operation and padding
            }catch(NoSuchAlgorithmException noSuchAlgoExc) {System.out.println("Exception while encrypting. Algorithm being requested is not available in this environment " + noSuchAlgoExc); System.exit(1); }
             catch(NoSuchPaddingException noSuchPaddingExc) {System.out.println("Exception while encrypting. Padding Scheme being requested is not available this environment " + noSuchPaddingExc); System.exit(1); }

            
            try {
                c.init(Cipher.ENCRYPT_MODE, aesKey, gcmParamSpec, new SecureRandom()) ;
            } catch(InvalidKeyException invalidKeyExc) {System.out.println("Exception while encrypting. Key being used is not valid. It could be due to invalid encoding, wrong length or uninitialized " + invalidKeyExc) ; System.exit(1); }
             catch(InvalidAlgorithmParameterException invalidAlgoParamExc) {System.out.println("Exception while encrypting. Algorithm parameters being specified are not valid " + invalidAlgoParamExc) ; System.exit(1); }

           try { 
                c.updateAAD(aadData) ; // add AAD tag data before encrypting
            }catch(IllegalArgumentException illegalArgumentExc) {System.out.println("Exception thrown while encrypting. Byte array might be null " +illegalArgumentExc ); System.exit(1);} 
            catch(IllegalStateException illegalStateExc) {System.out.println("Exception thrown while encrypting. CIpher is in an illegal state " +illegalStateExc); System.exit(1);} 
            catch(UnsupportedOperationException unsupportedExc) {System.out.println("Exception thrown while encrypting. Provider might not be supporting this method " +unsupportedExc); System.exit(1);} 
           
           byte[] cipherTextInByteArr = null ;
           try {
                cipherTextInByteArr = c.doFinal(message.getBytes()) ;
           } catch(IllegalBlockSizeException illegalBlockSizeExc) {System.out.println("Exception while encrypting, due to block size " + illegalBlockSizeExc) ; System.exit(1); }
             catch(BadPaddingException badPaddingExc) {System.out.println("Exception while encrypting, due to padding scheme " + badPaddingExc) ; System.exit(1); }

           return cipherTextInByteArr ;
    }


    public static byte[] aesDecrypt(byte[] encryptedMessage, SecretKey aesKey, GCMParameterSpec gcmParamSpec, byte[] aadData) {
           Cipher c = null ;
    
           try {
               c = Cipher.getInstance(ALGO_TRANSFORMATION_STRING); // Transformation specifies algortihm, mode of operation and padding
            } catch(NoSuchAlgorithmException noSuchAlgoExc) {System.out.println("Exception while decrypting. Algorithm being requested is not available in environment " + noSuchAlgoExc); System.exit(1); }
             catch(NoSuchPaddingException noSuchAlgoExc) {System.out.println("Exception while decrypting. Padding scheme being requested is not available in environment " + noSuchAlgoExc); System.exit(1); }  

            try {
                c.init(Cipher.DECRYPT_MODE, aesKey, gcmParamSpec, new SecureRandom()) ;
            } catch(InvalidKeyException invalidKeyExc) {System.out.println("Exception while encrypting. Key being used is not valid. It could be due to invalid encoding, wrong length or uninitialized " + invalidKeyExc) ; System.exit(1); }
             catch(InvalidAlgorithmParameterException invalidParamSpecExc) {System.out.println("Exception while encrypting. Algorithm Param being used is not valid. " + invalidParamSpecExc) ; System.exit(1); }

            try {
                c.updateAAD(aadData) ; // Add AAD details before decrypting
            }catch(IllegalArgumentException illegalArgumentExc) {System.out.println("Exception thrown while encrypting. Byte array might be null " +illegalArgumentExc ); System.exit(1);}
            catch(IllegalStateException illegalStateExc) {System.out.println("Exception thrown while encrypting. CIpher is in an illegal state " +illegalStateExc); System.exit(1);}
            
            byte[] plainTextInByteArr = null ;
            try {
                plainTextInByteArr = c.doFinal(encryptedMessage) ;
            } catch(IllegalBlockSizeException illegalBlockSizeExc) {System.out.println("Exception while decryption, due to block size " + illegalBlockSizeExc) ; System.exit(1); }
             catch(BadPaddingException badPaddingExc) {System.out.println("Exception while decryption, due to padding scheme " + badPaddingExc) ; System.exit(1); }

            return plainTextInByteArr ;
    }
}

cipher.java will produce

ENCRYPTED- WHcLaJZWwTTKD1fVkmOoH0KpShZRn/LIDQwp9Djz+0MG7bp+gO+4pHCmGw==
KEY- 4A5wU7DQ0orJv91J8eZu2yMcr6sHyuAiKaNe5KdM7iw=
IV- cG8zFrxyYSeXvwx7bxQrCp6LxaZ8GQhxUcrGJkZTzfKJaErLztV9dy/iz123cw/4wEz44IMtpNR0OZSz2SA+zZLfsge3m/WJlS9xwNSYjatzYMm123hpyStcFKedi+A8
DECRYPTED TEXT- Testing message to dencrypt

decryption.java (only decrypt) - I've isolated into a new class for handling only decryptions data to simulate production.

public class SecuredGCMUsage {

    public static int AES_KEY_SIZE = 256 ;
    public static int IV_SIZE = 96 ;
    public static int TAG_BIT_LENGTH = 128 ;
    public static String ALGO_TRANSFORMATION_STRING = "AES/GCM/PKCS5Padding" ;

    public static void main(String args[]) {
            // ARGS DETAILS 
            String payloadText = "WHcLaJZWwTTKD1fVkmOoH0KpShZRn/LIDQwp9Djz+0MG7bp+gO+4pHCmGw==";
            String payloadKey = "4A5wU7DQ0orJv91J8eZu2yMcr6sHyuAiKaNe5KdM7iw=";
            String payloadIv = "cG8zFrxyYSeXvwx7bxQrCp6LxaZ8GQhxUcrGJkZTzfKJaErLztV9dy/iz123cw/4wEz44IMtpNR0OZSz2SA+zZLfsge3m/WJlS9xwNSYjatzYMm123hpyStcFKedi+A8";

            byte[] aadData2 = "testing.com".getBytes() ; 
            byte[] encryptedText2 = Base64.getDecoder().decode(payloadText);
            byte[] decodedKey = Base64.getDecoder().decode(payloadKey);
            byte[] iv  = Base64.getDecoder().decode(payloadIv);
            

            GCMParameterSpec gcmParamSpec2 = new GCMParameterSpec(TAG_BIT_LENGTH, iv) ;      
            SecretKey aesKey2 = new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES"); 
            
            byte[] decryptedText2 = aesDecrypt(
                encryptedText2, 
                aesKey2, 
                gcmParamSpec2, 
                aadData2
            ) ; // Same key, IV and GCM Specs for decryption as used for encryption.
            System.out.println("DECRYPTED TEXT- " + new String(decryptedText2)) ;
    }


    public static byte[] aesDecrypt(byte[] encryptedMessage, SecretKey aesKey, GCMParameterSpec gcmParamSpec, byte[] aadData) {
           Cipher c = null ;
    
           try {
               c = Cipher.getInstance(ALGO_TRANSFORMATION_STRING); // Transformation specifies algortihm, mode of operation and padding
            } catch(NoSuchAlgorithmException noSuchAlgoExc) {System.out.println("Exception while decrypting. Algorithm being requested is not available in environment " + noSuchAlgoExc); System.exit(1); }
             catch(NoSuchPaddingException noSuchAlgoExc) {System.out.println("Exception while decrypting. Padding scheme being requested is not available in environment " + noSuchAlgoExc); System.exit(1); }  

            try {
                c.init(Cipher.DECRYPT_MODE, aesKey, gcmParamSpec, new SecureRandom()) ;
            } catch(InvalidKeyException invalidKeyExc) {System.out.println("Exception while encrypting. Key being used is not valid. It could be due to invalid encoding, wrong length or uninitialized " + invalidKeyExc) ; System.exit(1); }
             catch(InvalidAlgorithmParameterException invalidParamSpecExc) {System.out.println("Exception while encrypting. Algorithm Param being used is not valid. " + invalidParamSpecExc) ; System.exit(1); }

            try {
                // c.updateAAD(aadData) ; // Add AAD details before decrypting
            }catch(IllegalArgumentException illegalArgumentExc) {System.out.println("Exception thrown while encrypting. Byte array might be null " +illegalArgumentExc ); System.exit(1);}
            catch(IllegalStateException illegalStateExc) {System.out.println("Exception thrown while encrypting. CIpher is in an illegal state " +illegalStateExc); System.exit(1);}
            
            byte[] plainTextInByteArr = null ;
            try {
                plainTextInByteArr = c.doFinal(encryptedMessage) ;
            } catch(IllegalBlockSizeException illegalBlockSizeExc) {System.out.println("Exception while decryption, due to block size " + illegalBlockSizeExc) ; System.exit(1); }
             catch(BadPaddingException badPaddingExc) {System.out.println("Exception while decryption, due to padding scheme " + badPaddingExc) ; System.exit(1); }

            return plainTextInByteArr ;
    }
}

result (successs)

DECRYPTED TEXT- Testing message to dencrypt

However my struggle is in decryption.php where is constantly fails.

public static function decrypt_aes_gcm_pkcs5padding ($data)
{
    $ciphertext = $data["encryptedPayload"];
    $key = $data["encryptedSessionKey"];
    $iv = $data["iv"];
    // $aad = "";

    $encrypt = base64_decode($ciphertext);
    $ivlen = openssl_cipher_iv_length($cipher = "aes-256-gcm");
    $tag_length = 16;
    $iv = substr($encrypt, 0, $ivlen);
    $tag = substr($encrypt, -$tag_length);
    $ciphertext = substr($encrypt, $ivlen, -$tag_length);

    $ciphertext_raw = openssl_decrypt($ciphertext, $cipher, $key, OPENSSL_NO_PADDING, $iv, $tag);
    return $ciphertext_raw;
}

result (fail)


Solution

  • The decryption in the PHP code fails because IV, ciphertext and tag are determined incorrectly.

    Cipher#doFinal() in the Java code implicitly concatenates ciphertext and tag (ciphertext | tag). In contrast, the IV is neither implicitly nor explicitly concatenated. However, the PHP code assumes that the IV has also been concatenated (iv | ciphertext | tag). The separation performed on this assumption therefore fails.

    Since the ciphertext passed to openssl_decrypt() is not Base64 encoded due to the preceding separation, the OPENSSL_RAW_DATA flag (value: 1) must be set. GCM does not use padding. The OPENSSL_ZERO_PADDING flag (value: 2) disables padding, but need not be set explicitly since it is set implicitly for GCM. The OPENSSL_NO_PADDING flag (value: 3) is defined only in the context of asymmetric encryption and should not be used in the context of symmetric encryption. If set, it has the same effect as OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, which happens to be correct in this case, but generally sets OPENSSL_RAW_DATA unintentionally.

    The following PHP implementation decrypts the data of the Java code correctly:

    // Data from C# side
    $encrypt = base64_decode('7iJzLyqHMu3sVsUI4dCghjbf4gsQaFto/4L+6ccywRgq33hcWKOrz/pF');
    $key = base64_decode('ZQE3/FMRH1wOCFJWuaRpsizC/ltMFlxJvTYu0J4oN+Q=');
    $iv = base64_decode('SzlX3ihgFwBwUcDdWatoNTQ9BcMQ5nJoJqoV2eBtQuh/uP+eceqdUiVZldwOiUX8m5qIDYpyeqz7z8a+7xRWNUL3xnlcKBEemCrASLCG2tKpEXXMQD+a9t2v2WGUKQ6D');
    $aad = "sample aad";
    
    $tagLength = 16;
    $tag = substr($encrypt, strlen($encrypt) - $tagLength, $tagLength);
    $ciphertext = substr($encrypt, 0, strlen($encrypt) - $tagLength);
    
    $ciphertext_raw = openssl_decrypt($ciphertext, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $iv, $tag, $aad);
    print($ciphertext_raw);
    

    where the test data was generated with the Java code.

    The Java code is the reference code and cannot be changed. Nevertheless, the following should be noted: