使用 PHP OpenSSL 使用 JAVA 密码解密 aes-128-gcm 编码内容
Decrypt aes-128-gcm encoded content with JAVA Cipher using PHP OpenSSL
我必须使用用 JAVA 密码加密的 aes-128-gcm 解密一些发送到我网站的数据。
客户有一个与 Talend 合作的提供商,他必须通过 URL 参数向我发送一些信息,而我一直在尝试使用 PHP Openssl 解密数据。提供者并不能真正告诉我它是如何在他这边工作的,并且不太确定生成代码中使用的变量(例如 IV Length)。
加密方法是在 JAVA 上使用 Talend 组件完成的。提供商将 Talend 生成的代码作为示例发给我。
private static String encrypt(String data, String mainKey, int ivLength) throws Exception {
final byte[] dataBytes = data.getBytes(UNICODE_FORMAT);
byte[] initializationVector = generateInitializationVector(ivLength);
final Cipher cipher = getAesGcmCipher(Cipher.ENCRYPT_MODE, mainKey, initializationVector);
final byte[] encryptedData = cipher.doFinal(dataBytes);
final byte[] encryptedBytes = new byte[encryptedData.length + ivLength];
System.arraycopy(initializationVector, 0, encryptedBytes, 0, ivLength);
System.arraycopy(encryptedData, 0, encryptedBytes, ivLength, encryptedData.length);
return BASE64_ENCODER.apply(encryptedBytes);
}
private static String decrypt(String data, String mainKey, int ivLength) throws Exception {
final byte[] encryptedBytes = BASE64_DECODER.apply(data.getBytes(UNICODE_FORMAT));
final byte[] initializationVector = new byte[ivLength];
System.arraycopy(encryptedBytes, 0, initializationVector, 0, ivLength);
final Cipher cipher = getAesGcmCipher(Cipher.DECRYPT_MODE, mainKey, initializationVector);
return new String(cipher.doFinal(encryptedBytes, ivLength, encryptedBytes.length - ivLength),
UNICODE_FORMAT);
}
他还给我发送了一些用他正在使用的 class 初始化的常量:
static final String ALGO = "AES"; //$NON-NLS-1$
static final String GCMALGO = "AES/GCM/NoPadding"; //$NON-NLS-1$
static final String UNICODE_FORMAT = "UTF8"; //$NON-NLS-1$
static final String DES_ENCRYPTION_SCHEME = "DES"; //$NON-NLS-1$
private static final int DEFAULT_IV_LENGTH = 16;
public static final String NULL_PARAMETER_MESSAGE = "The parameter should not be null"; //$NON-NLS-1$
public static final String EMPTY_PARAMETER_MESSAGE = "String is empty"; //$NON-NLS-1$
private static final String KEY_GEN_ALGO = "PBKDF2WithHmacSHA256"; //$NON-NLS-1$
static final Random random = new SecureRandom();
static final BASE64Encoder b64Encoder = new DataMasking().new BASE64Encoder();
static final BASE64Decoder b64Dencoder = new DataMasking().new BASE64Decoder();
public static final Function<byte[], String> BASE64_ENCODER = bytes -> Base64.getEncoder().encodeToString(bytes);
public static final Function<byte[], byte[]> BASE64_DECODER = bytes -> Base64.getDecoder().decode(bytes);
据我了解,初始化向量位于字符串的开头,并且我了解到 Java 密码对象会自动将标签放在末尾。
我尝试在我的 PHP 端使用 openssl_encrypt() 自己加密和解密一些内容
和 openssl_decrypt(),它工作正常,但我无法解密 JAVA 应用程序发送给我的数据。
我想知道这是否与我必须对二进制数据进行的字符串操作有关,或者提供商没有给我所需的所有必要信息。
我也不知道 Tag_length 我必须使用什么来解密数据。
提供商还告诉我 IV_Length 是 16 个字符长,但是当我使用 openssl_cipher_iv_length('aes-128-gcm') 时,它建议 12.
这是我现在 PHP 端的代码:
/**
* @param string $str
* The URL parameter string
*/
function test_decrypt($str) {
$key = 'MySuperPassword7';
$cipher = 'aes-128-gcm';
$iv_len = 16;
$tag_length = 16;
echo $str . '<br>';
/**
* Encryption test
*/
// $tag = "";
// $iv = openssl_random_pseudo_bytes($iv_len);
// $enc_str = openssl_encrypt('Test of data to send', $cipher, $key, OPENSSL_RAW_DATA, $iv, $tag, "", $tag_length);
// $encrypt = base64_encode($iv.$enc_str.$tag);
// echo $key . '::' . $iv . '::' . $tag . '<br>';
// echo '$enc_str<pre>';
// var_dump($enc_str);
// echo '</pre>';
// echo '$encrypt<pre>';
// var_dump($encrypt);
// echo '</pre>';
/**
* Decryption part
*/
$encrypt = base64_decode($str);
$iv = substr($encrypt, 0, $iv_len);
$tag = substr($encrypt, - $tag_length);
$ciphertext = substr($encrypt, $iv_len, -$tag_length);
$uncrypt = openssl_decrypt($ciphertext, $cipher, $key, 0, $iv, $tag);//OPENSSL_RAW_DATA + OPENSSL_NO_PADDING
echo $iv_len . '::' . $tag_length . '<br>';
echo $key . '::' . $iv . '::' . $tag . '<br>';
echo '$encrypt<pre>';
var_dump($encrypt);
echo '</pre>';
echo '$ciphertext<pre>';
var_dump($ciphertext);
echo '</pre>';
echo '$uncrypt<pre>';
var_dump($uncrypt);
echo '</pre>';
exit;
}
我也尝试使用在线工具来解密 Java 应用程序发送给我的数据,但到目前为止我找不到任何有用的东西。
编辑
正如@JohnConde 在评论中所要求的,我可以分享我目前正在尝试解密的加密字符串和密钥。
我们决定使用与 Michael Fehr 的答案相同的数据进行测试:
- 密钥:'1234567890123456'
- 纯文本:"Secret data for TytooF"
- 加密文本:VODKjhFETSxMcaa7x/LIOYCfmqD1iWSCuxX80reQ1KoFhmU8/A5AlH0Pg/ZoK1eNSdhBpUed
长度与加密后的答案相同(P8kU/Gf27LU9qgmLpA8gpfHmdVuW5cC7XeKVIgmb3rgGzyjG0YHX5P/cFfRRoZpUxxTEKzj8)。
和以前一样,我无法解密数据...
这里是 Talend 组件的完整代码:
// ============================================================================
//
// Copyright (C) 2006-2019 Talend Inc. - www.talend.com
//
// This source code is available under agreement available at
// %InstallDIR%\features\org.talend.rcp.branding.%PRODUCTNAME%\%PRODUCTNAME%license.txt
//
// You should have received a copy of the agreement
// along with this program; if not, write to Talend SA
// 9 rue Pages 92150 Suresnes, France
//
// ============================================================================
package routines;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PushbackInputStream;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;
import java.util.Calendar;
import java.util.Date;
import java.util.Random;
import java.util.function.Function;
import java.util.stream.Stream;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
/**
* created by talend on 2016-04-08 Detailled comment.
*
*/
public class DataMasking {
static final String ALGO = "AES"; //$NON-NLS-1$
static final String GCMALGO = "AES/GCM/NoPadding"; //$NON-NLS-1$
static final String UNICODE_FORMAT = "UTF8"; //$NON-NLS-1$
static final String DES_ENCRYPTION_SCHEME = "DES"; //$NON-NLS-1$
private static final int DEFAULT_IV_LENGTH = 16;
public static final String NULL_PARAMETER_MESSAGE = "The parameter should not be null"; //$NON-NLS-1$
public static final String EMPTY_PARAMETER_MESSAGE = "String is empty"; //$NON-NLS-1$
private static final String KEY_GEN_ALGO = "PBKDF2WithHmacSHA256"; //$NON-NLS-1$
static final Random random = new SecureRandom();
static final BASE64Encoder b64Encoder = new DataMasking().new BASE64Encoder();
static final BASE64Decoder b64Dencoder = new DataMasking().new BASE64Decoder();
public static final Function<byte[], String> BASE64_ENCODER = bytes -> Base64.getEncoder().encodeToString(bytes);
public static final Function<byte[], byte[]> BASE64_DECODER = bytes -> Base64.getDecoder().decode(bytes);
public static class DataMaskingRoutineException extends RuntimeException {
private static final long serialVersionUID = -8622896150657449668L;
public DataMaskingRoutineException() {
super();
}
public DataMaskingRoutineException(String s) {
super(s);
}
public DataMaskingRoutineException(String s, Object o) {
super(s);
System.out.println(o);
}
}
/**
* Encrypt String: Encrypts a string using AES 128 .
* warning: this is not considered a secure function.
*
*
* {talendTypes} String
*
* {Category} Data Masking
*
* {param} String("foo") encryptString: The string to be encrypted.
*
* {param} byte[](new byte[] { 'T', 'a', 'l', 'e', 'n', 'd', 's', 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y' })
* keyValue: the
* key material of the secret key. The contents of the array are copied to protect against subsequent modification.
*
* {example} encryptAES("foo", new byte[] { 'T', 'a', 'l', 'e', 'n', 'd', 's', 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y' })
* result is UQ0VJZq5ymFkMYQeDrPi0A==
*
* @deprecated use {@link #encryptAESGCM(String, String, int)} instead of it
*
*/
@Deprecated
public static String encryptAES(String encryptString, byte[] keyValue) {
if (encryptString == null || keyValue == null) {
return NULL_PARAMETER_MESSAGE;
}
if (encryptString.length() == 0) {
return EMPTY_PARAMETER_MESSAGE;
}
try {
Key key = new SecretKeySpec(keyValue, ALGO);
Cipher cipher = Cipher.getInstance(ALGO);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encVal = cipher.doFinal(encryptString.getBytes());
String encryptedValue = b64Encoder.encode(encVal);
return encryptedValue;
} catch (Exception e) {
throw new DataMaskingRoutineException(e.getMessage(), e);
}
}
/**
* Encrypt String: Encrypts a string using AES GCM 128 .
*
* {talendTypes} String
*
* {Category} Data Masking
*
* {param} String("foo") encryptString: The string to be encrypted.
*
* {param} String("TalendMainKey123") the main key used to encrypt the data.
*
* {example} encryptAESGCM("foo","TalendMainKey123") result could be
* +ngs4RohAx8OvInmioRwRYS0pymcNkQJCKqsdUPZqUZppN4= (but it should change from an execution to another).
*
*/
public static String encryptAESGCM(String encryptString, String mainKey) {
return encryptAESGCM(encryptString, mainKey, DEFAULT_IV_LENGTH);
}
/**
* Encrypt String: Encrypts a string using AES GCM 128 .
*
* {talendTypes} String
*
* {Category} Data Masking
*
* {param} String("foo") encryptString: The string to be encrypted.
*
* {param} String("TalendMainKey123") the main key used to encrypt the data.
*
* {param} int the length of initializationVector. must be one of 12/13/14/15/16.
*
* {example} encryptAESGCM("foo","TalendMainKey123",16) result could be
* +ngs4RohAx8OvInmioRwRYS0pymcNkQJCKqsdUPZqUZppN4= (but it should change from an execution to another).
*
*/
public static String encryptAESGCM(String encryptString, String mainKey, int ivLength) {
if (encryptString == null || mainKey == null) {
return NULL_PARAMETER_MESSAGE;
}
if (encryptString.length() == 0) {
return EMPTY_PARAMETER_MESSAGE;
}
try {
return encrypt(encryptString, mainKey, ivLength);
} catch (Exception e) {
throw new DataMaskingRoutineException(e.getMessage(), e);
}
}
/**
* decrypt String: Decrypts a string using AES 128.
* warning: this is not considered a secure function.
*
*
* {talendTypes} String
*
* {Category} Data Masking
*
* {param} String("UQ0VJZq5ymFkMYQeDrPi0A==") encryptedString: The string to be decrypted.
*
* {param} byte[](new byte[] { 'T', 'a', 'l', 'e', 'n', 'd', 's', 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y' })
* keyValue: the key material of the secret key. The contents of the array are copied to protect against subsequent
* modification.
*
* {example} decryptAES("UQ0VJZq5ymFkMYQeDrPi0A==",new byte[] { 'T', 'a', 'l', 'e', 'n', 'd', 's', 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y' })
* result is "foo"
*
* @deprecated use {@link #decryptAESGCM(String, String, int)} instead of it
*
*/
@Deprecated
public static String decryptAES(String encryptedString, byte[] keyValue) {
if (encryptedString == null || keyValue == null) {
return NULL_PARAMETER_MESSAGE;
}
if (encryptedString.length() == 0) {
return EMPTY_PARAMETER_MESSAGE;
}
try {
Key key = new SecretKeySpec(keyValue, ALGO);
Cipher cipher = Cipher.getInstance(ALGO);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decordedValue = b64Dencoder.decodeBuffer(encryptedString);
byte[] decValue = cipher.doFinal(decordedValue);
String decryptedValue = new String(decValue);
return decryptedValue;
} catch (Exception e) {
throw new DataMaskingRoutineException(e.getMessage(), e);
}
}
/**
* decrypt String: Decrypts a string using AES GCM 128.
*
* {talendTypes} String
*
* {Category} Data Masking
*
* {param} String("+ngs4RohAx8OvInmioRwRYS0pymcNkQJCKqsdUPZqUZppN4=") encryptedString: The string to be decrypted.
*
* {param} String("TalendMainKey123") the main key used to decrypt the data.
*
* {param} int the length of initializationVector. must be one of 12/13/14/15/16.
*
* {example} decryptAESGCM("+ngs4RohAx8OvInmioRwRYS0pymcNkQJCKqsdUPZqUZppN4=","TalendMainKey123",16) result
* is "foo"
*
*/
public static String decryptAESGCM(String encryptedString, String mainKey, int ivLength) {
if (encryptedString == null || mainKey == null) {
return NULL_PARAMETER_MESSAGE;
}
if (encryptedString.length() == 0) {
return EMPTY_PARAMETER_MESSAGE;
}
try {
return decrypt(encryptedString, mainKey, ivLength);
} catch (Exception e) {
throw new DataMaskingRoutineException(e.getMessage(), e);
}
}
/**
* decrypt String: Decrypts a string using AES GCM 128.
*
* {talendTypes} String
*
* {Category} Data Masking
*
* {param} String("+ngs4RohAx8OvInmioRwRYS0pymcNkQJCKqsdUPZqUZppN4=") encryptedString: The string to be decrypted.
*
* {param} String("TalendMainKey123") the main key used to decrypt the data.
*
* {example} decryptAESGCM("+ngs4RohAx8OvInmioRwRYS0pymcNkQJCKqsdUPZqUZppN4=","TalendMainKey123") result
* is "foo"
*
*/
public static String decryptAESGCM(String encryptedString, String mainKey) {
return decryptAESGCM(encryptedString, mainKey, DEFAULT_IV_LENGTH);
}
/**
* Encrypt String: Encrypts a string using DES .
* warning: this is not considered a secure function.
*
*
* {talendTypes} String
*
* {Category} Data Masking
*
* {param} String("foo") unencryptedString: The string to be encrypted.
*
* {param} String("ThisIsSecretEncryptionKey") myEncryptionKey: the string with the DES key material.
*
* {example} encryptDES("foo") result is DmNj+x2LUXA=
*
* @throws Exception
*/
public static String encryptDES(String unencryptedString, String myEncryptionKey) {
if (unencryptedString == null || myEncryptionKey == null) {
return NULL_PARAMETER_MESSAGE;
}
if (unencryptedString.length() == 0) {
return EMPTY_PARAMETER_MESSAGE;
}
try {
String encryptedString = null;
String myEncryptionScheme = DES_ENCRYPTION_SCHEME;
byte[] keyAsBytes = myEncryptionKey.getBytes(UNICODE_FORMAT);
KeySpec myKeySpec = new DESKeySpec(keyAsBytes);
SecretKeyFactory mySecretKeyFactory = SecretKeyFactory.getInstance(myEncryptionScheme);
Cipher encipher = Cipher.getInstance(myEncryptionScheme);
SecretKey key = mySecretKeyFactory.generateSecret(myKeySpec);
encipher.init(Cipher.ENCRYPT_MODE, key);
byte[] plainText = unencryptedString.getBytes(UNICODE_FORMAT);
byte[] encryptedText = encipher.doFinal(plainText);
encryptedString = b64Encoder.encode(encryptedText);
return encryptedString;
} catch (Exception e) {
throw new DataMaskingRoutineException(e.getMessage());
}
}
/**
* Decrypt String: Decrypts a string using DES .
* warning: this is not considered a secure function.
*
*
* {talendTypes} String
*
* {Category} Data Masking
*
* {param} String("DmNj+x2LUXA=") encryptedString: the string with the DES key material.
*
* {param} String("ThisIsSecretEncryptionKey") myDecryptionKey: The string to be encrypted.
*
* {example} decryptDES("DmNj+x2LUXA=") result is "foo"
*
*/
public static String decryptDES(String encryptedString, String myDecryptionKey) {
if (encryptedString == null || myDecryptionKey == null) {
return NULL_PARAMETER_MESSAGE;
}
if (encryptedString.length() == 0) {
return EMPTY_PARAMETER_MESSAGE;
}
try {
String decryptedText = null;
String myDecryptionScheme = DES_ENCRYPTION_SCHEME;
byte[] keyAsBytes = myDecryptionKey.getBytes(UNICODE_FORMAT);
KeySpec myKeySpec = new DESKeySpec(keyAsBytes);
Cipher decipher = Cipher.getInstance(myDecryptionScheme);
SecretKeyFactory mySecretKeyFactory = SecretKeyFactory.getInstance(myDecryptionScheme);
SecretKey key = mySecretKeyFactory.generateSecret(myKeySpec);
decipher.init(Cipher.DECRYPT_MODE, key);
byte[] encryptedText = b64Dencoder.decodeBuffer(encryptedString);
byte[] plainText = decipher.doFinal(encryptedText);
StringBuilder stringBuilder = new StringBuilder();
for (byte element : plainText) {
stringBuilder.append((char) element);
}
decryptedText = stringBuilder.toString();
return decryptedText;
} catch (Exception e) {
throw new DataMaskingRoutineException(e.getMessage(), e);
}
}
/**
* This method generates a secret Key using the key-stretching algorithm PBKDF2 of
* <a href="https://docs.oracle.com/javase/7/docs/api/javax/crypto/package-summary.html">javax.crypto</a>.
* It is basically a hashing algorithm slow by design, in order to increase the time
* required for an attacker to try a lot of passwords in a bruteforce attack.
* <br>
* About the salt :
* <ul>
* <li>The salt is not secret, the use of Random is not critical and ensure determinism.</li>
* <li>The salt is important to avoid rainbow table attacks.</li>
* <li>The salt should be generated with SecureRandom() in case the passwords are stored.</li>
* <li>In that case the salt should be stored in plaintext next to the password and a unique user identifier.</li>
* </ul>
*
* @param password a password given as a {@code String}.
* @param keyLength key length to generate
* @return a {@code SecretKey} securely generated.
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
*/
private static byte[] generateSecretKeyFromPassword(String password, int keyLength)
throws NoSuchAlgorithmException, InvalidKeySpecException {
byte[] salt = new byte[keyLength];
new Random(password.hashCode()).nextBytes(salt);
SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_GEN_ALGO);
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, keyLength << 3);
return factory.generateSecret(spec).getEncoded();
}
private static String encrypt(String data, String mainKey, int ivLength) throws Exception {
final byte[] dataBytes = data.getBytes(UNICODE_FORMAT);
byte[] initializationVector = generateInitializationVector(ivLength);
final Cipher cipher = getAesGcmCipher(Cipher.ENCRYPT_MODE, mainKey, initializationVector);
final byte[] encryptedData = cipher.doFinal(dataBytes);
final byte[] encryptedBytes = new byte[encryptedData.length + ivLength];
System.arraycopy(initializationVector, 0, encryptedBytes, 0, ivLength);
System.arraycopy(encryptedData, 0, encryptedBytes, ivLength, encryptedData.length);
return BASE64_ENCODER.apply(encryptedBytes);
}
private static String decrypt(String data, String mainKey, int ivLength) throws Exception {
final byte[] encryptedBytes = BASE64_DECODER.apply(data.getBytes(UNICODE_FORMAT));
final byte[] initializationVector = new byte[ivLength];
System.arraycopy(encryptedBytes, 0, initializationVector, 0, ivLength);
final Cipher cipher = getAesGcmCipher(Cipher.DECRYPT_MODE, mainKey, initializationVector);
return new String(cipher.doFinal(encryptedBytes, ivLength, encryptedBytes.length - ivLength),
UNICODE_FORMAT);
}
}
** 编辑 2 **
在与提供者调试了一段时间后,我们最终决定使用 Michael Fehr 提供的代码。
我从组件的代码中了解到,区别在于主键的使用方式。但我不是 Java 开发人员,也许我误解了什么。
以防万一它可以帮助某人,这里是额外的 Java IV 生成和密钥生成缺少的代码。
private static Cipher getAesGcmCipher(int encryptMode, String mainKey, byte[] initializationVector)
throws Exception {
int ivLength = initializationVector.length;
if (Stream.of(12, 13, 14, 15, 16).noneMatch(i -> i == ivLength)) {
throw new IllegalArgumentException("Invalid IV length"); //$NON-NLS-1$
}
final Cipher cipher = Cipher.getInstance(GCMALGO);
SecretKey key = new SecretKeySpec(generateSecretKeyFromPassword(mainKey, mainKey.length()), ALGO);
final GCMParameterSpec spec = new GCMParameterSpec(ivLength * 8, initializationVector);
cipher.init(encryptMode, key, spec);
return cipher;
}
private static byte[] generateSecretKeyFromPassword(String password, int keyLength)
throws NoSuchAlgorithmException, InvalidKeySpecException {
byte[] salt = new byte[keyLength];
new Random(password.hashCode()).nextBytes(salt);
SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_GEN_ALGO);
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, keyLength << 3);
return factory.generateSecret(spec).getEncoded();
}
我在 Java 中设置了您的代码,并使用(固定)密钥和随机初始化向量进行了加密:
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Random;
public class SO_Main_Final {
static final String ALGO = "AES"; //$NON-NLS-1$
static final String GCMALGO = "AES/GCM/NoPadding"; //$NON-NLS-1$
static final String UNICODE_FORMAT = "UTF8"; //$NON-NLS-1$
static final Random random = new SecureRandom();
public static void main(String[] args) throws Exception {
System.out.println("
String myData = "Secret data for TytooF";
String myKey = "1234567890123456";
String encryptString = encrypt(myData, myKey, 16);
String decryptString = decrypt(encryptString, myKey, 16);
System.out.println("encryptString: " + encryptString);
System.out.println("decryptString: " + decryptString);
}
private static String encrypt(String data, String mainKey, int ivLength) throws Exception {
final byte[] dataBytes = data.getBytes(UNICODE_FORMAT);
// byte[] initializationVector = generateInitializationVector(ivLength);
byte[] initializationVector = new byte[ivLength];
random.nextBytes(initializationVector);
SecretKeySpec secretKeySpec = new SecretKeySpec(mainKey.getBytes(UNICODE_FORMAT), ALGO);
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, initializationVector);
Cipher cipher = Cipher.getInstance(GCMALGO);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec);
final byte[] encryptedData = cipher.doFinal(dataBytes);
final byte[] encryptedBytes = new byte[encryptedData.length + ivLength];
System.arraycopy(initializationVector, 0, encryptedBytes, 0, ivLength);
System.arraycopy(encryptedData, 0, encryptedBytes, ivLength, encryptedData.length);
System.out.println("data [String] : " + data);
System.out.println("data length: " + dataBytes.length
+ " data: " + bytesToHex(dataBytes));
System.out.println("mainKey length: " + mainKey.getBytes(UNICODE_FORMAT).length
+ " data: " + bytesToHex(mainKey.getBytes(UNICODE_FORMAT)));
System.out.println("initvector length: " + initializationVector.length
+ " data: " + bytesToHex(initializationVector));
System.out.println("encryptedBytes length: " + encryptedBytes.length
+ " data: " + bytesToHex(encryptedBytes));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
private static String decrypt(String data, String mainKey, int ivLength) throws Exception {
final byte[] encryptedBytes = Base64.getDecoder().decode(data.getBytes(UNICODE_FORMAT));
final byte[] initializationVector = new byte[ivLength];
System.arraycopy(encryptedBytes, 0, initializationVector, 0, ivLength);
SecretKeySpec secretKeySpec = new SecretKeySpec(mainKey.getBytes(UNICODE_FORMAT), ALGO);
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, initializationVector);
Cipher cipher = Cipher.getInstance(GCMALGO);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmParameterSpec);
return new String(cipher.doFinal(encryptedBytes, ivLength, encryptedBytes.length - ivLength),
UNICODE_FORMAT);
}
private static String bytesToHex(byte[] bytes) {
StringBuffer result = new StringBuffer();
for (byte b : bytes) result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
return result.toString();
}
}
最后我得到了这个结果——“encryptString”需要转移到您的网站:
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
在 webserver/PHP-side 我稍微修改了你的代码(“主要变化”是行 openssl_decrypt($ciphertext, $cipher, $key, true, $iv, $tag) 因为我们没有将 Base64 编码的数据提供给解密
方法,因为这是在之前完成的一些代码行 ($encrypt = base64_decode($str).
这里是 PHP-代码:
<?php
/**
* @param string $str
* The URL parameter string
*/
function test_decrypt($str)
{
$key = '1234567890123456';
$cipher = 'aes-128-gcm';
$iv_len = 16;
$tag_length = 16;
echo $str . '<br>';
/**
* Decryption part
*/
$encrypt = base64_decode($str);
$iv = substr($encrypt, 0, $iv_len);
$tag = substr($encrypt, -$tag_length);
$ciphertext = substr($encrypt, $iv_len, -$tag_length);
echo "" . "\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 </b><br>' . "\n";
echo '' . "\n";
echo 'Start decryption' . "\n";
$receivedData = "P8kU/Gf27LU9qgmLpA8gpfHmdVuW5cC7XeKVIgmb3rgGzyjG0YHX5P/cFfRRoZpUxxTEKzj8";
test_decrypt($receivedData);
?>
这是网络服务器上的解密输出:
Output for
Start decryption P8kU/Gf27LU9qgmLpA8gpfHmdVuW5cC7XeKVIgmb3rgGzyjG0YHX5P/cFfRRoZpUxxTEKzj8
iv:3fc914fc67f6ecb53daa098ba40f20a5
ciphertext:f1e6755b96e5c0bb5de29522099bdeb806cf28c6d181
tag:d7e4ffdc15f451a19a54c714c42b38fc
DecryptedString: Secret data for TytooF
DecryptedString [byte[]]:536563726574206461746120666f72205479746f6f46
2021 年 10 月 10 日编辑: 上面的代码使用的 IV/nonce 长度为 16 个字节,但 推荐 IV/nonce长度为12.
我必须使用用 JAVA 密码加密的 aes-128-gcm 解密一些发送到我网站的数据。
客户有一个与 Talend 合作的提供商,他必须通过 URL 参数向我发送一些信息,而我一直在尝试使用 PHP Openssl 解密数据。提供者并不能真正告诉我它是如何在他这边工作的,并且不太确定生成代码中使用的变量(例如 IV Length)。
加密方法是在 JAVA 上使用 Talend 组件完成的。提供商将 Talend 生成的代码作为示例发给我。
private static String encrypt(String data, String mainKey, int ivLength) throws Exception {
final byte[] dataBytes = data.getBytes(UNICODE_FORMAT);
byte[] initializationVector = generateInitializationVector(ivLength);
final Cipher cipher = getAesGcmCipher(Cipher.ENCRYPT_MODE, mainKey, initializationVector);
final byte[] encryptedData = cipher.doFinal(dataBytes);
final byte[] encryptedBytes = new byte[encryptedData.length + ivLength];
System.arraycopy(initializationVector, 0, encryptedBytes, 0, ivLength);
System.arraycopy(encryptedData, 0, encryptedBytes, ivLength, encryptedData.length);
return BASE64_ENCODER.apply(encryptedBytes);
}
private static String decrypt(String data, String mainKey, int ivLength) throws Exception {
final byte[] encryptedBytes = BASE64_DECODER.apply(data.getBytes(UNICODE_FORMAT));
final byte[] initializationVector = new byte[ivLength];
System.arraycopy(encryptedBytes, 0, initializationVector, 0, ivLength);
final Cipher cipher = getAesGcmCipher(Cipher.DECRYPT_MODE, mainKey, initializationVector);
return new String(cipher.doFinal(encryptedBytes, ivLength, encryptedBytes.length - ivLength),
UNICODE_FORMAT);
}
他还给我发送了一些用他正在使用的 class 初始化的常量:
static final String ALGO = "AES"; //$NON-NLS-1$
static final String GCMALGO = "AES/GCM/NoPadding"; //$NON-NLS-1$
static final String UNICODE_FORMAT = "UTF8"; //$NON-NLS-1$
static final String DES_ENCRYPTION_SCHEME = "DES"; //$NON-NLS-1$
private static final int DEFAULT_IV_LENGTH = 16;
public static final String NULL_PARAMETER_MESSAGE = "The parameter should not be null"; //$NON-NLS-1$
public static final String EMPTY_PARAMETER_MESSAGE = "String is empty"; //$NON-NLS-1$
private static final String KEY_GEN_ALGO = "PBKDF2WithHmacSHA256"; //$NON-NLS-1$
static final Random random = new SecureRandom();
static final BASE64Encoder b64Encoder = new DataMasking().new BASE64Encoder();
static final BASE64Decoder b64Dencoder = new DataMasking().new BASE64Decoder();
public static final Function<byte[], String> BASE64_ENCODER = bytes -> Base64.getEncoder().encodeToString(bytes);
public static final Function<byte[], byte[]> BASE64_DECODER = bytes -> Base64.getDecoder().decode(bytes);
据我了解,初始化向量位于字符串的开头,并且我了解到 Java 密码对象会自动将标签放在末尾。
我尝试在我的 PHP 端使用 openssl_encrypt() 自己加密和解密一些内容 和 openssl_decrypt(),它工作正常,但我无法解密 JAVA 应用程序发送给我的数据。
我想知道这是否与我必须对二进制数据进行的字符串操作有关,或者提供商没有给我所需的所有必要信息。
我也不知道 Tag_length 我必须使用什么来解密数据。 提供商还告诉我 IV_Length 是 16 个字符长,但是当我使用 openssl_cipher_iv_length('aes-128-gcm') 时,它建议 12.
这是我现在 PHP 端的代码:
/**
* @param string $str
* The URL parameter string
*/
function test_decrypt($str) {
$key = 'MySuperPassword7';
$cipher = 'aes-128-gcm';
$iv_len = 16;
$tag_length = 16;
echo $str . '<br>';
/**
* Encryption test
*/
// $tag = "";
// $iv = openssl_random_pseudo_bytes($iv_len);
// $enc_str = openssl_encrypt('Test of data to send', $cipher, $key, OPENSSL_RAW_DATA, $iv, $tag, "", $tag_length);
// $encrypt = base64_encode($iv.$enc_str.$tag);
// echo $key . '::' . $iv . '::' . $tag . '<br>';
// echo '$enc_str<pre>';
// var_dump($enc_str);
// echo '</pre>';
// echo '$encrypt<pre>';
// var_dump($encrypt);
// echo '</pre>';
/**
* Decryption part
*/
$encrypt = base64_decode($str);
$iv = substr($encrypt, 0, $iv_len);
$tag = substr($encrypt, - $tag_length);
$ciphertext = substr($encrypt, $iv_len, -$tag_length);
$uncrypt = openssl_decrypt($ciphertext, $cipher, $key, 0, $iv, $tag);//OPENSSL_RAW_DATA + OPENSSL_NO_PADDING
echo $iv_len . '::' . $tag_length . '<br>';
echo $key . '::' . $iv . '::' . $tag . '<br>';
echo '$encrypt<pre>';
var_dump($encrypt);
echo '</pre>';
echo '$ciphertext<pre>';
var_dump($ciphertext);
echo '</pre>';
echo '$uncrypt<pre>';
var_dump($uncrypt);
echo '</pre>';
exit;
}
我也尝试使用在线工具来解密 Java 应用程序发送给我的数据,但到目前为止我找不到任何有用的东西。
编辑
正如@JohnConde 在评论中所要求的,我可以分享我目前正在尝试解密的加密字符串和密钥。 我们决定使用与 Michael Fehr 的答案相同的数据进行测试:
- 密钥:'1234567890123456'
- 纯文本:"Secret data for TytooF"
- 加密文本:VODKjhFETSxMcaa7x/LIOYCfmqD1iWSCuxX80reQ1KoFhmU8/A5AlH0Pg/ZoK1eNSdhBpUed
长度与加密后的答案相同(P8kU/Gf27LU9qgmLpA8gpfHmdVuW5cC7XeKVIgmb3rgGzyjG0YHX5P/cFfRRoZpUxxTEKzj8)。
和以前一样,我无法解密数据...
这里是 Talend 组件的完整代码:
// ============================================================================
//
// Copyright (C) 2006-2019 Talend Inc. - www.talend.com
//
// This source code is available under agreement available at
// %InstallDIR%\features\org.talend.rcp.branding.%PRODUCTNAME%\%PRODUCTNAME%license.txt
//
// You should have received a copy of the agreement
// along with this program; if not, write to Talend SA
// 9 rue Pages 92150 Suresnes, France
//
// ============================================================================
package routines;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PushbackInputStream;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;
import java.util.Calendar;
import java.util.Date;
import java.util.Random;
import java.util.function.Function;
import java.util.stream.Stream;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
/**
* created by talend on 2016-04-08 Detailled comment.
*
*/
public class DataMasking {
static final String ALGO = "AES"; //$NON-NLS-1$
static final String GCMALGO = "AES/GCM/NoPadding"; //$NON-NLS-1$
static final String UNICODE_FORMAT = "UTF8"; //$NON-NLS-1$
static final String DES_ENCRYPTION_SCHEME = "DES"; //$NON-NLS-1$
private static final int DEFAULT_IV_LENGTH = 16;
public static final String NULL_PARAMETER_MESSAGE = "The parameter should not be null"; //$NON-NLS-1$
public static final String EMPTY_PARAMETER_MESSAGE = "String is empty"; //$NON-NLS-1$
private static final String KEY_GEN_ALGO = "PBKDF2WithHmacSHA256"; //$NON-NLS-1$
static final Random random = new SecureRandom();
static final BASE64Encoder b64Encoder = new DataMasking().new BASE64Encoder();
static final BASE64Decoder b64Dencoder = new DataMasking().new BASE64Decoder();
public static final Function<byte[], String> BASE64_ENCODER = bytes -> Base64.getEncoder().encodeToString(bytes);
public static final Function<byte[], byte[]> BASE64_DECODER = bytes -> Base64.getDecoder().decode(bytes);
public static class DataMaskingRoutineException extends RuntimeException {
private static final long serialVersionUID = -8622896150657449668L;
public DataMaskingRoutineException() {
super();
}
public DataMaskingRoutineException(String s) {
super(s);
}
public DataMaskingRoutineException(String s, Object o) {
super(s);
System.out.println(o);
}
}
/**
* Encrypt String: Encrypts a string using AES 128 .
* warning: this is not considered a secure function.
*
*
* {talendTypes} String
*
* {Category} Data Masking
*
* {param} String("foo") encryptString: The string to be encrypted.
*
* {param} byte[](new byte[] { 'T', 'a', 'l', 'e', 'n', 'd', 's', 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y' })
* keyValue: the
* key material of the secret key. The contents of the array are copied to protect against subsequent modification.
*
* {example} encryptAES("foo", new byte[] { 'T', 'a', 'l', 'e', 'n', 'd', 's', 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y' })
* result is UQ0VJZq5ymFkMYQeDrPi0A==
*
* @deprecated use {@link #encryptAESGCM(String, String, int)} instead of it
*
*/
@Deprecated
public static String encryptAES(String encryptString, byte[] keyValue) {
if (encryptString == null || keyValue == null) {
return NULL_PARAMETER_MESSAGE;
}
if (encryptString.length() == 0) {
return EMPTY_PARAMETER_MESSAGE;
}
try {
Key key = new SecretKeySpec(keyValue, ALGO);
Cipher cipher = Cipher.getInstance(ALGO);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encVal = cipher.doFinal(encryptString.getBytes());
String encryptedValue = b64Encoder.encode(encVal);
return encryptedValue;
} catch (Exception e) {
throw new DataMaskingRoutineException(e.getMessage(), e);
}
}
/**
* Encrypt String: Encrypts a string using AES GCM 128 .
*
* {talendTypes} String
*
* {Category} Data Masking
*
* {param} String("foo") encryptString: The string to be encrypted.
*
* {param} String("TalendMainKey123") the main key used to encrypt the data.
*
* {example} encryptAESGCM("foo","TalendMainKey123") result could be
* +ngs4RohAx8OvInmioRwRYS0pymcNkQJCKqsdUPZqUZppN4= (but it should change from an execution to another).
*
*/
public static String encryptAESGCM(String encryptString, String mainKey) {
return encryptAESGCM(encryptString, mainKey, DEFAULT_IV_LENGTH);
}
/**
* Encrypt String: Encrypts a string using AES GCM 128 .
*
* {talendTypes} String
*
* {Category} Data Masking
*
* {param} String("foo") encryptString: The string to be encrypted.
*
* {param} String("TalendMainKey123") the main key used to encrypt the data.
*
* {param} int the length of initializationVector. must be one of 12/13/14/15/16.
*
* {example} encryptAESGCM("foo","TalendMainKey123",16) result could be
* +ngs4RohAx8OvInmioRwRYS0pymcNkQJCKqsdUPZqUZppN4= (but it should change from an execution to another).
*
*/
public static String encryptAESGCM(String encryptString, String mainKey, int ivLength) {
if (encryptString == null || mainKey == null) {
return NULL_PARAMETER_MESSAGE;
}
if (encryptString.length() == 0) {
return EMPTY_PARAMETER_MESSAGE;
}
try {
return encrypt(encryptString, mainKey, ivLength);
} catch (Exception e) {
throw new DataMaskingRoutineException(e.getMessage(), e);
}
}
/**
* decrypt String: Decrypts a string using AES 128.
* warning: this is not considered a secure function.
*
*
* {talendTypes} String
*
* {Category} Data Masking
*
* {param} String("UQ0VJZq5ymFkMYQeDrPi0A==") encryptedString: The string to be decrypted.
*
* {param} byte[](new byte[] { 'T', 'a', 'l', 'e', 'n', 'd', 's', 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y' })
* keyValue: the key material of the secret key. The contents of the array are copied to protect against subsequent
* modification.
*
* {example} decryptAES("UQ0VJZq5ymFkMYQeDrPi0A==",new byte[] { 'T', 'a', 'l', 'e', 'n', 'd', 's', 'S', 'e', 'c', 'r', 'e', 't', 'K', 'e', 'y' })
* result is "foo"
*
* @deprecated use {@link #decryptAESGCM(String, String, int)} instead of it
*
*/
@Deprecated
public static String decryptAES(String encryptedString, byte[] keyValue) {
if (encryptedString == null || keyValue == null) {
return NULL_PARAMETER_MESSAGE;
}
if (encryptedString.length() == 0) {
return EMPTY_PARAMETER_MESSAGE;
}
try {
Key key = new SecretKeySpec(keyValue, ALGO);
Cipher cipher = Cipher.getInstance(ALGO);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decordedValue = b64Dencoder.decodeBuffer(encryptedString);
byte[] decValue = cipher.doFinal(decordedValue);
String decryptedValue = new String(decValue);
return decryptedValue;
} catch (Exception e) {
throw new DataMaskingRoutineException(e.getMessage(), e);
}
}
/**
* decrypt String: Decrypts a string using AES GCM 128.
*
* {talendTypes} String
*
* {Category} Data Masking
*
* {param} String("+ngs4RohAx8OvInmioRwRYS0pymcNkQJCKqsdUPZqUZppN4=") encryptedString: The string to be decrypted.
*
* {param} String("TalendMainKey123") the main key used to decrypt the data.
*
* {param} int the length of initializationVector. must be one of 12/13/14/15/16.
*
* {example} decryptAESGCM("+ngs4RohAx8OvInmioRwRYS0pymcNkQJCKqsdUPZqUZppN4=","TalendMainKey123",16) result
* is "foo"
*
*/
public static String decryptAESGCM(String encryptedString, String mainKey, int ivLength) {
if (encryptedString == null || mainKey == null) {
return NULL_PARAMETER_MESSAGE;
}
if (encryptedString.length() == 0) {
return EMPTY_PARAMETER_MESSAGE;
}
try {
return decrypt(encryptedString, mainKey, ivLength);
} catch (Exception e) {
throw new DataMaskingRoutineException(e.getMessage(), e);
}
}
/**
* decrypt String: Decrypts a string using AES GCM 128.
*
* {talendTypes} String
*
* {Category} Data Masking
*
* {param} String("+ngs4RohAx8OvInmioRwRYS0pymcNkQJCKqsdUPZqUZppN4=") encryptedString: The string to be decrypted.
*
* {param} String("TalendMainKey123") the main key used to decrypt the data.
*
* {example} decryptAESGCM("+ngs4RohAx8OvInmioRwRYS0pymcNkQJCKqsdUPZqUZppN4=","TalendMainKey123") result
* is "foo"
*
*/
public static String decryptAESGCM(String encryptedString, String mainKey) {
return decryptAESGCM(encryptedString, mainKey, DEFAULT_IV_LENGTH);
}
/**
* Encrypt String: Encrypts a string using DES .
* warning: this is not considered a secure function.
*
*
* {talendTypes} String
*
* {Category} Data Masking
*
* {param} String("foo") unencryptedString: The string to be encrypted.
*
* {param} String("ThisIsSecretEncryptionKey") myEncryptionKey: the string with the DES key material.
*
* {example} encryptDES("foo") result is DmNj+x2LUXA=
*
* @throws Exception
*/
public static String encryptDES(String unencryptedString, String myEncryptionKey) {
if (unencryptedString == null || myEncryptionKey == null) {
return NULL_PARAMETER_MESSAGE;
}
if (unencryptedString.length() == 0) {
return EMPTY_PARAMETER_MESSAGE;
}
try {
String encryptedString = null;
String myEncryptionScheme = DES_ENCRYPTION_SCHEME;
byte[] keyAsBytes = myEncryptionKey.getBytes(UNICODE_FORMAT);
KeySpec myKeySpec = new DESKeySpec(keyAsBytes);
SecretKeyFactory mySecretKeyFactory = SecretKeyFactory.getInstance(myEncryptionScheme);
Cipher encipher = Cipher.getInstance(myEncryptionScheme);
SecretKey key = mySecretKeyFactory.generateSecret(myKeySpec);
encipher.init(Cipher.ENCRYPT_MODE, key);
byte[] plainText = unencryptedString.getBytes(UNICODE_FORMAT);
byte[] encryptedText = encipher.doFinal(plainText);
encryptedString = b64Encoder.encode(encryptedText);
return encryptedString;
} catch (Exception e) {
throw new DataMaskingRoutineException(e.getMessage());
}
}
/**
* Decrypt String: Decrypts a string using DES .
* warning: this is not considered a secure function.
*
*
* {talendTypes} String
*
* {Category} Data Masking
*
* {param} String("DmNj+x2LUXA=") encryptedString: the string with the DES key material.
*
* {param} String("ThisIsSecretEncryptionKey") myDecryptionKey: The string to be encrypted.
*
* {example} decryptDES("DmNj+x2LUXA=") result is "foo"
*
*/
public static String decryptDES(String encryptedString, String myDecryptionKey) {
if (encryptedString == null || myDecryptionKey == null) {
return NULL_PARAMETER_MESSAGE;
}
if (encryptedString.length() == 0) {
return EMPTY_PARAMETER_MESSAGE;
}
try {
String decryptedText = null;
String myDecryptionScheme = DES_ENCRYPTION_SCHEME;
byte[] keyAsBytes = myDecryptionKey.getBytes(UNICODE_FORMAT);
KeySpec myKeySpec = new DESKeySpec(keyAsBytes);
Cipher decipher = Cipher.getInstance(myDecryptionScheme);
SecretKeyFactory mySecretKeyFactory = SecretKeyFactory.getInstance(myDecryptionScheme);
SecretKey key = mySecretKeyFactory.generateSecret(myKeySpec);
decipher.init(Cipher.DECRYPT_MODE, key);
byte[] encryptedText = b64Dencoder.decodeBuffer(encryptedString);
byte[] plainText = decipher.doFinal(encryptedText);
StringBuilder stringBuilder = new StringBuilder();
for (byte element : plainText) {
stringBuilder.append((char) element);
}
decryptedText = stringBuilder.toString();
return decryptedText;
} catch (Exception e) {
throw new DataMaskingRoutineException(e.getMessage(), e);
}
}
/**
* This method generates a secret Key using the key-stretching algorithm PBKDF2 of
* <a href="https://docs.oracle.com/javase/7/docs/api/javax/crypto/package-summary.html">javax.crypto</a>.
* It is basically a hashing algorithm slow by design, in order to increase the time
* required for an attacker to try a lot of passwords in a bruteforce attack.
* <br>
* About the salt :
* <ul>
* <li>The salt is not secret, the use of Random is not critical and ensure determinism.</li>
* <li>The salt is important to avoid rainbow table attacks.</li>
* <li>The salt should be generated with SecureRandom() in case the passwords are stored.</li>
* <li>In that case the salt should be stored in plaintext next to the password and a unique user identifier.</li>
* </ul>
*
* @param password a password given as a {@code String}.
* @param keyLength key length to generate
* @return a {@code SecretKey} securely generated.
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
*/
private static byte[] generateSecretKeyFromPassword(String password, int keyLength)
throws NoSuchAlgorithmException, InvalidKeySpecException {
byte[] salt = new byte[keyLength];
new Random(password.hashCode()).nextBytes(salt);
SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_GEN_ALGO);
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, keyLength << 3);
return factory.generateSecret(spec).getEncoded();
}
private static String encrypt(String data, String mainKey, int ivLength) throws Exception {
final byte[] dataBytes = data.getBytes(UNICODE_FORMAT);
byte[] initializationVector = generateInitializationVector(ivLength);
final Cipher cipher = getAesGcmCipher(Cipher.ENCRYPT_MODE, mainKey, initializationVector);
final byte[] encryptedData = cipher.doFinal(dataBytes);
final byte[] encryptedBytes = new byte[encryptedData.length + ivLength];
System.arraycopy(initializationVector, 0, encryptedBytes, 0, ivLength);
System.arraycopy(encryptedData, 0, encryptedBytes, ivLength, encryptedData.length);
return BASE64_ENCODER.apply(encryptedBytes);
}
private static String decrypt(String data, String mainKey, int ivLength) throws Exception {
final byte[] encryptedBytes = BASE64_DECODER.apply(data.getBytes(UNICODE_FORMAT));
final byte[] initializationVector = new byte[ivLength];
System.arraycopy(encryptedBytes, 0, initializationVector, 0, ivLength);
final Cipher cipher = getAesGcmCipher(Cipher.DECRYPT_MODE, mainKey, initializationVector);
return new String(cipher.doFinal(encryptedBytes, ivLength, encryptedBytes.length - ivLength),
UNICODE_FORMAT);
}
}
** 编辑 2 **
在与提供者调试了一段时间后,我们最终决定使用 Michael Fehr 提供的代码。
我从组件的代码中了解到,区别在于主键的使用方式。但我不是 Java 开发人员,也许我误解了什么。
以防万一它可以帮助某人,这里是额外的 Java IV 生成和密钥生成缺少的代码。
private static Cipher getAesGcmCipher(int encryptMode, String mainKey, byte[] initializationVector)
throws Exception {
int ivLength = initializationVector.length;
if (Stream.of(12, 13, 14, 15, 16).noneMatch(i -> i == ivLength)) {
throw new IllegalArgumentException("Invalid IV length"); //$NON-NLS-1$
}
final Cipher cipher = Cipher.getInstance(GCMALGO);
SecretKey key = new SecretKeySpec(generateSecretKeyFromPassword(mainKey, mainKey.length()), ALGO);
final GCMParameterSpec spec = new GCMParameterSpec(ivLength * 8, initializationVector);
cipher.init(encryptMode, key, spec);
return cipher;
}
private static byte[] generateSecretKeyFromPassword(String password, int keyLength)
throws NoSuchAlgorithmException, InvalidKeySpecException {
byte[] salt = new byte[keyLength];
new Random(password.hashCode()).nextBytes(salt);
SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_GEN_ALGO);
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, keyLength << 3);
return factory.generateSecret(spec).getEncoded();
}
我在 Java 中设置了您的代码,并使用(固定)密钥和随机初始化向量进行了加密:
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Random;
public class SO_Main_Final {
static final String ALGO = "AES"; //$NON-NLS-1$
static final String GCMALGO = "AES/GCM/NoPadding"; //$NON-NLS-1$
static final String UNICODE_FORMAT = "UTF8"; //$NON-NLS-1$
static final Random random = new SecureRandom();
public static void main(String[] args) throws Exception {
System.out.println("
String myData = "Secret data for TytooF";
String myKey = "1234567890123456";
String encryptString = encrypt(myData, myKey, 16);
String decryptString = decrypt(encryptString, myKey, 16);
System.out.println("encryptString: " + encryptString);
System.out.println("decryptString: " + decryptString);
}
private static String encrypt(String data, String mainKey, int ivLength) throws Exception {
final byte[] dataBytes = data.getBytes(UNICODE_FORMAT);
// byte[] initializationVector = generateInitializationVector(ivLength);
byte[] initializationVector = new byte[ivLength];
random.nextBytes(initializationVector);
SecretKeySpec secretKeySpec = new SecretKeySpec(mainKey.getBytes(UNICODE_FORMAT), ALGO);
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, initializationVector);
Cipher cipher = Cipher.getInstance(GCMALGO);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec);
final byte[] encryptedData = cipher.doFinal(dataBytes);
final byte[] encryptedBytes = new byte[encryptedData.length + ivLength];
System.arraycopy(initializationVector, 0, encryptedBytes, 0, ivLength);
System.arraycopy(encryptedData, 0, encryptedBytes, ivLength, encryptedData.length);
System.out.println("data [String] : " + data);
System.out.println("data length: " + dataBytes.length
+ " data: " + bytesToHex(dataBytes));
System.out.println("mainKey length: " + mainKey.getBytes(UNICODE_FORMAT).length
+ " data: " + bytesToHex(mainKey.getBytes(UNICODE_FORMAT)));
System.out.println("initvector length: " + initializationVector.length
+ " data: " + bytesToHex(initializationVector));
System.out.println("encryptedBytes length: " + encryptedBytes.length
+ " data: " + bytesToHex(encryptedBytes));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
private static String decrypt(String data, String mainKey, int ivLength) throws Exception {
final byte[] encryptedBytes = Base64.getDecoder().decode(data.getBytes(UNICODE_FORMAT));
final byte[] initializationVector = new byte[ivLength];
System.arraycopy(encryptedBytes, 0, initializationVector, 0, ivLength);
SecretKeySpec secretKeySpec = new SecretKeySpec(mainKey.getBytes(UNICODE_FORMAT), ALGO);
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, initializationVector);
Cipher cipher = Cipher.getInstance(GCMALGO);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmParameterSpec);
return new String(cipher.doFinal(encryptedBytes, ivLength, encryptedBytes.length - ivLength),
UNICODE_FORMAT);
}
private static String bytesToHex(byte[] bytes) {
StringBuffer result = new StringBuffer();
for (byte b : bytes) result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
return result.toString();
}
}
最后我得到了这个结果——“encryptString”需要转移到您的网站:
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
在 webserver/PHP-side 我稍微修改了你的代码(“主要变化”是行 openssl_decrypt($ciphertext, $cipher, $key, true, $iv, $tag) 因为我们没有将 Base64 编码的数据提供给解密 方法,因为这是在之前完成的一些代码行 ($encrypt = base64_decode($str).
这里是 PHP-代码:
<?php
/**
* @param string $str
* The URL parameter string
*/
function test_decrypt($str)
{
$key = '1234567890123456';
$cipher = 'aes-128-gcm';
$iv_len = 16;
$tag_length = 16;
echo $str . '<br>';
/**
* Decryption part
*/
$encrypt = base64_decode($str);
$iv = substr($encrypt, 0, $iv_len);
$tag = substr($encrypt, -$tag_length);
$ciphertext = substr($encrypt, $iv_len, -$tag_length);
echo "" . "\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 </b><br>' . "\n";
echo '' . "\n";
echo 'Start decryption' . "\n";
$receivedData = "P8kU/Gf27LU9qgmLpA8gpfHmdVuW5cC7XeKVIgmb3rgGzyjG0YHX5P/cFfRRoZpUxxTEKzj8";
test_decrypt($receivedData);
?>
这是网络服务器上的解密输出:
Output for
Start decryption P8kU/Gf27LU9qgmLpA8gpfHmdVuW5cC7XeKVIgmb3rgGzyjG0YHX5P/cFfRRoZpUxxTEKzj8
iv:3fc914fc67f6ecb53daa098ba40f20a5
ciphertext:f1e6755b96e5c0bb5de29522099bdeb806cf28c6d181
tag:d7e4ffdc15f451a19a54c714c42b38fc
DecryptedString: Secret data for TytooF
DecryptedString [byte[]]:536563726574206461746120666f72205479746f6f46
2021 年 10 月 10 日编辑: 上面的代码使用的 IV/nonce 长度为 16 个字节,但 推荐 IV/nonce长度为12.