繁体   English   中英

使用 PHP OpenSSL 使用 JAVA 密码解密 aes-128-gcm 编码内容

[英]Decrypt aes-128-gcm encoded content with JAVA Cipher using PHP OpenSSL

我必须使用用 JAVA 密码加密的 aes-128-gcm 解密一些发送到我的网站的数据。

客户有一个与 Talend 合作的提供商,他必须通过 URL 参数向我发送一些信息,而我试图使用 PHP ZD1EAA0E8179D35A0A5353D6EEC71C834 解密数据。 提供者并不能真正告诉我它是如何在他这边工作的,并且不太确定生成的代码中使用的变量(例如 IV 长度)。

加密方法是使用 Talend 组件在 JAVA 上完成的。 提供商将 Talend 生成的代码作为示例发送给我。

private static String encrypt(String data, String mainKey, int ivLength) throws Exception {
  final byte[] dataBytes = data.getBytes(UNICODE_FORMAT);
  byte[] initializationVector = generateInitializationVector(ivLength);
  final Cipher cipher = getAesGcmCipher(Cipher.ENCRYPT_MODE, mainKey, initializationVector);

  final byte[] encryptedData = cipher.doFinal(dataBytes);
  final byte[] encryptedBytes = new byte[encryptedData.length + ivLength];
  System.arraycopy(initializationVector, 0, encryptedBytes, 0, ivLength);
  System.arraycopy(encryptedData, 0, encryptedBytes, ivLength, encryptedData.length);

  return BASE64_ENCODER.apply(encryptedBytes);
}

private static String decrypt(String data, String mainKey, int ivLength) throws Exception {
  final byte[] encryptedBytes = BASE64_DECODER.apply(data.getBytes(UNICODE_FORMAT));

  final byte[] initializationVector = new byte[ivLength];
  System.arraycopy(encryptedBytes, 0, initializationVector, 0, ivLength);

  final Cipher cipher = getAesGcmCipher(Cipher.DECRYPT_MODE, mainKey, initializationVector);
  return new String(cipher.doFinal(encryptedBytes, ivLength, encryptedBytes.length - ivLength),
      UNICODE_FORMAT);
}

他还向我发送了一些用他正在使用的 class 初始化的常量:

static final String ALGO = "AES"; //$NON-NLS-1$
static final String GCMALGO = "AES/GCM/NoPadding"; //$NON-NLS-1$
static final String UNICODE_FORMAT = "UTF8"; //$NON-NLS-1$
static final String DES_ENCRYPTION_SCHEME = "DES"; //$NON-NLS-1$
private static final int DEFAULT_IV_LENGTH = 16;
public static final String NULL_PARAMETER_MESSAGE = "The parameter should not be null"; //$NON-NLS-1$
public static final String EMPTY_PARAMETER_MESSAGE = "String is empty"; //$NON-NLS-1$
private static final String KEY_GEN_ALGO = "PBKDF2WithHmacSHA256"; //$NON-NLS-1$
static final Random random = new SecureRandom();
static final BASE64Encoder b64Encoder = new DataMasking().new BASE64Encoder();
static final BASE64Decoder b64Dencoder = new DataMasking().new BASE64Decoder();
public static final Function<byte[], String> BASE64_ENCODER = bytes -> Base64.getEncoder().encodeToString(bytes);
public static final Function<byte[], byte[]> BASE64_DECODER = bytes -> Base64.getDecoder().decode(bytes);

据我了解,初始化向量放置在字符串的开头,并且我了解到标签由 Java 密码 object 自动放置在末尾。

我尝试使用openssl_encrypt()openssl_decrypt()在我的 PHP 端自己加密和解密一些内容,它工作正常,但我无法解密 JAVA 应用程序发送给我的数据。

我想知道这是否与我必须对二进制数据进行的字符串操作有关,或者提供者是否没有给我所需的所有必要信息。

