PHP/Java 中的 AES GCM

AES GCM in PHP/Java

我必须使用 GCM 块模式在 AES-256 中编​​写 encryption/decryption 库。 我在 java 中写了同样的东西,它工作正常。

这是代码:

private static final int GCM_IV_SIZE_BYTES = 12;
    private static final int GCM_TAG_SIZE_BYTES = 16;
    private static final int GCM_SALT_SIZE_BYTES = 16;
public static byte[] encrypt(byte[] plaintext, byte[] dataKey, String version) throws Exception
    {
        long startTime = System.currentTimeMillis();
        // Generate Initialization Vector
        byte[] IV = generateIV();
        
        // Get Cipher Instance
        Cipher cipher = getCipher();
        
        // Get Salt
        byte[] salt = generateSalt();
        
        // Store Version
        byte[] versionArr = new byte[3];
        versionArr = version.getBytes();
        
        // Generate Key
        SecretKeySpec keySpec = new SecretKeySpec(dataKey, "AES");
    
        // Create GCMParameterSpec
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_SIZE_BYTES * 8, IV);
        
        // Initialize Cipher for ENCRYPT_MODE
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmParameterSpec);
        
        // Perform Encryption
        byte[] cipherText = cipher.doFinal(plaintext);
        
        
        
        int capacity = 3 + GCM_SALT_SIZE_BYTES + GCM_IV_SIZE_BYTES + plaintext.length + GCM_TAG_SIZE_BYTES;
        
        // Create ByteBuffer & add SALT, IV & CipherText
        ByteBuffer buffer = ByteBuffer.allocate(capacity);
        buffer.put(versionArr);
        buffer.put(salt);
        buffer.put(IV);
        buffer.put(cipherText);
        long endTime = System.currentTimeMillis();
        System.out.println("Encryption Time : "+(endTime - startTime)+"ms");
        // return the final encrypted cipher txt
        return buffer.array();
    }

public static String decrypt(byte[] cipherText, byte[] dataKey) throws Exception
    {
        long startTime = System.currentTimeMillis();
        if (cipherText.length < GCM_IV_SIZE_BYTES + GCM_TAG_SIZE_BYTES + GCM_SALT_SIZE_BYTES) throw new IllegalArgumentException();
        ByteBuffer buffer = ByteBuffer.wrap(cipherText);
    
        byte[]version = new byte[3];
        buffer.get(version, 0, version.length);
        
        System.out.println(new String(version));
        // Get Salt from Cipher
        byte[] salt = new byte[GCM_SALT_SIZE_BYTES];
        buffer.get(salt, 0, salt.length);
        System.out.println(new String(salt));
        // GET IV from cipher
        byte[] ivBytes1 = new byte[GCM_IV_SIZE_BYTES];
        buffer.get(ivBytes1, 0, ivBytes1.length);
        System.out.println(new String(ivBytes1));
        byte[] encryptedTextBytes = new byte[buffer.capacity() - salt.length - ivBytes1.length- 3];
        buffer.get(encryptedTextBytes);
        
        System.out.println("enc tect bytes");
        System.out.println(new String(encryptedTextBytes));
        // Get Cipher Instance
        Cipher cipher = getCipher();
        
        // Generate Key
        SecretKeySpec keySpec = new SecretKeySpec(dataKey, "AES");
        
        // Create GCMParameterSpec
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_SIZE_BYTES * 8, ivBytes1);
        
        // Initialize Cipher for DECRYPT_MODE
        cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
        
        // Perform Decryption
        byte[] decryptedText = cipher.doFinal(encryptedTextBytes);
        long endTime = System.currentTimeMillis();
        System.out.println("Decryption Time : "+(endTime - startTime)+"ms");
        return new String(decryptedText);
    }

现在的问题是我必须在 PHP 中编写相同的库,然后我必须使用 PHP 库加密并使用 Java 库解密,反之亦然

这是我的 PHP 加密代码:

function encrypt($key, $textToEncrypt){
    $cipher = 'aes-256-gcm';
    $iv_len = 12;
    $tag_length = 16;
    $version_length = 3;
    $salt_length = 16;

    $version = "v01";
    $iv = openssl_random_pseudo_bytes($iv_len);
    $salt = openssl_random_pseudo_bytes($salt_length);
    $tag = ""; // will be filled by openssl_encrypt
    $ciphertext = openssl_encrypt($textToEncrypt, $cipher, $key, 0, $iv, $tag, "", $tag_length);

    $encrypted = base64_encode($version.$salt.$iv.$ciphertext.$tag);
    return $encrypted;

}

