如何在php和java之间使用aes/gcm/加密和解密

wpcxdonn  于 2021-07-03  发布在  Java
关注(0)|答案(1)|浏览(2215)

我用php加密,用aes/gcm和java通信,但是不起作用。这是代码。我不知道哪里出错了?

<?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;
}

这是java代码

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代码不能修改,因为它不是我的,我必须适应java代码。

q9yhzks0

q9yhzks01#

这两种代码的行为可能与您预期的不同。
在java代码中,虽然 PKCS5Padding (在java中,指定了pkcs7padding的同义词)。sunjce提供程序禁用指定的 PKCS5Padding 适用于gcm NoPadding . 这很有用,因为gcm是一种不需要填充的流密码模式。
应该提到的是,行为依赖于版本。只有早期的jdk版本(如8、11、12)才接受 PKCS5Padding 用于gcm并作为 NoPadding . 相比之下,更高版本的jdk(例如14、15)会引发一个异常。另外,其他提供者的行为也可能不同。
在php代码中,密文由 openssl_encrypt 作为原始数据,因此只对base64编码一次(即在与iv和tag串联之后),这是应该的。因此,代码的行为就像 OPENSSL_RAW_DATA 已设置。这是因为 OPENSSL_NO_PADDING (值为3)被使用,它实际上只为非对称加密定义,而不是为对称加密定义,因此不应该在这里应用。对称加密的标志是 OPENSSL_RAW_DATA (值为1)和 OPENSSL_ZERO_PADDING (值为2),以便 OPENSSL_NO_PADDING 相当于 OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING ,从而隐式设置 OPENSSL_RAW_DATA .
请注意 OPENSSL_ZERO_PADDING 不启用零填充,但禁用默认的pkcs7填充。与sunjce提供程序类似,openssl也隐式地禁用gcm的默认pkcs7填充,即无论 OPENSSL_ZERO_PADDING 是否设置,gcm不使用填充。
总之,可以说,填充和标志被排除在错误的原因之外:在这两个代码中,在php代码中没有应用填充 OPENSSL_RAW_DATA 已设置。
不幸的是,您没有描述错误,因此只能猜测。这个问题可能是由不兼容的键引起的,因为这两个代码在我的机器上工作并且是兼容的。
如注解中所述,对于aes-128/192/256,必须使用16/24/32字节密钥。在java代码中,密钥的长度决定aes变量,即16字节的长度意味着aes-128。在php代码中,必须显式指定aes变量,例如。 aes-128-gcm . 太长的键被简单地切断,太短的键被0值填充。
例如,如果在java代码中使用16字节的密钥进行加密(aes-128)和php代码中 aes-128-gcm 并且应用相同的密钥进行解密,则解密成功。
如果有进一步的问题,请张贴使用的java版本,错误信息和完整的样本数据,即密钥,明文和密文。
样本数据:

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

java代码(在jdk 11下)提供了以下密文(当然,由于随机生成的iv,每个加密都不同):

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

这个密文可以由php解密 decrypt 方法使用 aes-128-cbc 以及上面的键:

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

相应地 aesGcmEncrypt 返回相同的密文,如果 aes-128-cbc ,使用上述键和相同的iv(对于后者,php代码中随机生成的iv必须替换为java代码中生成的iv,当然仅用于此测试):

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

相关问题