我也不知道我必须用来解密数据的 Tag_length 是多少。 提供者还告诉我 IV_Length 是 16 个字符长,但是当我使用 openssl_cipher_iv_length('aes-128-gcm') 时,它建议 12。

这是我现在在 PHP 端的代码:

/**
 * @param string $str
 *   The URL parameter string
 */
function test_decrypt($str) {
  $key = 'MySuperPassword7';
  $cipher = 'aes-128-gcm';

  $iv_len = 16;
  $tag_length = 16;

  echo $str . '<br>';
  /**
   * Encryption test
   */
//  $tag = ""; 
//  $iv = openssl_random_pseudo_bytes($iv_len);
//  $enc_str = openssl_encrypt('Test of data to send', $cipher, $key, OPENSSL_RAW_DATA, $iv, $tag, "", $tag_length);
//  $encrypt = base64_encode($iv.$enc_str.$tag);

//  echo $key . '::' . $iv . '::' . $tag . '<br>';
//  echo '$enc_str<pre>';
//  var_dump($enc_str);
//  echo '</pre>';
//  echo '$encrypt<pre>';
//  var_dump($encrypt);
//  echo '</pre>';

  /**
   * Decryption part
   */
  $encrypt = base64_decode($str);
  $iv = substr($encrypt, 0, $iv_len);
  $tag = substr($encrypt, - $tag_length);
  $ciphertext = substr($encrypt, $iv_len, -$tag_length);

  $uncrypt = openssl_decrypt($ciphertext, $cipher, $key, 0, $iv, $tag);//OPENSSL_RAW_DATA + OPENSSL_NO_PADDING
  echo $iv_len . '::' . $tag_length . '<br>';
  echo $key . '::' . $iv . '::' . $tag . '<br>';
  echo '$encrypt<pre>';
  var_dump($encrypt);
  echo '</pre>';
  echo '$ciphertext<pre>';
  var_dump($ciphertext);
  echo '</pre>';
  echo '$uncrypt<pre>';
  var_dump($uncrypt);
  echo '</pre>';
  exit;
}

我还尝试使用在线工具来解密 Java 应用程序发送给我的数据,但到目前为止我找不到任何有用的东西。

编辑

正如@JohnConde 在评论中所问的那样,我可以分享我目前正在尝试解密的加密字符串和密钥。 我们决定使用与 Michael Fehr 的答案相同的数据进行测试:

  • 密钥:'1234567890123456'
  • 纯文本:“TytooF 的秘密数据”
  • 加密文本:VODKjhFETSxMcaa7x/LIOYCfmqD1iWSCuxX80reQ1KoFhmU8/A5AlH0Pg/ZoK1eNSdhBpUed

长度与答案的加密之一相同(P8kU/Gf27LU9qgmLpA8gpfHmdVuW5cC7XeKVIgmb3rgGzyjG0YHX5P/cFfRRoZpUxxTEKzj8)。

和以前一样,我无法解密数据......

以下是 Talend 组件的完整代码:

// ============================================================================
//
// Copyright (C) 2006-2019 Talend Inc. - www.talend.com
//
// This source code is available under agreement available at
// %InstallDIR%\features\org.talend.rcp.branding.%PRODUCTNAME%\%PRODUCTNAME%license.txt
//
// You should have received a copy of the agreement
// along with this program; if not, write to Talend SA
// 9 rue Pages 92150 Suresnes, France
//
// ============================================================================

package routines;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PushbackInputStream;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;
import java.util.Calendar;
import java.util.Date;
import java.util.Random;
import java.util.function.Function;
import java.util.stream.Stream;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;


/**
 * created by talend on 2016-04-08 Detailled comment.
 *
 */
public class DataMasking {

    static final String ALGO = "AES"; //$NON-NLS-1$

    static final String GCMALGO = "AES/GCM/NoPadding"; //$NON-NLS-1$

    static final String UNICODE_FORMAT = "UTF8"; //$NON-NLS-1$