现在的问题是,当我使用 PHP 加密数据然后尝试使用 Java 代码对其进行解密时,出现以下异常

:Exception in thread "main" javax.crypto.AEADBadTagException: Tag mismatch!
    at com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:578)
    at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1049)
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:985)
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:847)
    at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:446)
    at javax.crypto.Cipher.doFinal(Cipher.java:2164)

我在这里错过了什么?

PHP 代码中的 Base64 存在,在调用 encode/decode 函数时 Java 代码中存在同样的内容,因此在此 post 代码中不存在。

PHP 和 Java 之间使用 AES GCM 模式的跨平台加密正在运行。有一些细节可能会阻止您成功完成。

首先:在 PHP 侧 openssl_encrypt returns 一个 base64 编码的密文,当将密文与版本、iv 和标签连接时,它再次被 base64 编码。为了避免这种情况,我将 OPENSSL 选项设置为“OPENSSL_RAW_DATA”。

其次:在Java端,标签附加到密文,因此“密文|标签”可以直接使用。

请注意:我的示例只是展示了 PHP 端的加密和 Java 端的解密如何工作,但可能与您的源代码无关(特别是Java side) - 我懒得采用我的例子:-)

这是PHP端的输出:

AES GCM in PHP/Java
ciphertext: djAx/kMbxfJI5Zx7lTWeDbw601cD2wkjBvuKeVBbKOZHll98GstPNfi1xHvyRlBwJDQ6YWvpymsk76kwbBbD0cBsOzzK/tH8UpA=

将密文复制到Java程序中,让它运行:

AES GCM in PHP/Java
decryptedtext: The quick brown fox jumps over the lazy dog

您可以在下面找到这两个程序的源代码。 安全警告:代码使用固定和硬编码密钥 - 不要这样做 这个。这些程序没有任何异常处理,仅用于教育目的。

代码是 运行ning PHP > 7.2 和 Java 11+。

PHP-代码:

<?php
function encrypt($key, $textToEncrypt){
    $cipher = 'aes-256-gcm';
    $iv_len = 12;
    $tag_length = 16;
    $version_length = 3;
    $version = "v01";
    $iv = openssl_random_pseudo_bytes($iv_len);
    $tag = ""; // will be filled by openssl_encrypt
    $ciphertext = openssl_encrypt($textToEncrypt, $cipher, $key, OPENSSL_RAW_DATA, $iv, $tag, "", $tag_length);
    $encrypted = base64_encode($version.$iv.$ciphertext.$tag);
    return $encrypted;
}

echo 'AES GCM in PHP/Java' . PHP_EOL;
// ### security warning: never use hardcoded keys in source ###
$key = '12345678901234567890123456789012';
$plaintext = 'The quick brown fox jumps over the lazy dog';
$ciphertext = encrypt($key, $plaintext);
echo 'ciphertext: ' . $ciphertext . PHP_EOL;
?>

Java-代码:

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Base64;

public class SO_final {
    public static void main(String[] args) throws NoSuchPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
        System.out.println("AES GCM in PHP/Java");
        // 
        String ciphertext = "djAx/kMbxfJI5Zx7lTWeDbw601cD2wkjBvuKeVBbKOZHll98GstPNfi1xHvyRlBwJDQ6YWvpymsk76kwbBbD0cBsOzzK/tH8UpA=";
        // ### security warning: never use hardcoded keys in source ###
        byte[] key = "12345678901234567890123456789012".getBytes(StandardCharsets.UTF_8);
        String decryptedtext = decryptGcmBase64(key, ciphertext);
        System.out.println("decryptedtext: " + decryptedtext);
    }

    public static String decryptGcmBase64(byte[] key, String ciphertextBase64) throws
            NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException,
            InvalidAlgorithmParameterException, BadPaddingException, IllegalBlockSizeException {
        byte[] ciphertextComplete = Base64.getDecoder().decode(ciphertextBase64);
        // split data
        // base64 encoding $encrypted = base64_encode($version.$iv.$ciphertext.$tag);
        byte[] version = Arrays.copyOfRange(ciphertextComplete, 0, 3); // 3 bytes
        byte[] iv = Arrays.copyOfRange(ciphertextComplete, 3, 15); // 12 bytes
        byte[] ciphertextWithTag = Arrays.copyOfRange(ciphertextComplete, 15, ciphertextComplete.length);
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(16 * 8, iv);
        Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5Padding");//NOPadding
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmParameterSpec);
        return new String(cipher.doFinal(ciphertextWithTag), StandardCharsets.UTF_8);
    }
}