问:
Decrypt aes-128-gcm encoded content with JAVA Cipher using PHP OpenSSL
I must decrypt some data send to my website, using aes-128-gcm encrypted with JAVA Cipher.

The customer has a provider working with Talend who must send me some information via URL parameter, and I'm stuck trying to decrypt the Data using PHP Openssl. The provider is not really able to tell me how is it working on his side, and is not too sure about the variables used on the generated code (like IV Length for instance).

The encryption method is done on JAVA using a Talend component. The provider send me the Talend generated code as an example.

```<code>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);
}
</code>```

He also send me some constants initialised with the class he is using:

```<code>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);
</code>```

As I understand the Initialisation Vector is placed at the beginning of the string, and I learn that the Tag is automatically placed at the end by the Java Cipher object.

I tried to encrypt and decrypt some content by my own on my PHP side, using openssl_encrypt()
 and openssl_decrypt(), and it is working fine, but I cannot manage to decrypt the data the JAVA application is sending me.

I wonder if it has anything to do with the manipulation of string I have to do on binary data, or if the provider didn't give me all the necessary information I needed.

I also don't know what would be the Tag_length I have to use to decrypt the data.
The provider also tells me the IV_Length is 16 characters long, but when I use openssl_cipher_iv_length('aes-128-gcm'), it propose 12.

Here is the code I'm having for now on my PHP side:

```<code>/**
 * @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```';