    static final String DES_ENCRYPTION_SCHEME = "DES"; //$NON-NLS-1$

    private static final int DEFAULT_IV_LENGTH = 16;

    public static final String NULL_PARAMETER_MESSAGE = "The parameter should not be null"; //$NON-NLS-1$

    public static final String EMPTY_PARAMETER_MESSAGE = "String is empty"; //$NON-NLS-1$

    private static final String KEY_GEN_ALGO = "PBKDF2WithHmacSHA256"; //$NON-NLS-1$

    static final Random random = new SecureRandom();

    static final BASE64Encoder b64Encoder = new DataMasking().new BASE64Encoder();

    static final BASE64Decoder b64Dencoder = new DataMasking().new BASE64Decoder();

    public static final Function<byte[], String> BASE64_ENCODER = bytes -> Base64.getEncoder().encodeToString(bytes);

    public static final Function<byte[], byte[]> BASE64_DECODER = bytes -> Base64.getDecoder().decode(bytes);

    public static class DataMaskingRoutineException extends RuntimeException {

        private static final long serialVersionUID = -8622896150657449668L;

        public DataMaskingRoutineException() {
            super();
        }

        public DataMaskingRoutineException(String s) {
            super(s);
        }

        public DataMaskingRoutineException(String s, Object o) {
            super(s);
            System.out.println(o);
        }

    }


    /**
     * Encrypt String: Encrypts a string using AES 128 .
     * warning: this is not considered a secure function.
     *
     *
     * {talendTypes} String
     *
     * {Category} Data Masking
     *
     * {param} String("foo") encryptString: The string to be encrypted.
     * 
     * {param} byte[](new byte[] { 'T', 'a', 'l', 'e', 'n', 'd', 's', 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y' })
     * keyValue: the
     * key material of the secret key. The contents of the array are copied to protect against subsequent modification.
     * 
     * {example} encryptAES("foo", new byte[] { 'T', 'a', 'l', 'e', 'n', 'd', 's', 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y' })
     * result is UQ0VJZq5ymFkMYQeDrPi0A==
     *
     * @deprecated use {@link #encryptAESGCM(String, String, int)} instead of it
     * 
     */

