【问题标题】:AES GCM in PHP/JavaPHP/Java 中的 AES GCM
【发布时间】:2020-11-25 18:40:36
【问题描述】:

我必须使用 GCM 块模式以 AES-256 编写加密/解密库。 我用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,调用编码/解码函数时 Java 代码中也存在 Base64,因此本文中不存在。

【问题讨论】:

  • 为什么php代码中只有base64,Java代码中没有?你验证输入了吗? (它们是您在每个阶段所期望的))
  • 这个忘了说了,这个我已经处理过了。当我调用java代码时,我正在使用它。
  • 你使用的是 php > 7.1.0 ?
  • 7.2.24 ?这有关系吗。我正在 PHP 7.2 上测试代码,但稍后我将不得不在 PHP 5.6 上运行它
  • 我看到标签,tag_length 是在 PHP 7.1 之后添加的。那么我们不能在 PHP 5.6 中使用 aes gcm 吗?

标签: aes


【解决方案1】:

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

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

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

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

这是 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

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

代码在 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");
        // https://stackoverflow.com/questions/65001817/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);
    }
}

【讨论】:

  • 感谢您的详细回答。我正在使用 PHP 7.2 / Java 8 及其工作(使用您的代码艺术)。现在有没有办法可以在 PHP 5.6 中使用它
  • 由于我不再使用 PHP 5.x,我无法帮助您解决这个问题,但我看到仍然有可用的 AES GCM 模式的纯 PHP 代码(例如在 Github 上)。您能否将我的答案标记为已接受 - 谢谢?
猜你喜欢
  • 2017-10-21
  • 2018-07-31
  • 2021-09-11
  • 1970-01-01
  • 2019-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-29
相关资源
最近更新 更多