//  var_dump($enc_str);
//  echo '```';
//  echo '$encrypt```';
//  var_dump($encrypt);
//  echo '```';

  /**
   * 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```';
  var_dump($encrypt);
  echo '```';
  echo '$ciphertext```';
  var_dump($ciphertext);
  echo '```';
  echo '$uncrypt```';
  var_dump($uncrypt);
  echo '```';
  exit;
}
</code>```

I also tried to use online tools to decrypt the data the Java app is sending me, but I couldn't find anything working for so far.

Edit

As asked by @JohnConde in the comment, I can share the encrypted string and key I'm currently trying to decrypt.
We decided to test with the same data as the answer of Michael Fehr :

Key : '1234567890123456'

Plain text : "Secret data for TytooF"

Encrypted text : VODKjhFETSxMcaa7x/LIOYCfmqD1iWSCuxX80reQ1KoFhmU8/A5AlH0Pg/ZoK1eNSdhBpUed

The length is the same as the encrypted one of the answer (P8kU/Gf27LU9qgmLpA8gpfHmdVuW5cC7XeKVIgmb3rgGzyjG0YHX5P/cFfRRoZpUxxTEKzj8).

As before, I'm not able to decrypt the data...

Here is the full code of the Talend component:

```<code>// ============================================================================
//
// 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);
    }

}
</code>```

** EDIT 2 **

After debugging for some times with the provider, we finally decided to use the code provided by Michael Fehr.

What I understood from the code of the component, is that the difference is in the way the main key is used. But I'm not a Java developer, and maybe I misunderstood something.

Just in case it can help someone, here is the additional Java code missing for the IV generation, and the Secret key generation.

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();
}
```

答:
I setup your code in Java and made an encryption with a (fixed) key and an random initialization vector:

```<code>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();
    }
}
</code>```

In the end I got this result - the "encryptString" needs to get transfered to your website:

```<code>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
</code>```

On webserver/PHP-side I slightly modified your code (the "main change" was the change on the line openssl_decrypt($ciphertext, $cipher, $key, true, $iv, $tag) because we do not present Base64-encoded data to the decrypt
method as this is done some codelines before ($encrypt = base64_decode($str).

Here is the PHP-code:

```<code><?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 "" . "\n";
    $value = unpack('H*', $iv);
    echo '<br>iv:' . $value[1];
    echo "" . "\n";
    $value = unpack('H*', $ciphertext);
    echo '<br>ciphertext:' . $value[1];
    echo "" . "\n";
    $value = unpack('H*', $tag);
    echo '<br>tag:' . $value[1];
    echo "<br>" . "\n";
    $uncrypt = openssl_decrypt($ciphertext, $cipher, $key, true, $iv, $tag);//OPENSSL_RAW_DATA + OPENSSL_NO_PADDING
    echo '<br>DecryptedString: ' . $uncrypt . "\n";
    $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>' . "\n";
echo '' . "\n";
echo 'Start decryption' . "\n";
$receivedData = "P8kU/Gf27LU9qgmLpA8gpfHmdVuW5cC7XeKVIgmb3rgGzyjG0YHX5P/cFfRRoZpUxxTEKzj8";
test_decrypt($receivedData);
?>
</code>```

This is the decryption output on the webserver:

```<code>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
</code>```

Edit Oct. 10th 2021: The code above is using an IV/nonce with a length of 16 bytes but the recommended IV/nonce length is 12.

Body:
I must decrypt some data send to my website, using aes-128-gcm encrypted with JAVA Cipher.

The customer has a provider working with Talend who must send me some information via URL parameter, and I'm stuck trying to decrypt the Data using PHP Openssl. The provider is not really able to tell me how is it working on his side, and is not too sure about the variables used on the generated code (like IV Length for instance).

The encryption method is done on JAVA using a Talend component. The provider send me the Talend generated code as an example.

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

He also send me some constants initialised with the class he is using:

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);

As I understand the Initialisation Vector is placed at the beginning of the string, and I learn that the Tag is automatically placed at the end by the Java Cipher object.

I tried to encrypt and decrypt some content by my own on my PHP side, using openssl_encrypt()
 and openssl_decrypt(), and it is working fine, but I cannot manage to decrypt the data the JAVA application is sending me.

I wonder if it has anything to do with the manipulation of string I have to do on binary data, or if the provider didn't give me all the necessary information I needed.

I also don't know what would be the Tag_length I have to use to decrypt the data.
The provider also tells me the IV_Length is 16 characters long, but when I use openssl_cipher_iv_length('aes-128-gcm'), it propose 12.

Here is the code I'm having for now on my PHP side:

/**
 * @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 . '
';
  /**
   * 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 . '
';
//  echo '$enc_str
';
//  var_dump($enc_str);
//  echo '
';
//  echo '$encrypt
';
//  var_dump($encrypt);
//  echo '
';

  /**
   * 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 . '
';
  echo $key . '::' . $iv . '::' . $tag . '
';
  echo '$encrypt
';
  var_dump($encrypt);
  echo '
';
  echo '$ciphertext
';
  var_dump($ciphertext);
  echo '
';
  echo '$uncrypt
';
  var_dump($uncrypt);
  echo '
';
  exit;
}

I also tried to use online tools to decrypt the data the Java app is sending me, but I couldn't find anything working for so far.

Edit

As asked by @JohnConde in the comment, I can share the encrypted string and key I'm currently trying to decrypt.
We decided to test with the same data as the answer of Michael Fehr :

Key : '1234567890123456'

Plain text : "Secret data for TytooF"

Encrypted text : VODKjhFETSxMcaa7x/LIOYCfmqD1iWSCuxX80reQ1KoFhmU8/A5AlH0Pg/ZoK1eNSdhBpUed

The length is the same as the encrypted one of the answer (P8kU/Gf27LU9qgmLpA8gpfHmdVuW5cC7XeKVIgmb3rgGzyjG0YHX5P/cFfRRoZpUxxTEKzj8).

As before, I'm not able to decrypt the data...

Here is the full code of the Talend component:

// ============================================================================
//
// 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
     * javax.crypto.
     * 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.
     *

     * About the salt :
     *

     *
The salt is not secret, the use of Random is not critical and ensure determinism.

     *
The salt is important to avoid rainbow table attacks.

     *
The salt should be generated with SecureRandom() in case the passwords are stored.

     *
In that case the salt should be stored in plaintext next to the password and a unique user identifier.

     *

     *
     * @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);
    }

}

** EDIT 2 **

After debugging for some times with the provider, we finally decided to use the code provided by Michael Fehr.

What I understood from the code of the component, is that the difference is in the way the main key is used. But I'm not a Java developer, and maybe I misunderstood something.

Just in case it can help someone, here is the additional Java code missing for the IV generation, and the Secret key generation.

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();
}

Body:
I setup your code in Java and made an encryption with a (fixed) key and an random initialization vector:

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();
    }
}

In the end I got this result - the "encryptString" needs to get transfered to your website:

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

On webserver/PHP-side I slightly modified your code (the "main change" was the change on the line openssl_decrypt($ciphertext, $cipher, $key, true, $iv, $tag) because we do not present Base64-encoded data to the decrypt
method as this is done some codelines before ($encrypt = base64_decode($str).

Here is the PHP-code:

<?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 "" . "\n";
    $value = unpack('H*', $iv);
    echo '<br>iv:' . $value[1];
    echo "" . "\n";
    $value = unpack('H*', $ciphertext);
    echo '<br>ciphertext:' . $value[1];
    echo "" . "\n";
    $value = unpack('H*', $tag);
    echo '<br>tag:' . $value[1];
    echo "<br>" . "\n";
    $uncrypt = openssl_decrypt($ciphertext, $cipher, $key, true, $iv, $tag);//OPENSSL_RAW_DATA + OPENSSL_NO_PADDING
    echo '<br>DecryptedString: ' . $uncrypt . "\n";
    $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>' . "\n";
echo '' . "\n";
echo 'Start decryption' . "\n";
$receivedData = "P8kU/Gf27LU9qgmLpA8gpfHmdVuW5cC7XeKVIgmb3rgGzyjG0YHX5P/cFfRRoZpUxxTEKzj8";
test_decrypt($receivedData);
?>

This is the decryption output on the webserver:

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

Edit Oct. 10th 2021: The code above is using an IV/nonce with a length of 16 bytes but the recommended IV/nonce length is 12.


来源: stackexchange

问:
What is the best way to host my cli application in k8s?
I have two services one is http(s) based while the other is cli which exists in a separate deployment (not on the same host as the first one). Lets name these services as Alice and Bob.

Alice is a http server while bob  is cli tool that is used to put some data into Alice on demand.

What have I done so far ?

Bob exists as a separate pod and since it does not have a http service to provide, I have used "sleep infinity" in its container CMD to make it run always.

What do I expect ?

Considering it a service that will not be in use most of the time except when someone needs to provision data in Alice. What other deployment options exist for this kind of application?

What I don't expect ?

For the following reason, I cannot let it reside inside the same container as Alice

No http service exist for it, time constraints, lot of code refactoring required. Its my official project so can't reveal more details.

答:
You have two options in my view.
Option 1: Bob is CLI tools which does not require a endpoint, so you can deploy this as kubernetes batch job. It is super easy and you can use corn expression to schedule it.
Here is a link https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/

Alice is a service so you can execute it based on the endpoint you expose. So it can be an ingress or loadbalancer or nodeport whatever you want based on your specific need.

Option 2: You can use Knative architecture. You need to install knative crds based on the cloud provider. Once installed, you can simply use event driven - knative eventing to call batch. For example, you want to call Bob based on some event say Pubsub or SNS update, you can very well do it using it.
Knative service is a simple service and easy to implement. It is like kubernetes manifest file (it has only service, unlike kubernetes deployment and service). So this you can use it as an endpoint when you want to invoke Alice as service.

Hope this helps.

ContentLicense: CC BY-SA 4.0


问:
Why do I get "MismatchSenderId" from GCM server side?
I'm trying to create a push service for my Android app, and I follow Google GCM's documentation and example for this matter:

I can register/unregister my Android app. From my server side, I can see that I have one subscription registered, but when I try to send a message from server to my Android app I always get following error:

Error sending message to device #0: MismatchSenderId

For my Android app, I use SENDER_ID = 200000000001.

And for my server side as Google says I use API_KEY = AIzxxxxxxxxxxxxxxxxXxxXxxXxxxXXXXXxxxxs

I followed this document:

http://developer.android.com/guide/google/gcm/demo.html

I've enabled GCM in my Google API panel too, yet I'll get that annoying error message.

How can I fix this problem?

答: With the deprecation of GCM and removal of its APIs, it appears that you could see MismatchSenderId if you try to use GCM after May 29, 2019. See the Google GCM and FCM FAQ for more details.</p>

ContentLicense: CC BY-SA 3.0


问:
Printing Right Triangles w/ Nested For-Loop
Here's my loop:

int height = Convert.ToInt32(Console.ReadLine());

Console.WriteLine("Printing a " + height + " by " + height + " right triangle...");

for (int i = 1; i <= height; i++)
{
    Console.WriteLine("*");
    for (int j = 1; j <= i; j++)
    {
        Console.Write("*");
    }
```

<p>And here is the resulting print (program takes a user input for the height, this example has a height of 5:</p>

<pre class="lang-none prettyprint-override"><code>*
**
***
****
****
</code>```

As you can see, I can't get the last star to print on the last row. This makes absolutely no sense to me, as I've compared it to many other similar constructs and there appears to be no discrepancies between the one I've constructed and the ones others have made. I just started coding about a month ago so I'm still a greenhorn.

答:
As pointed out, the issue is with the Console.WriteLine("")*. If you count the amount of rows being produced after running your code segment, you will find that there is an extra row each time.
Running your code segment with a height of 5 will actually produce 6 rows of stars:

```<code>*
**
***
****
*****
*****
</code>```

This is because before you even enter the inner loop, you are printing a star and then starting a new line.
To simplify this, it is best to assign each loop with a role, the inner loop should be the one outputting the stars. The outer loop is responsible for printing the new line.

Here is what I came up with. I modified the for loops to use the variables row and col. I find it much easier to comprehend.

```<code>for (int row = 1; row <= height; row++) {
   for (int col = 1; col <= row; col++){
      Console.Write("*");
   }
   Console.WriteLine();
}
</code>```

Body:
Here's my loop:

int height = Convert.ToInt32(Console.ReadLine());

Console.WriteLine("Printing a " + height + " by " + height + " right triangle...");

for (int i = 1; i <= height; i++)
{
    Console.WriteLine("*");
    for (int j = 1; j <= i; j++)
    {
        Console.Write("*");
    }

And here is the resulting print (program takes a user input for the height, this example has a height of 5:

*
**
***
****
****

As you can see, I can't get the last star to print on the last row. This makes absolutely no sense to me, as I've compared it to many other similar constructs and there appears to be no discrepancies between the one I've constructed and the ones others have made. I just started coding about a month ago so I'm still a greenhorn.

Body:
As pointed out, the issue is with the Console.WriteLine("")*. If you count the amount of rows being produced after running your code segment, you will find that there is an extra row each time.
Running your code segment with a height of 5 will actually produce 6 rows of stars:

*
**
***
****
*****
*****

This is because before you even enter the inner loop, you are printing a star and then starting a new line.
To simplify this, it is best to assign each loop with a role, the inner loop should be the one outputting the stars. The outer loop is responsible for printing the new line.

Here is what I came up with. I modified the for loops to use the variables row and col. I find it much easier to comprehend.

for (int row = 1; row <= height; row++) {
   for (int col = 1; col <= row; col++){
      Console.Write("*");
   }
   Console.WriteLine();
}


问:
How to store a const char* in std :: string?
```<code>    char       *buffer1 = "abc";

    const char *buffer2 = (const char*) buffer;

    std :: string str (buffer2);
</code>```

This works, but I want to declare the std::string object i.e. str, once and use it many times to store different const char*.

What's the way out?

答:
You can just re-assign:</p>

```<code>const char *buf1 = "abc";
const char *buf2 = "def";

std::string str(buf1);

str = buf2; // Calls str.operator=(const char *)
</code>```

Body:
    char       *buffer1 = "abc";

    const char *buffer2 = (const char*) buffer;

    std :: string str (buffer2);

This works, but I want to declare the std::string object i.e. str, once and use it many times to store different const char*.

What's the way out?

Body:
You can just re-assign:

const char *buf1 = "abc";
const char *buf2 = "def";

std::string str(buf1);

str = buf2; // Calls str.operator=(const char *)


问:
Deep stubbing in Scala
I am facing to mock nested function problem in Scala context. I have found answer like this:
How to mock fields of inner singleton objects? which works but isn't concise. I am wondering in Scala community is there something like mockito's deepstub?</p>

答:
You can use ReturnsDeepStubs from mockito-scala. Here is a working example

```<code>import org.mockito.stubbing.ReturnsDeepStubs
import org.mockito.{ArgumentMatchersSugar, IdiomaticMockito}
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

trait Bar {
  def zar: Int
}
trait Foo {
  def bar: Bar
}

class MockitoScalaDeepStubExampleSpec extends AnyFlatSpec with Matchers with IdiomaticMockito with ArgumentMatchersSugar {
  "mockito-scala" should "provide deep stubs" in {
    val foo = mock[Foo](ReturnsDeepStubs)
    foo.bar.zar returns 42
    foo.bar.zar should be (42)
  }
}
</code>```

Title: Deep stubbing in Scala

Body:
You can use ReturnsDeepStubs from mockito-scala. Here is a working example

import org.mockito.stubbing.ReturnsDeepStubs
import org.mockito.{ArgumentMatchersSugar, IdiomaticMockito}
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

trait Bar {
  def zar: Int
}
trait Foo {
  def bar: Bar
}

class MockitoScalaDeepStubExampleSpec extends AnyFlatSpec with Matchers with IdiomaticMockito with ArgumentMatchersSugar {
  "mockito-scala" should "provide deep stubs" in {
    val foo = mock[Foo](ReturnsDeepStubs)
    foo.bar.zar returns 42
    foo.bar.zar should be (42)
  }
}


问:
How do I use Microsoft Graph OAuth code flow to produce a token that can read mail?
Steps to recreate require:

A web browser

A command console with CURL installed.

An existing Microsoft Azure account.

An existing Azure App registration (see https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade).

Step 1: Replace "MY_CLIENT_ID" with an Azure client ID for an authorized application.

https://login.microsoftonline.com/common/oauth2/authorize?client_id=MY_CLIENT_ID&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A12345&response_mode=query&resource=https%3A%2F%2Fgraph.microsoft.com%2F&state=12345&prompt=admin_consent&scope=https%3A%2F%2Fgraph.microsoft.com%2Fmail.read

Step 2: Enter the URL in a web browser address bar.

Step 3: Proceed to authorize the application to read email using an existing Azure account.  (The browser URL will change to one with an OAuth code.)

Step 4: Copy the OAuth code.

Step 5: In the CURL command below:

Replace "MY_CLIENT_ID" with an Azure client ID for an authorized application.

Replace "MY_CLIENT_SECRET" with the client secret for the authorized application.

Replace "MY_OAUTH_CODE" with the OAuth code.

curl -X POST -H "content-type: application/x-www-form-urlencoded" -d "grant_type=authorization_code&code=MY_OAUTH_CODE&redirect_uri=http%3A%2F%2Flocalhost%3A12345&client_id=MY_CLIENT_ID&scope=https%3A%2F%2Fgraph.microsoft.com%2Fmail.read&client_secret=MY_CLIENT_SECRET" "https://login.microsoftonline.com/common/oauth2/token"

Step 6: Enter the CURL command in a command console.  (A valid request token will be returned.)

Step 7: Replace "MY_REQUEST_TOKEN" in the CURL command below with the request token and execute the command in a command console.

```<code>curl -H "Authorization: Bearer MY_REQUEST_TOKEN" "https://graph.microsoft.com/v1.0/me/"
</code>```

Notice that the basic account info is returned - meaning the token is VALID.

Step 8: Replace "MY_REQUEST_TOKEN" in the CURL command below with the request token and execute the command in a command console.

```<code>curl -H "Authorization: Bearer MY_REQUEST_TOKEN" "https://graph.microsoft.com/v1.0/me/messages"
</code>```

This error is returned:

```<code>{
  "error": {
    "code": "ResourceNotFound",
    "message": "Resource could not be discovered.",
  }
}
</code>```

答:
This morning I was able to access the email messages of a MS Outlook account using the URIs recommended by the Microsoft OAuth code flow document here:

https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow

It appears that one or more parameters I’d been using in my testing last week was preventing email access.  (Or a required parameter was absent.)

Step: Paste Code Request URI into browser navigation bar:

https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=MY_CLIENT_ID&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A12345&response_mode=query&scope=openid%20offline_access%20https%3A%2F%2Fgraph.microsoft.com%2Fmail.read&state=12345

Step: Authorize in the Browser prompt.

Step: Copy "code" from browser navigation bar into this CURL command:

curl -X POST -H "content-type: application/x-www-form-urlencoded" -d "client_id=MY_CLIENT_ID&scope=https%3A%2F%2Fgraph.microsoft.com%2Fmail.read&code=MY_CODE&redirect_uri=http%3A%2F%2Flocalhost%3A12345&grant_type=authorization_code" "https://login.microsoftonline.com/common/oauth2/v2.0/token"

Step: Copy the access token from the previous CURL command into this CURL command:

curl -H "Authorization: Bearer MY_TOKEN" "https://graph.microsoft.com/v1.0/me/messages"

Body:
Steps to recreate require:

A web browser

A command console with CURL installed.

An existing Microsoft Azure account.

An existing Azure App registration (see https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade).

Step 1: Replace "MY_CLIENT_ID" with an Azure client ID for an authorized application.

https://login.microsoftonline.com/common/oauth2/authorize?client_id=MY_CLIENT_ID&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A12345&response_mode=query&resource=https%3A%2F%2Fgraph.microsoft.com%2F&state=12345&prompt=admin_consent&scope=https%3A%2F%2Fgraph.microsoft.com%2Fmail.read

Step 2: Enter the URL in a web browser address bar.

Step 3: Proceed to authorize the application to read email using an existing Azure account.  (The browser URL will change to one with an OAuth code.)

Step 4: Copy the OAuth code.

Step 5: In the CURL command below:

Replace "MY_CLIENT_ID" with an Azure client ID for an authorized application.

Replace "MY_CLIENT_SECRET" with the client secret for the authorized application.

Replace "MY_OAUTH_CODE" with the OAuth code.

curl -X POST -H "content-type: application/x-www-form-urlencoded" -d "grant_type=authorization_code&code=MY_OAUTH_CODE&redirect_uri=http%3A%2F%2Flocalhost%3A12345&client_id=MY_CLIENT_ID&scope=https%3A%2F%2Fgraph.microsoft.com%2Fmail.read&client_secret=MY_CLIENT_SECRET" "https://login.microsoftonline.com/common/oauth2/token"

Step 6: Enter the CURL command in a command console.  (A valid request token will be returned.)

Step 7: Replace "MY_REQUEST_TOKEN" in the CURL command below with the request token and execute the command in a command console.

curl -H "Authorization: Bearer MY_REQUEST_TOKEN" "https://graph.microsoft.com/v1.0/me/"

Notice that the basic account info is returned - meaning the token is VALID.

Step 8: Replace "MY_REQUEST_TOKEN" in the CURL command below with the request token and execute the command in a command console.

curl -H "Authorization: Bearer MY_REQUEST_TOKEN" "https://graph.microsoft.com/v1.0/me/messages"

This error is returned:

{
  "error": {
    "code": "ResourceNotFound",
    "message": "Resource could not be discovered.",
  }
}


问:
How to do filter(in=...) using a different list for every object I do filter on in Django?
I've written an implementation that explains what I mean. Is there a better way to do what I did?

class Rstq(models.Model):
    pass

class Baz(models.Model):
    rstq_set = models.ManyToManyField(Rstq, blank=True)

class Foo(models.Model):
    # bar_set points to Bar because of many-to-many relationship

    def get_bars(self):
        bars = []
        for bar in self.bar_set:
            if bar.rstq not in bar.baz.rstq_set:
                bars.append(bar)
        return bars

class Bar(models.Model):
    baz = models.ForeignKey(Baz, on_delete=models.CASCADE)
    rstq = models.OneToOneField(Rstq, on_delete=models.CASCADE)
    foo_set = models.ManyToManyField(Foo)
```

答:
Yes, you can make use of an F object [Django-doc] to refer to a field, so:

```<code>from django.db.models import <b>F</b>

class Foo(models.Model):

    # …

    def get_bars(self):
        return self.bar_set.filter(<b>baz__rstq_set=F('rstq')</b>).distinct()</code>```

This yields a query that looks like:

```<code>SELECT DISTINCT <i>app_name</i>_bar.*
FROM <i>app_name</i>_bar
INNER JOIN <i>app_name</i>_baz ON <i>app_name</i>_bar.baz_id = <i>app_name</i>_baz.id
INNER JOIN <i>app_name</i>_baz_rstq_set ON <i>app_name</i>_baz.id = <i>app_name</i>_baz_rstq_set.baz_id
WHERE <i>app_name</i>_baz_rstq_set.rstq_id = <i>app_name</i>_bar.rstq_id</code>```

Body:
Yes, you can make use of an F object [Django-doc] to refer to a field, so:

from django.db.models import F

class Foo(models.Model):

    # …

    def get_bars(self):
        return self.bar_set.filter(baz__rstq_set=F('rstq')).distinct()

This yields a query that looks like:

SELECT DISTINCT app_name_bar.*
FROM app_name_bar
INNER JOIN app_name_baz ON app_name_bar.baz_id = app_name_baz.id
INNER JOIN app_name_baz_rstq_set ON app_name_baz.id = app_name_baz_rstq_set.baz_id
WHERE app_name_baz_rstq_set.rstq_id = app_name_bar.rstq_id


问:
How to parse a string with multiple underscores and dashes
I need some guidance and looking for a better way to parse strings.

Example 1:

String: Automation_LOY_Loyalty_PC_CampaignName3-Abandoners-Email1_NoPromo_USA

CSV: Automation, LOY, Loyalty, PC, CampaignName3, Abandoners, Email1, NoPromo, USA

Example 2:

String: 20200601_LOY_Functional_PC_CampaignName1_NoPromo_CAN

CSV: 20200601, LOY, Functional, PC, CampaignName1,,, No Promo, CAN

As you can see, some strings don’t have all the fields, so some csv fields need to be left blank blank.

Right now I’m using this code below and it’s very messy. Is there a better way to handle it other than using CHARINDEX like this?

SELECT [EmailName]
      -- Deployment Type
      ,CASE LEFT([EmailName], (CHARINDEX('_', [EmailName])) - 1)
        WHEN 'Automation' THEN 'Automation'
        ELSE 'AdHoc' END AS [Deployment]
      -- Type
      ,SUBSTRING([EmailName], (CHARINDEX('_', [EmailName], (CHARINDEX('_', [EmailName])) + 1)) + 1, CHARINDEX('_', [EmailName], CHARINDEX('_', [EmailName], (CHARINDEX('_', [EmailName])) + 1) + 1) - CHARINDEX('_', [EmailName], (CHARINDEX('_', [EmailName])) + 1) - 1) AS [Type]
      -- Customer_Type
      ,SUBSTRING([EmailName], CHARINDEX('_', [EmailName], CHARINDEX('_', [EmailName], (CHARINDEX('_', [EmailName])) + 1) + 1) + 1, CHARINDEX('_', [EmailName],CHARINDEX('_', [EmailName], CHARINDEX('_', [EmailName], (CHARINDEX('_', [EmailName], (CHARINDEX('_', [EmailName])) + 1)) + 1) + 1)) - CHARINDEX('_', [EmailName], (CHARINDEX('_', [EmailName], (CHARINDEX('_', [EmailName])) + 1)) + 1) - 1) AS [Customer_Type]
      -- Campaign_Name
      ,CASE WHEN (CHARINDEX('-', [EmailName])) = 0 THEN SUBSTRING([EmailName], (CHARINDEX('_', [EmailName], CHARINDEX('_', [EmailName], CHARINDEX('_', [EmailName], (CHARINDEX('_', [EmailName], (CHARINDEX('_', [EmailName])) + 1)) + 1) + 1))) + 1, (CHARINDEX('_', [EmailName], CHARINDEX('_', [EmailName], CHARINDEX('_', [EmailName], CHARINDEX('_', [EmailName], CHARINDEX('_', [EmailName], (CHARINDEX('_', [EmailName], (CHARINDEX('_', [EmailName])) + 1)) + 1) + 1)) + 1))) - (CHARINDEX('_', [EmailName], CHARINDEX('_', [EmailName], CHARINDEX('_', [EmailName], (CHARINDEX('_', [EmailName], (CHARINDEX('_', [EmailName])) + 1)) + 1) + 1))) - 1)
            WHEN (CHARINDEX('-', [EmailName])) > 0 THEN SUBSTRING([EmailName], (CHARINDEX('_', [EmailName], CHARINDEX('_', [EmailName], CHARINDEX('_', [EmailName], (CHARINDEX('_', [EmailName], (CHARINDEX('_', [EmailName])) + 1)) + 1) + 1))) + 1, ((CHARINDEX('-', [EmailName])) - (CHARINDEX('_', [EmailName], CHARINDEX('_', [EmailName], CHARINDEX('_', [EmailName], (CHARINDEX('_', [EmailName], (CHARINDEX('_', [EmailName])) + 1)) + 1) + 1)))) - 1)
            ELSE NULL END AS [Campaign_Name]
      ,CASE WHEN (CHARINDEX('-', [EmailName])) = 0 THEN 1
            WHEN (CHARINDEX('-', [EmailName])) > 0 THEN REPLACE(SUBSTRING([EmailName], ((CHARINDEX('-', [EmailName])) + 1), (CHARINDEX('_', [EmailName], CHARINDEX('_', [EmailName], CHARINDEX('_', [EmailName], CHARINDEX('_', [EmailName], CHARINDEX('_', [EmailName], (CHARINDEX('_', [EmailName], (CHARINDEX('_', [EmailName])) + 1)) + 1) + 1)) + 1))) - (CHARINDEX('-', [EmailName])) - 1),'Email','')
            ELSE NULL END AS [Email_Num]
FROM TableName
```

答:
I will not fully answer the question but give you some clues anyway. In SQL Server you can use the STRING_SPLIT function to split a string. But it accepts only one delimiter. Since it appears that you want to split on hyphen (-) and underscore (_) what you could do is:

replace all occurrences of hyphen to underscore

then split the string

Example:

```<code>declare @str varchar(500)
set @str = 'Automation_LOY_Loyalty_PC_CampaignName3-Abandoners-Email1_NoPromo_USA'
set @str = REPLACE(@str, '-', '_')

SELECT
    value
FROM
    STRING_SPLIT(@str,'_')
</code>```

Result (9 rows):

```
Automation
LOY
Loyalty
PC
CampaignName3
Abandoners
Email1
NoPromo
USA
```

The problem is with the second string: 20200601_LOY_Functional_PC_CampaignName1_NoPromo_CAN.

Result (7 rows):

```
20200601
LOY
Functional
PC
CampaignName1
NoPromo
CAN
```

There is no easy way to tell which fields are missing. If there was one 'placeholder' underscore per blank field we could count them but that is not the case here. Maybe you can devise a rule. But I am not sure SQL is the answer. Perhaps you should try a scripting language like Powershell to split the strings, look at the patterns and figure out which fields are present/missing.

Body:
I will not fully answer the question but give you some clues anyway. In SQL Server you can use the STRING_SPLIT function to split a string. But it accepts only one delimiter. Since it appears that you want to split on hyphen (-) and underscore (_) what you could do is:

replace all occurrences of hyphen to underscore

then split the string

Example:

declare @str varchar(500)
set @str = 'Automation_LOY_Loyalty_PC_CampaignName3-Abandoners-Email1_NoPromo_USA'
set @str = REPLACE(@str, '-', '_')

SELECT
    value
FROM
    STRING_SPLIT(@str,'_')

Result (9 rows):

Automation
LOY
Loyalty
PC
CampaignName3
Abandoners
Email1
NoPromo
USA

The problem is with the second string: 20200601_LOY_Functional_PC_CampaignName1_NoPromo_CAN.

Result (7 rows):

20200601
LOY
Functional
PC
CampaignName1
NoPromo
CAN

There is no easy way to tell which fields are missing. If there was one 'placeholder' underscore per blank field we could count them but that is not the case here. Maybe you can devise a rule. But I am not sure SQL is the answer. Perhaps you should try a scripting language like Powershell to split the strings, look at the patterns and figure out which fields are present/missing.


问:
Ruby - convert string to date
I have a string like "2011-06-02T23:59:59+05:30".

I want to convert it to date format and need to parse only the date, "2011-06-02".

Title: Ruby - convert string to date

Body:
require 'date'
d = Date.parse("2011-06-02T23:59:59+05:30")
d.strftime("%F")


问:
How to show Ajax results in Descending order?
Hi I have an ajax code which is able to fetch data from the sql table in ascending order.

This is the main.php

```<code><?php
$connect = mysqli_connect("localhost", "admin", "", "main");
 $sql = "SELECT * FROM names LIMIT 8";
 $result = mysqli_query($connect, $sql);
 $id = '';
?>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
 <div class="table-responsive">
    <table class="table table-bordered" id="load_data_table">
        <?php
          while($row = mysqli_fetch_array($result))
            {
            ?>
                <tr>
                 <td><?php echo $row["id"]; ?></td>
                 <td><?php echo $row["name"]; ?></td>
                </tr>
          <?php
          $id = $row["id"];
          }
          ?>
          <tr id="remove_row">
          td><button type="button" name="btn_more" data-id="<?php echo $id; ?>" id="btn_more" class="btn btn-success form-control">more</button></td>
          </tr>
          </table>
          </div>
 <script>
 $(document).ready(function(){
      $(document).on('click', '#btn_more', function(){
           var last_id = $(this).data("id");
           $('#btn_more').html("Loading...");
           $.ajax({
                url:"resources/views/frontend/ajax/one.php",
                method:"POST",
                data:{last_id:last_id},
                dataType:"text",
                success:function(data)
                {
                     if(data != '')
                     {
                          $('#remove_row').remove();
                          $('#load_data_table').append(data);
                     }
                     else
                     {
                          $('#btn_more').html("No Data");
                     }
                }
           });
      });
 });
 </script>
</code>```

This is the one.php

```<code><?php
$output = '';
$id = '';
sleep(1);
$connect = mysqli_connect("localhost", "admin", "", "main");
$sql = "SELECT * FROM names WHERE id > ".$_POST['last_id']." LIMIT 8";
$result = mysqli_query($connect, $sql);
if(mysqli_num_rows($result) > 0)
{
     while($row = mysqli_fetch_array($result))
     {
          $id = $row["id"];
          $output .= '
               <tbody>
               <tr>
                    <td>'.$row["id"].'</td>
                    <td>'.$row["name"].'</td>
               </tr></tbody>';
     }
     $output .= '
               <tbody><tr id="remove_row">
                    <td><button type="button" name="btn_more" data-id="'. $id .'" id="btn_more" class="btn btn-success form-control">more</button></td>
               </tr></tbody>
     ';
     echo $output;
}
?>
</code>```

My sql table has two columns id and name.

This code works perfectly for ascending order i.e. if the values of "id" is increasing (1,2,3...)

I have already tried the ORDER BY clause in the SQL statement but no luck.

I would like to show the data in descending order i.e. the highest "id" value first.

Please help.

答:
You should use the Order By clause. That helps sorting your results.

Modify your query like this:

```<code>$sql = "SELECT * FROM names ORDER BY id DESC LIMIT 8";
</code>```

You really should consider learning PDO as your second query may get SQL injection.

Body:
Hi I have an ajax code which is able to fetch data from the sql table in ascending order.

This is the main.php

<?php
$connect = mysqli_connect("localhost", "admin", "", "main");
 $sql = "SELECT * FROM names LIMIT 8";
 $result = mysqli_query($connect, $sql);
 $id = '';
?>

        <?php
          while($row = mysqli_fetch_array($result))
            {
            ?>

                 <?php echo $row["id"]; ?>
                 <?php echo $row["name"]; ?>

          <?php
          $id = $row["id"];
          }
          ?>

          td><button type="button" name="btn_more" data-id="<?php echo $id; ?>" id="btn_more" class="btn btn-success form-control">more</button>

This is the one.php

<?php
$output = '';
$id = '';
sleep(1);
$connect = mysqli_connect("localhost", "admin", "", "main");
$sql = "SELECT * FROM names WHERE id > ".$_POST['last_id']." LIMIT 8";
$result = mysqli_query($connect, $sql);
if(mysqli_num_rows($result) > 0)
{
     while($row = mysqli_fetch_array($result))
     {
          $id = $row["id"];
          $output .= '

                    '.$row["id"].'
                    '.$row["name"].'

';
     }
     $output .= '

                    <button type="button" name="btn_more" data-id="'. $id .'" id="btn_more" class="btn btn-success form-control">more</button>

     ';
     echo $output;
}
?>

My sql table has two columns id and name.

This code works perfectly for ascending order i.e. if the values of "id" is increasing (1,2,3...)

I have already tried the ORDER BY clause in the SQL statement but no luck.

I would like to show the data in descending order i.e. the highest "id" value first.

Please help.

Body:
You should use the Order By clause. That helps sorting your results.

Modify your query like this:

$sql = "SELECT * FROM names ORDER BY id DESC LIMIT 8";

You really should consider learning PDO as your second query may get SQL injection.


问:
In F#, how is an interface cast within a lambda expression?
I'm finding the concept of interfaces in F# to be significantly different then C#.

```<code>The following code in F# works correctly:

type MyVisit = {
        lastname : string
        firstname : string
        birthdate : DateTime
        appointment_time : DateTime
        service_time : DateTime
        posting_time : DateTime
        chart_number : int
    }

module FsNetwork =
    let context = Wsdl1.GetBasicHttpBinding_IMedicalService()
    let GetScheduleAsync (tableDate : DateTime) =
        async {
            let! data = context.GetOfficeScheduleAsync(tableDate) |> Async.AwaitTask
            return data |> Seq.map(fun q -> {
                MyVisit.lastname = q.lastname
                firstname = q.firstname
                birthdate = q.birthdate
                appointmentTime = q.appointment_time
                tservice = q.service_time
                posting_time = q.posting_time
                chart_number = q.chart_number
                })
        }
       |> Async.StartAsTask
</code>```

However, when implementing the interface of:

```<code>type IVisit =
    abstract member lastname: string with get
    abstract member firstname: string with get
    abstract member mi: string with get
    abstract member birthdate: Nullable<DateTime> with get
    abstract member appointmentTime : System.Nullable<DateTime> with get
    abstract member tservice : System.Nullable<DateTime> with get
    abstract member postingTime : System.Nullable<DateTime> with get
    abstract member chartNumber : System.Nullable<int> with get

type Visit =
    interface IVisit with
        member this.lastname = "lastname"
        member this.firstname = "FirstName"
        member this.mi = "mi"
        member this.birthdate = Nullable(DateTime(2020, 1, 1))
        member this.appointmentTime = Nullable(DateTime(2020, 1, 1))
        member this.tservice = Nullable(DateTime(2020, 1, 1))
        member this.postingTime = Nullable(DateTime(2020, 1, 1))
        member this.chartNumber = Nullable(0)
</code>```

The lambda now fails with
The type 'Visit' does not define the field, constructor or member 'lastname'

```<code>module FsNetwork =
    let context = Wsdl1.GetBasicHttpBinding_IMedicalService()
    let GetScheduleAsync (tableDate : DateTime) =
        async {
            let! data = context.GetOfficeScheduleAsync(tableDate) |> Async.AwaitTask
            return data |> Seq.map(fun q -> {
                Visit.lastname = q.lastname
                firstname = q.firstname
                birthdate = q.birthdate
                appointmentTime = q.appointment_time
                tservice = q.service_time
                posting_time = q.posting_time
                chart_number = q.chart_number
                })
        }
       |> Async.StartAsTask
</code>```

Attempting to cast 'Visit' with Visit:>IVisit.lastname =..., or Visit:#IVisit.lastname =... also fail. I'm sure its simple, how is this done?

Thanks for any help.

答:
The syntax you are using in your second code snippet is a snippet for record initialization. However, you want to create an instance of an interface instead.

You could do this by creating a class - but your class definition is not quite that yet, because it does not have a constructor. So, you would need something along the lines:

```<code>type Visit(first:string, last:string) =
  interface IVisit with
    member x.first = first
    member x.last = last
</code>```

This gets quite cumbersome, so I would instead use another F# feature, anonymous object expression, which lets you create an instance of an interface without a class:

```<code> { new IVisit with
    member x.first = "Yadda"
    member x.last = "Yadda" }
</code>```

Using this, your code would look something like:

```<code>let context = Wsdl1.GetBasicHttpBinding_IMedicalService()
let GetScheduleAsync (tableDate : DateTime) =
    async {
        let data = context.GetOfficeScheduleAsync(tableDate) |> Async.AwaitTask
        return data |> Seq.map(fun q ->
          { new IVisit with
            member this.mi = "??"
            member this.lastname = q.lastname
            member this.firstname = q.firstname
            member this.birthdate = q.birthdate
            member this.appointmentTime = q.appointment_time
            member this.tservice = q.service_time
            member this.postingTime = q.posting_time
            member this.chartNumber = q.chart_number })
    }
   |> Async.StartAsTask
</code>```

That said, I don't really see why you need an interface here, rather than ordinary F# record. Even if you are going to be using this library from C#, a record will look just like a normal class.

Body:
I'm finding the concept of interfaces in F# to be significantly different then C#.

The following code in F# works correctly:

type MyVisit = {
        lastname : string
        firstname : string
        birthdate : DateTime
        appointment_time : DateTime
        service_time : DateTime
        posting_time : DateTime
        chart_number : int
    }

module FsNetwork =
    let context = Wsdl1.GetBasicHttpBinding_IMedicalService()
    let GetScheduleAsync (tableDate : DateTime) =
        async {
            let! data = context.GetOfficeScheduleAsync(tableDate) |> Async.AwaitTask
            return data |> Seq.map(fun q -> {
                MyVisit.lastname = q.lastname
                firstname = q.firstname
                birthdate = q.birthdate
                appointmentTime = q.appointment_time
                tservice = q.service_time
                posting_time = q.posting_time
                chart_number = q.chart_number
                })
        }
       |> Async.StartAsTask

However, when implementing the interface of:

type IVisit =
    abstract member lastname: string with get
    abstract member firstname: string with get
    abstract member mi: string with get
    abstract member birthdate: Nullable<DateTime> with get
    abstract member appointmentTime : System.Nullable<DateTime> with get
    abstract member tservice : System.Nullable<DateTime> with get
    abstract member postingTime : System.Nullable<DateTime> with get
    abstract member chartNumber : System.Nullable<int> with get

type Visit =
    interface IVisit with
        member this.lastname = "lastname"
        member this.firstname = "FirstName"
        member this.mi = "mi"
        member this.birthdate = Nullable(DateTime(2020, 1, 1))
        member this.appointmentTime = Nullable(DateTime(2020, 1, 1))
        member this.tservice = Nullable(DateTime(2020, 1, 1))
        member this.postingTime = Nullable(DateTime(2020, 1, 1))
        member this.chartNumber = Nullable(0)

The lambda now fails with
The type 'Visit' does not define the field, constructor or member 'lastname'

module FsNetwork =
    let context = Wsdl1.GetBasicHttpBinding_IMedicalService()
    let GetScheduleAsync (tableDate : DateTime) =
        async {
            let! data = context.GetOfficeScheduleAsync(tableDate) |> Async.AwaitTask
            return data |> Seq.map(fun q -> {
                Visit.lastname = q.lastname
                firstname = q.firstname
                birthdate = q.birthdate
                appointmentTime = q.appointment_time
                tservice = q.service_time
                posting_time = q.posting_time
                chart_number = q.chart_number
                })
        }
       |> Async.StartAsTask

Attempting to cast 'Visit' with Visit:>IVisit.lastname =..., or Visit:#IVisit.lastname =... also fail. I'm sure its simple, how is this done?

Thanks for any help.

Body:
The syntax you are using in your second code snippet is a snippet for record initialization. However, you want to create an instance of an interface instead.

You could do this by creating a class - but your class definition is not quite that yet, because it does not have a constructor. So, you would need something along the lines:

type Visit(first:string, last:string) =
  interface IVisit with
    member x.first = first
    member x.last = last

This gets quite cumbersome, so I would instead use another F# feature, anonymous object expression, which lets you create an instance of an interface without a class:

 { new IVisit with
    member x.first = "Yadda"
    member x.last = "Yadda" }

Using this, your code would look something like:

let context = Wsdl1.GetBasicHttpBinding_IMedicalService()
let GetScheduleAsync (tableDate : DateTime) =
    async {
        let data = context.GetOfficeScheduleAsync(tableDate) |> Async.AwaitTask
        return data |> Seq.map(fun q ->
          { new IVisit with
            member this.mi = "??"
            member this.lastname = q.lastname
            member this.firstname = q.firstname
            member this.birthdate = q.birthdate
            member this.appointmentTime = q.appointment_time
            member this.tservice = q.service_time
            member this.postingTime = q.posting_time
            member this.chartNumber = q.chart_number })
    }
   |> Async.StartAsTask

That said, I don't really see why you need an interface here, rather than ordinary F# record. Even if you are going to be using this library from C#, a record will look just like a normal class.