    @Deprecated
    public static String encryptAES(String encryptString, byte[] keyValue) {

        if (encryptString == null || keyValue == null) {
            return NULL_PARAMETER_MESSAGE;
        }
        if (encryptString.length() == 0) {
            return EMPTY_PARAMETER_MESSAGE;
        }
        try {
            Key key = new SecretKeySpec(keyValue, ALGO);
            Cipher cipher = Cipher.getInstance(ALGO);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] encVal = cipher.doFinal(encryptString.getBytes());
            String encryptedValue = b64Encoder.encode(encVal);
            return encryptedValue;

        } catch (Exception e) {
            throw new DataMaskingRoutineException(e.getMessage(), e);
        }
    }


    /**
     * Encrypt String: Encrypts a string using AES GCM 128 .
     * 
     * {talendTypes} String
     * 
     * {Category} Data Masking
     * 
     * {param} String("foo") encryptString: The string to be encrypted.
     * 
     * {param} String("TalendMainKey123") the main key used to encrypt the data.
     * 
     * {example} encryptAESGCM("foo","TalendMainKey123") result could be
     * +ngs4RohAx8OvInmioRwRYS0pymcNkQJCKqsdUPZqUZppN4= (but it should change from an execution to another).
     * 
     */

    public static String encryptAESGCM(String encryptString, String mainKey) {

        return encryptAESGCM(encryptString, mainKey, DEFAULT_IV_LENGTH);
    }

    /**
     * Encrypt String: Encrypts a string using AES GCM 128 .
     * 
     * {talendTypes} String
     * 
     * {Category} Data Masking
     * 
     * {param} String("foo") encryptString: The string to be encrypted.
     * 
     * {param} String("TalendMainKey123") the main key used to encrypt the data.
     * 
     * {param} int the length of initializationVector. must be one of 12/13/14/15/16.
     * 
     * {example} encryptAESGCM("foo","TalendMainKey123",16) result could be
     * +ngs4RohAx8OvInmioRwRYS0pymcNkQJCKqsdUPZqUZppN4= (but it should change from an execution to another).
     * 
     */

    public static String encryptAESGCM(String encryptString, String mainKey, int ivLength) {

        if (encryptString == null || mainKey == null) {
            return NULL_PARAMETER_MESSAGE;
        }

        if (encryptString.length() == 0) {
            return EMPTY_PARAMETER_MESSAGE;
        }

        try {
            return encrypt(encryptString, mainKey, ivLength);
        } catch (Exception e) {
            throw new DataMaskingRoutineException(e.getMessage(), e);
        }
    }



    /**
     * decrypt String: Decrypts a string using AES 128.
     * warning: this is not considered a secure function.
     *
     *
     * {talendTypes} String
     *
     * {Category} Data Masking
     *
     * {param} String("UQ0VJZq5ymFkMYQeDrPi0A==") encryptedString: The string to be decrypted.
     *
     * {param} byte[](new byte[] { 'T', 'a', 'l', 'e', 'n', 'd', 's', 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y' })
     * keyValue: the key material of the secret key. The contents of the array are copied to protect against subsequent
     * modification.
     * 
     * {example} decryptAES("UQ0VJZq5ymFkMYQeDrPi0A==",new byte[] { 'T', 'a', 'l', 'e', 'n', 'd', 's', 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y' })
     * result is "foo"
     * 
     * @deprecated use {@link #decryptAESGCM(String, String, int)} instead of it
     * 
     */

    @Deprecated
    public static String decryptAES(String encryptedString, byte[] keyValue) {

        if (encryptedString == null || keyValue == null) {
            return NULL_PARAMETER_MESSAGE;
        }

        if (encryptedString.length() == 0) {
            return EMPTY_PARAMETER_MESSAGE;
        }
        try {
            Key key = new SecretKeySpec(keyValue, ALGO);
            Cipher cipher = Cipher.getInstance(ALGO);
            cipher.init(Cipher.DECRYPT_MODE, key);
            byte[] decordedValue = b64Dencoder.decodeBuffer(encryptedString);
            byte[] decValue = cipher.doFinal(decordedValue);
            String decryptedValue = new String(decValue);
            return decryptedValue;

        } catch (Exception e) {
            throw new DataMaskingRoutineException(e.getMessage(), e);
        }
    }

    /**
     * decrypt String: Decrypts a string using AES GCM 128.
     * 
     * {talendTypes} String
     * 
     * {Category} Data Masking
     * 
     * {param} String("+ngs4RohAx8OvInmioRwRYS0pymcNkQJCKqsdUPZqUZppN4=") encryptedString: The string to be decrypted.
     * 
     * {param} String("TalendMainKey123") the main key used to decrypt the data.
     * 
     * {param} int the length of initializationVector. must be one of 12/13/14/15/16.
     * 
     * {example} decryptAESGCM("+ngs4RohAx8OvInmioRwRYS0pymcNkQJCKqsdUPZqUZppN4=","TalendMainKey123",16) result
     * is "foo"
     * 
     */

    public static String decryptAESGCM(String encryptedString, String mainKey, int ivLength) {

        if (encryptedString == null || mainKey == null) {
            return NULL_PARAMETER_MESSAGE;
        }

        if (encryptedString.length() == 0) {
            return EMPTY_PARAMETER_MESSAGE;
        }

        try {
            return decrypt(encryptedString, mainKey, ivLength);

        } catch (Exception e) {
            throw new DataMaskingRoutineException(e.getMessage(), e);
        }

    }

    /**
     * decrypt String: Decrypts a string using AES GCM 128.
     * 
     * {talendTypes} String
     * 
     * {Category} Data Masking
     * 
     * {param} String("+ngs4RohAx8OvInmioRwRYS0pymcNkQJCKqsdUPZqUZppN4=") encryptedString: The string to be decrypted.
     * 
     * {param} String("TalendMainKey123") the main key used to decrypt the data.
     * 
     * {example} decryptAESGCM("+ngs4RohAx8OvInmioRwRYS0pymcNkQJCKqsdUPZqUZppN4=","TalendMainKey123") result
     * is "foo"
     * 
     */

    public static String decryptAESGCM(String encryptedString, String mainKey) {

        return decryptAESGCM(encryptedString, mainKey, DEFAULT_IV_LENGTH);
    }

    /**
     * Encrypt String: Encrypts a string using DES .
     * warning: this is not considered a secure function.
     *
     *
     * {talendTypes} String
     *
     * {Category} Data Masking
     *
     * {param} String("foo") unencryptedString: The string to be encrypted.
     *
     * {param} String("ThisIsSecretEncryptionKey") myEncryptionKey: the string with the DES key material.
     *
     * {example} encryptDES("foo") result is DmNj+x2LUXA=
     *
     * @throws Exception
     */

    public static String encryptDES(String unencryptedString, String myEncryptionKey) {

        if (unencryptedString == null || myEncryptionKey == null) {
            return NULL_PARAMETER_MESSAGE;
        }
        if (unencryptedString.length() == 0) {
            return EMPTY_PARAMETER_MESSAGE;
        }
        try {
            String encryptedString = null;

            String myEncryptionScheme = DES_ENCRYPTION_SCHEME;
            byte[] keyAsBytes = myEncryptionKey.getBytes(UNICODE_FORMAT);
            KeySpec myKeySpec = new DESKeySpec(keyAsBytes);
            SecretKeyFactory mySecretKeyFactory = SecretKeyFactory.getInstance(myEncryptionScheme);
            Cipher encipher = Cipher.getInstance(myEncryptionScheme);
            SecretKey key = mySecretKeyFactory.generateSecret(myKeySpec);

            encipher.init(Cipher.ENCRYPT_MODE, key);
            byte[] plainText = unencryptedString.getBytes(UNICODE_FORMAT);
            byte[] encryptedText = encipher.doFinal(plainText);
            encryptedString = b64Encoder.encode(encryptedText);

            return encryptedString;

        } catch (Exception e) {
            throw new DataMaskingRoutineException(e.getMessage());
        }

    }

    /**
     * Decrypt String: Decrypts a string using DES .
     * warning: this is not considered a secure function.
     *
     *
     * {talendTypes} String
     *
     * {Category} Data Masking
     *
     * {param} String("DmNj+x2LUXA=") encryptedString: the string with the DES key material.
     *
     * {param} String("ThisIsSecretEncryptionKey") myDecryptionKey: The string to be encrypted.
     *
     * {example} decryptDES("DmNj+x2LUXA=") result is "foo"
     *
     */

    public static String decryptDES(String encryptedString, String myDecryptionKey) {

        if (encryptedString == null || myDecryptionKey == null) {
            return NULL_PARAMETER_MESSAGE;
        }

        if (encryptedString.length() == 0) {
            return EMPTY_PARAMETER_MESSAGE;
        }
        try {
            String decryptedText = null;

            String myDecryptionScheme = DES_ENCRYPTION_SCHEME;

            byte[] keyAsBytes = myDecryptionKey.getBytes(UNICODE_FORMAT);

            KeySpec myKeySpec = new DESKeySpec(keyAsBytes);
            Cipher decipher = Cipher.getInstance(myDecryptionScheme);
            SecretKeyFactory mySecretKeyFactory = SecretKeyFactory.getInstance(myDecryptionScheme);
            SecretKey key = mySecretKeyFactory.generateSecret(myKeySpec);

            decipher.init(Cipher.DECRYPT_MODE, key);
            byte[] encryptedText = b64Dencoder.decodeBuffer(encryptedString);
            byte[] plainText = decipher.doFinal(encryptedText);

            StringBuilder stringBuilder = new StringBuilder();
            for (byte element : plainText) {
                stringBuilder.append((char) element);
            }
            decryptedText = stringBuilder.toString();
            return decryptedText;

        } catch (Exception e) {
            throw new DataMaskingRoutineException(e.getMessage(), e);
        }

    }


    /**
     * This method generates a secret Key using the key-stretching algorithm PBKDF2 of
     * <a href="https://docs.oracle.com/javase/7/docs/api/javax/crypto/package-summary.html">javax.crypto</a>.
     * It is basically a hashing algorithm slow by design, in order to increase the time
     * required for an attacker to try a lot of passwords in a bruteforce attack.
     * <br>
     * About the salt :
     * <ul>
     * <li>The salt is not secret, the use of Random is not critical and ensure determinism.</li>
     * <li>The salt is important to avoid rainbow table attacks.</li>
     * <li>The salt should be generated with SecureRandom() in case the passwords are stored.</li>
     * <li>In that case the salt should be stored in plaintext next to the password and a unique user identifier.</li>
     * </ul>
     *
     * @param password a password given as a {@code String}.
     * @param keyLength key length to generate
     * @return a {@code SecretKey} securely generated.
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    private static byte[] generateSecretKeyFromPassword(String password, int keyLength)
            throws NoSuchAlgorithmException, InvalidKeySpecException {
        byte[] salt = new byte[keyLength];
        new Random(password.hashCode()).nextBytes(salt);
        SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_GEN_ALGO);
        KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, keyLength << 3);
        return factory.generateSecret(spec).getEncoded();
    }

    private static String encrypt(String data, String mainKey, int ivLength) throws Exception {
        final byte[] dataBytes = data.getBytes(UNICODE_FORMAT);
        byte[] initializationVector = generateInitializationVector(ivLength);
        final Cipher cipher = getAesGcmCipher(Cipher.ENCRYPT_MODE, mainKey, initializationVector);

        final byte[] encryptedData = cipher.doFinal(dataBytes);
        final byte[] encryptedBytes = new byte[encryptedData.length + ivLength];
        System.arraycopy(initializationVector, 0, encryptedBytes, 0, ivLength);
        System.arraycopy(encryptedData, 0, encryptedBytes, ivLength, encryptedData.length);

        return BASE64_ENCODER.apply(encryptedBytes);
    }


    private static String decrypt(String data, String mainKey, int ivLength) throws Exception {
        final byte[] encryptedBytes = BASE64_DECODER.apply(data.getBytes(UNICODE_FORMAT));

        final byte[] initializationVector = new byte[ivLength];
        System.arraycopy(encryptedBytes, 0, initializationVector, 0, ivLength);

        final Cipher cipher = getAesGcmCipher(Cipher.DECRYPT_MODE, mainKey, initializationVector);
        return new String(cipher.doFinal(encryptedBytes, ivLength, encryptedBytes.length - ivLength),
                UNICODE_FORMAT);
    }

}

** 编辑 2 **

在与提供者调试了一段时间后,我们最终决定使用 Michael Fehr 提供的代码。

我从组件的代码中了解到,区别在于主键的使用方式。 但我不是 Java 开发人员,也许我误解了一些东西。

以防万一它可以帮助某人,这里是 IV 生成和密钥生成缺少的额外 Java 代码。

private static Cipher getAesGcmCipher(int encryptMode, String mainKey, byte[] initializationVector)
            throws Exception {
    int ivLength = initializationVector.length;
    if (Stream.of(12, 13, 14, 15, 16).noneMatch(i -> i == ivLength)) {
        throw new IllegalArgumentException("Invalid IV length"); //$NON-NLS-1$
    }
    final Cipher cipher = Cipher.getInstance(GCMALGO);
    SecretKey key = new SecretKeySpec(generateSecretKeyFromPassword(mainKey, mainKey.length()), ALGO);
    final GCMParameterSpec spec = new GCMParameterSpec(ivLength * 8, initializationVector);
    cipher.init(encryptMode, key, spec);
    return cipher;
}



private static byte[] generateSecretKeyFromPassword(String password, int keyLength)
          throws NoSuchAlgorithmException, InvalidKeySpecException {
      byte[] salt = new byte[keyLength];
      new Random(password.hashCode()).nextBytes(salt);
      SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_GEN_ALGO);
      KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, keyLength << 3);
      return factory.generateSecret(spec).getEncoded();
}

我在 Java 中设置了您的代码,并使用(固定)密钥和随机初始化向量进行了加密:

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Random;

public class SO_Main_Final {

    static final String ALGO = "AES"; //$NON-NLS-1$
    static final String GCMALGO = "AES/GCM/NoPadding"; //$NON-NLS-1$
    static final String UNICODE_FORMAT = "UTF8"; //$NON-NLS-1$
    static final Random random = new SecureRandom();

    public static void main(String[] args) throws Exception {
        System.out.println("https://stackoverflow.com/questions/62129604/decrypt-aes-128-gcm-encoded-content-with-java-cipher-using-php-openssl");
        String myData = "Secret data for TytooF";
        String myKey = "1234567890123456";
        String encryptString = encrypt(myData, myKey, 16);
        String decryptString = decrypt(encryptString, myKey, 16);
        System.out.println("encryptString:         " + encryptString);
        System.out.println("decryptString:         " + decryptString);

    }

    private static String encrypt(String data, String mainKey, int ivLength) throws Exception {
        final byte[] dataBytes = data.getBytes(UNICODE_FORMAT);
        // byte[] initializationVector = generateInitializationVector(ivLength);
        byte[] initializationVector = new byte[ivLength];
        random.nextBytes(initializationVector);
        SecretKeySpec secretKeySpec = new SecretKeySpec(mainKey.getBytes(UNICODE_FORMAT), ALGO);
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, initializationVector);
        Cipher cipher = Cipher.getInstance(GCMALGO);
        cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec);
        final byte[] encryptedData = cipher.doFinal(dataBytes);
        final byte[] encryptedBytes = new byte[encryptedData.length + ivLength];
        System.arraycopy(initializationVector, 0, encryptedBytes, 0, ivLength);
        System.arraycopy(encryptedData, 0, encryptedBytes, ivLength, encryptedData.length);
        System.out.println("data [String]        : " + data);
        System.out.println("data           length: " + dataBytes.length
                + " data: " + bytesToHex(dataBytes));
        System.out.println("mainKey        length: " + mainKey.getBytes(UNICODE_FORMAT).length
                + " data: " + bytesToHex(mainKey.getBytes(UNICODE_FORMAT)));
        System.out.println("initvector     length: " + initializationVector.length
                + " data: " + bytesToHex(initializationVector));
        System.out.println("encryptedBytes length: " + encryptedBytes.length
                + " data: " + bytesToHex(encryptedBytes));
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }

    private static String decrypt(String data, String mainKey, int ivLength) throws Exception {
        final byte[] encryptedBytes = Base64.getDecoder().decode(data.getBytes(UNICODE_FORMAT));
        final byte[] initializationVector = new byte[ivLength];
        System.arraycopy(encryptedBytes, 0, initializationVector, 0, ivLength);
        SecretKeySpec secretKeySpec = new SecretKeySpec(mainKey.getBytes(UNICODE_FORMAT), ALGO);
        GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, initializationVector);
        Cipher cipher = Cipher.getInstance(GCMALGO);
        cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmParameterSpec);
        return new String(cipher.doFinal(encryptedBytes, ivLength, encryptedBytes.length - ivLength),
                UNICODE_FORMAT);
    }

    private static String bytesToHex(byte[] bytes) {
        StringBuffer result = new StringBuffer();
        for (byte b : bytes) result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
        return result.toString();
    }
}

最后我得到了这个结果——“encryptString”需要转移到你的网站:

https://stackoverflow.com/questions/62129604/decrypt-aes-128-gcm-encoded-content-with-java-cipher-using-php-openssl
data [String]        : Secret data for TytooF
data           length: 22 data: 536563726574206461746120666f72205479746f6f46
mainKey        length: 16 data: 31323334353637383930313233343536
initvector     length: 16 data: 3fc914fc67f6ecb53daa098ba40f20a5
encryptedBytes length: 54 data: 3fc914fc67f6ecb53daa098ba40f20a5f1e6755b96e5c0bb5de29522099bdeb806cf28c6d181d7e4ffdc15f451a19a54c714c42b38fc
encryptString:         P8kU/Gf27LU9qgmLpA8gpfHmdVuW5cC7XeKVIgmb3rgGzyjG0YHX5P/cFfRRoZpUxxTEKzj8
decryptString:         Secret data for TytooF

在网络服务器/PHP 端,我稍微修改了您的代码(“主要更改”是 openssl_decrypt($ciphertext, $cipher, $key, true , $iv, $tag) 行上的更改,因为我们不提供 Base64 编码数据到解密方法,因为这是在一些代码行之前完成的 ($encrypt = base64_decode($str).

这是PHP代码:

<?php

/**
 * @param string $str
 *   The URL parameter string
 */
