4

I use PHP to encrypt use AES/GCM to communicate with JAVA,but it does not work。 This is the code。I don't know where is the wrong?

<?php

$key = "123456789012345678901234567890";
$plaintext = "aaaaaaa";
$encryptStr = aesGcmEncrypt($plaintext, $key);
echo "加密后:" . $encryptStr;


function aesGcmEncrypt($plaintext, $key)
{

    $ivlen = openssl_cipher_iv_length($cipher = "aes-128-gcm");
    $iv = openssl_random_pseudo_bytes($ivlen);
    $ciphertext_raw = openssl_encrypt($plaintext, $cipher, $key, OPENSSL_NO_PADDING, $iv, $tag);
    $ciphertext = base64_encode($iv . $ciphertext_raw . $tag);
    return $ciphertext;
}

function decrypt($str, $key)
{
    $encrypt = base64_decode($str);
    $ivlen = openssl_cipher_iv_length($cipher = "aes-128-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;
}

this is the java code。

private static String aesGcmEncrypt(String content, byte[] key) {
        try {
            System.out.println(content);
            System.out.println(content.getBytes(UTF_8).length);
            // 根据指定算法ALGORITHM自成密码器
            Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5Padding");
            SecretKeySpec skey = new SecretKeySpec(key, "AES");
            cipher.init(Cipher.ENCRYPT_MODE, skey);
            //获取向量
            byte[] ivb = cipher.getIV();
            byte[] encodedByteArray = cipher.doFinal(content.getBytes(UTF_8));

            byte[] message = new byte[ivb.length + encodedByteArray.length];

            System.arraycopy(ivb, 0, message, 0, ivb.length);
            System.arraycopy(encodedByteArray, 0, message, ivb.length, encodedByteArray.length);
            String ss = Base64.getEncoder().encodeToString(message);
            return ss;
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException
                | BadPaddingException e) {
            return null;
        }
    }

JAVA code can't modify,because it is not mine,I must adapt to the java code.

doit
  • 41
  • 2
  • In the Java code you specifically add PKCS#5 (better to say PKCS#7) padding, but you `OPENSSL_NO_PADDING` in the PHP code – Bram Dec 07 '20 at 09:13
  • On Java-side the algorithm-key is defined by the length of the key (e.g. using a 128 bit = 16 byte long key will give you AES 128 GCM mode, a 256 bit = 32 byte long key will end in AES 256 GCM). On PHP-side you need to specify "hard coded" the algorithm (e.g. "aes-128-gcm" or "aes-192-gcm" or "aes-256-gcm"). Second point: just use "OPENSSL_RAW_DATA" as option and leave out "OPENSSL_NO_PADDING" as already commented by @Bram. – Michael Fehr Dec 07 '20 at 09:34

1 Answers1

2

Both codes probably behave differently than you expect.

In the Java code no padding is used at all, although PKCS5Padding (in Java a synonym for PKCS7Padding) is specified. The SunJCE Provider disables the specified PKCS5Padding for GCM and applies NoPadding. This is useful because GCM is a stream cipher mode that does not require padding.
It should be mentioned that the behavior is version dependent. Only earlier JDK versions (e.g. 8, 11, 12) accept PKCS5Padding for GCM and run it as NoPadding. Later JDK versions (e.g. 14, 15), in contrast, raise an exception. Also, other providers may behave differently.

In the PHP code the ciphertext is returned by openssl_encrypt as raw data and therefore is only Base64 encoded once (namely after concatenation with IV and tag), as it should be. Thus the code behaves as if the flag OPENSSL_RAW_DATA is set. This is because OPENSSL_NO_PADDING (with the value 3) is used, which is actually only defined for asymmetric encryption but not for symmetric encryption, and therefore should not be applied here at all. Flags for symmetric encryption are OPENSSL_RAW_DATA (with the value 1) and OPENSSL_ZERO_PADDING (with the value 2), so that OPENSSL_NO_PADDING is equivalent to OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, thus implicitly setting OPENSSL_RAW_DATA.
Note that OPENSSL_ZERO_PADDING does not enable Zero padding, but disables the default PKCS7 padding. Similar to the SunJCE Provider, also openssl implicitly disables the default PKCS7 padding for GCM, i.e. no matter if OPENSSL_ZERO_PADDING is set or not, no padding is used for GCM.

In summary it can be said that padding and flags are ruled out as the reasons for the error: In both codes there is no padding applied, in the PHP code OPENSSL_RAW_DATA is set.

Unfortunately you did not describe the error, so just guesses are possible. Probably the issue is caused by incompatible keys, since both codes work on my machine and are compatible.
As already mentioned in the comments, for AES-128/192/256 a 16/24/32 bytes key must be used. In the Java code the length of the key determines the AES variant, i.e. a length of e.g. 16 bytes implies AES-128. In the PHP code the AES variant must be explicitly specified, e.g. aes-128-gcm. A key that is too long is simply cut off, a key that is too short is padded with 0 values.
For example, if in the Java code a 16 bytes key is used for encryption (AES-128) and in the PHP code aes-128-gcm and the same key is applied for decryption, then the decryption is successful.

In case of further problems please post the used Java version, the error message and also complete sample data, i.e. key, plaintext and ciphertext.


Sample data:

Plaintext (UTF8):   The quick brown fox jumps over the lazy dog
Key (UTF8):         0123456789012345

The Java code provides (under JDK 11) the following ciphertext (this is of course different for each encryption because of the randomly generated IV):

Ciphertext (Base64): 8DcD/QwKeFG1u2N1ve3mtsX1Lq7js33ESTigT2GH6Lrqrckh5I4qzkJMG3rnuJ9CSFZ1jai8LTChe3tuIJSMLmMTbUQ9mB0=
IV (hex):            f03703fd0c0a7851b5bb6375

This ciphertext can be decrypted by the PHP decrypt method using aes-128-cbc and the above key:

print(decrypt("8DcD/QwKeFG1u2N1ve3mtsX1Lq7js33ESTigT2GH6Lrqrckh5I4qzkJMG3rnuJ9CSFZ1jai8LTChe3tuIJSMLmMTbUQ9mB0=", "0123456789012345") . "\n");

Accordingly aesGcmEncrypt returns the same ciphertext if aes-128-cbc, the above key and the same IV are used (for the latter the randomly generated IV in the PHP code has to be replaced by the IV generated in the Java code, of course only for this test):

$iv = hex2bin('f03703fd0c0a7851b5bb6375'); // IV to use in aesGcmEncrypt
print(aesGcmEncrypt("The quick brown fox jumps over the lazy dog", "0123456789012345") . "\n");
Topaco
  • 40,594
  • 4
  • 35
  • 62