function test_decrypt($str)
{
    $key = '1234567890123456';
    $cipher = 'aes-128-gcm';
    $iv_len = 16;
    $tag_length = 16;
    echo $str . '<br>';
    /**
     * Decryption part
     */
    $encrypt = base64_decode($str);
    $iv = substr($encrypt, 0, $iv_len);
    $tag = substr($encrypt, -$tag_length);
    $ciphertext = substr($encrypt, $iv_len, -$tag_length);
    echo "" . "\xA";
    $value = unpack('H*', $iv);
    echo '<br>iv:' . $value[1];
    echo "" . "\xA";
    $value = unpack('H*', $ciphertext);
    echo '<br>ciphertext:' . $value[1];
    echo "" . "\xA";
    $value = unpack('H*', $tag);
    echo '<br>tag:' . $value[1];
    echo "<br>" . "\xA";
    $uncrypt = openssl_decrypt($ciphertext, $cipher, $key, true, $iv, $tag);//OPENSSL_RAW_DATA + OPENSSL_NO_PADDING
    echo '<br>DecryptedString: ' . $uncrypt . "\xA";
    $value = unpack('H*', $uncrypt);
    echo '<br>DecryptedString [byte[]]:' . $value[1];
    exit;
}

echo '<b>Output for https://stackoverflow.com/questions/62129604/decrypt-aes-128-gcm-encoded-content-with-java-cipher-using-php-openssl</b><br>' . "\xA";
echo '' . "\xA";
echo 'Start decryption' . "\xA";
$receivedData = "P8kU/Gf27LU9qgmLpA8gpfHmdVuW5cC7XeKVIgmb3rgGzyjG0YHX5P/cFfRRoZpUxxTEKzj8";
test_decrypt($receivedData);
?>

这是webserver上的解密output:

Output for https://stackoverflow.com/questions/62129604/decrypt-aes-128-gcm-encoded-content-with-java-cipher-using-php-openssl
Start decryption P8kU/Gf27LU9qgmLpA8gpfHmdVuW5cC7XeKVIgmb3rgGzyjG0YHX5P/cFfRRoZpUxxTEKzj8

iv:3fc914fc67f6ecb53daa098ba40f20a5
ciphertext:f1e6755b96e5c0bb5de29522099bdeb806cf28c6d181
tag:d7e4ffdc15f451a19a54c714c42b38fc

DecryptedString: Secret data for TytooF
DecryptedString [byte[]]:536563726574206461746120666f72205479746f6f46

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM