I have been working on this for way too long. At this rate I wont have any hair left. I have a situation where I am trying to encrypt with c++ and decrypt with java which I thought would be easy with a standard like AES, or so I thought. Here is the code
The encrypted c++ does not decrypt correctly in the java code.
C++ using crypto++
byte key[ CryptoPP::AES::MAX_KEYLENGTH ], iv[ CryptoPP::AES::BLOCKSIZE ];
memset( key, 0x00, CryptoPP::AES::MAX_KEYLENGTH );
memset( iv, 0x00, CryptoPP::AES::BLOCKSIZE );
string plaintext = "this is a test string";
string ciphertext;
string decryptedtext;
CryptoPP::AES::Encryption aesEncryption(key, CryptoPP::AES::MAX_KEYLENGTH);
CryptoPP::CBC_Mode_ExternalCipher::Encryption cbcEncryption( aesEncryption, iv );
CryptoPP::StreamTransformationFilter stfEncryptor(cbcEncryption, new CryptoPP::StringSink( ciphertext ) );
stfEncryptor.Put( reinterpret_cast<const unsigned char*>( plaintext.c_str() ), plaintext.length() + 1 );
stfEncryptor.MessageEnd();
std::cout << "Cipher Text (" << ciphertext() << " bytes)" << std::endl;
My android/java code -
MainActivity.java
String sEncryptedMsg = "paste encrypted string here from crypto++";
try {
String messageAfterDecrypt = AESCrypt.decrypt(password, sEncryptedMsg);
Log.i("decrypted --> ", messageAfterDecrypt);
}catch (GeneralSecurityException e){
}
Android/java code - AESCrypt.java
import android.util.Base64;
import android.util.Log;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public final class AESCrypt
{
private static final String TAG = "AESCrypt";
//AESCrypt-ObjC uses CBC and PKCS7Padding
private static final String AES_MODE = "AES/CBC/PKCS7Padding";
private static final String CHARSET = "UTF-8";
//AESCrypt-ObjC uses SHA-256 (and so a 256-bit key)
private static final String HASH_ALGORITHM = "SHA-256";
//AESCrypt-ObjC uses blank IV (not the best security, but the aim here is compatibility)
private static final byte[] ivBytes = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
//togglable log option (please turn off in live!)
public static boolean DEBUG_LOG_ENABLED = false;
/**
* Generates SHA256 hash of the password which is used as key
*
* #param password used to generated key
* #return SHA256 of the password
*/
private static SecretKeySpec generateKey(final String password) throws NoSuchAlgorithmException, UnsupportedEncodingException {
final MessageDigest digest = MessageDigest.getInstance(HASH_ALGORITHM);
byte[] bytes = password.getBytes("UTF-8");
digest.update(bytes, 0, bytes.length);
byte[] key = digest.digest();
log("SHA-256 key ", key);
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
return secretKeySpec;
}
/**
* Encrypt and encode message using 256-bit AES with key generated from password.
*
*
* #param password used to generated key
* #param message the thing you want to encrypt assumed String UTF-8
* #return Base64 encoded CipherText
* #throws GeneralSecurityException if problems occur during encryption
*/
/**
* Decrypt and decode ciphertext using 256-bit AES with key generated from password
*
* #param password used to generated key
* #param base64EncodedCipherText the encrpyted message encoded with base64
* #return message in Plain text (String UTF-8)
* #throws GeneralSecurityException if there's an issue decrypting
*/
public static String decrypt(final String password, String base64EncodedCipherText)
throws GeneralSecurityException {
try {
final SecretKeySpec key = generateKey(password);
log("base64EncodedCipherText", base64EncodedCipherText);
byte[] decodedCipherText = Base64.decode(base64EncodedCipherText, Base64.NO_WRAP);
log("decodedCipherText", decodedCipherText);
byte[] decryptedBytes = decrypt(key, ivBytes, decodedCipherText);
log("decryptedBytes", decryptedBytes);
String message = new String(decryptedBytes, CHARSET);
log("message", message);
return message;
} catch (UnsupportedEncodingException e) {
if (DEBUG_LOG_ENABLED)
Log.e(TAG, "UnsupportedEncodingException ", e);
throw new GeneralSecurityException(e);
}
}
/**
* More flexible AES decrypt that doesn't encode
*
* #param key AES key typically 128, 192 or 256 bit
* #param iv Initiation Vector
* #param decodedCipherText in bytes (assumed it's already been decoded)
* #return Decrypted message cipher text (not encoded)
* #throws GeneralSecurityException if something goes wrong during encryption
*/
public static byte[] decrypt(final SecretKeySpec key, final byte[] iv, final byte[] decodedCipherText)
throws GeneralSecurityException {
final Cipher cipher = Cipher.getInstance(AES_MODE);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] decryptedBytes = cipher.doFinal(decodedCipherText);
log("decryptedBytes", decryptedBytes);
return decryptedBytes;
}
private static void log(String what, byte[] bytes) {
if (DEBUG_LOG_ENABLED)
Log.d(TAG, what + "[" + bytes.length + "] [" + bytesToHex(bytes) + "]");
}
private static void log(String what, String value) {
if (DEBUG_LOG_ENABLED)
Log.d(TAG, what + "[" + value.length() + "] [" + value + "]");
}
/**
* Converts byte array to hexidecimal useful for logging and fault finding
* #param bytes
* #return
*/
private static String bytesToHex(byte[] bytes) {
final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'A', 'B', 'C', 'D', 'E', 'F'};
char[] hexChars = new char[bytes.length * 2];
int v;
for (int j = 0; j < bytes.length; j++) {
v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
private AESCrypt() {
}
Thanks in advance with any help you can provide!
Related
I am trying to decrypt the string which is encrypted in c# with same key length and vector.
JAVA CODE
public static void main(String[] args) {
// TODO Auto-generated method stub
String cum006333 = decrypt("h+VJ5or2yBFQYawNWXET11nekUzWYYVWk0/O2fHxoLNm60l3d3qQo2NJjGr3+zZP", "MYPRIVATE#KEY");
System.out.println(cum006333);
}
public static String decrypt(String cipherText, String passphrase) {
String s = null;
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] bytes = Base64.getDecoder().decode(cipherText);
int keySize = 256;
int iterCount = 1000;
int ivLength = 16;
byte[] saltByte = new byte[]{0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76};
byte[] ivB = Arrays.copyOf(bytes, ivLength);
byte[] data = Arrays.copyOfRange(bytes,ivLength, bytes.length);
KeySpec spec = new PBEKeySpec(passphrase.toCharArray(), saltByte, iterCount, keySize);
SecretKey key = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
IvParameterSpec iv = new IvParameterSpec(ivB);
cipher.init(Cipher.DECRYPT_MODE, key, iv);
byte[] original = cipher.doFinal(data);
s = new String(original);
System.out.println(s);
} catch (Exception ex) {
ex.printStackTrace();
}
return s;
}
}
expexted output is - Duplicate Request ID
output coming is - e Request Id
I need to encrypt the request in java and send to api where they will decrypt it and use it. So i want java equivalent of the below C# code..
please find C# code used for encrypting and decrypting below
public static string EncryptResponse(string clearText)
{
string EncryptionKey = System.Configuration.ConfigurationManager.AppSettings["SECRETMSG"].ToString();
byte[] clearBytes = Encoding.Unicode.GetBytes(clearText);
using (Aes encryptor = Aes.Create())
{
Rfc2898DeriveBytes rfcdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });
encryptor.Key = rfcdb.GetBytes(32);
encryptor.IV = rfcdb.GetBytes(16);
using (MemoryStream mem = new MemoryStream())
{
using (CryptoStream cryp = new CryptoStream(mem, encryptor.CreateEncryptor(), CryptoStreamMode.Write))
{
cryp.Write(clearBytes, 0, clearBytes.Length);
cryp.Close();
}
clearText = Convert.ToBase64String(mem.ToArray());
}
}
return clearText;
}
public static string DecryptResponse(string cipherText)
{
string EncryptionKey = System.Configuration.ConfigurationManager.AppSettings["SECRETMSG"].ToString();
byte[] cipherBytes = Convert.FromBase64String(cipherText);
using (Aes encryptor = Aes.Create())
{
Rfc2898DeriveBytes rfcdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });
encryptor.Key = rfcdb.GetBytes(32);
encryptor.IV = rfcdb.GetBytes(16);
using (MemoryStream mem = new MemoryStream())
{
using (CryptoStream cryp = new CryptoStream(mem, encryptor.CreateDecryptor(), CryptoStreamMode.Write))
{
cryp.Write(cipherBytes, 0, cipherBytes.Length);
cryp.Close();
}
cipherText = Encoding.Unicode.GetString(mem.ToArray());
}
}
return cipherText;
}
As I don't like to mix answers I'm writing a second answer with the final solution for your question.
Thanks for providing the C#-code as it answered 2 questions - what is the encoding of the plaintext and what data is included in the ciphertext.
Using this line on C# shows us that the encoding is UNICODE (=UTF 16) as I argued in my first answer:
byte[] clearBytes = Encoding.Unicode.GetBytes(clearText);
To get identical results in Java you need to decode any decryption result with
s = new String(original, StandardCharsets.UTF_16LE);
The second question - what is included in the ciphertext is answered very simple - just the ciphertext itself (no IV). The neccessary IV is derived by the
Rfc2898DeriveBytes...
encryptor.Key = rfcdb.GetBytes(32);
encryptor.IV = rfcdb.GetBytes(16);
Putting both information together I can decrypt the ciphertext with the given key as follows:
First 8 bytes is truncating in AES decryption in java where encryption is done in c#
cipherByte length: 48 data: 87e549e68af6c8115061ac0d597113d759de914cd6618556934fced9f1f1a0b366eb4977777a90a363498c6af7fb364f
keyDerived length: 32 data: e35b88684a86fe06ae80eab429b2c9c242ab64f225649aa484ced8f53e5de146
ivB length: 16 data: 19b9e11cb1e6c6aa69060fbdfe10484c
original length: 40 data: 4400750070006c006900630061007400650020005200650071007500650073007400200049004400
Duplicate Request ID
Duplicate Request ID
Here is the complete code:
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.spec.KeySpec;
import java.util.Arrays;
import java.util.Base64;
public class mainSo2 {
public static void main(String[] args) {
System.out.println("First 8 bytes is truncating in AES decryption in java where encryption is done in c#");
String cipherstring = "h+VJ5or2yBFQYawNWXET11nekUzWYYVWk0/O2fHxoLNm60l3d3qQo2NJjGr3+zZP";
byte[] cipherByte = Base64.getDecoder().decode(cipherstring);
System.out.println("cipherByte length: " + cipherByte.length + " data: " + bytesToHex(cipherByte));
String cum006333 = decrypt(cipherstring, "KEY#CRISIL123");
System.out.println(cum006333);
}
public static String decrypt(String cipherText, String passphrase) {
String s = null;
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] bytes = Base64.getDecoder().decode(cipherText);
int keySize = 256;
int iterCount = 1000;
int ivLength = 16;
byte[] saltByte = new byte[]{0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76};
KeySpec spec = new PBEKeySpec(passphrase.toCharArray(), saltByte, iterCount, (keySize + (8 * ivLength))); // 32 + 16
byte[] rawKey = factory.generateSecret(spec).getEncoded(); // 48 bytes
byte[] keyDerived = Arrays.copyOf(rawKey, (keySize / 8)); // first 32 bytes
byte[] ivB = Arrays.copyOfRange(rawKey, (keySize / 8), rawKey.length); // last 16 bytes
System.out.println("keyDerived length: " + keyDerived.length + " data: " + bytesToHex(keyDerived));
System.out.println("ivB length: " + ivB.length + " data: " + bytesToHex(ivB));
SecretKey key = new SecretKeySpec(keyDerived, "AES");
IvParameterSpec iv = new IvParameterSpec(ivB);
cipher.init(Cipher.DECRYPT_MODE, key, iv);
//byte[] original = cipher.doFinal(data);
byte[] original = cipher.doFinal(bytes);
System.out.println("original length: " + original.length + " data: " + bytesToHex(original));
s = new String(original, StandardCharsets.UTF_16LE);
System.out.println(s);
} catch (Exception ex) {
ex.printStackTrace();
}
return s;
}
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();
}
}
What a shame that you don't show us the encryption code in C# as it would point us directly to the solution instead of arguing and searching.
The reason for your claiming of "truncating" is not on Java-side (decryption) but on your c# encryption function. Most probably you are using a UTF-16 encoding for your plaintext but on Java-side the standard encoding (UTF-8) takes place.
Below you find a slightly added version of your code with dedicated outputs for your data to be fore sure all data has the right length.
Having a look on the ".doFinal" output we see a value of (hex) "650020005200650071007500650073007400200049004400" - a lot of "x00" values between each character that is definitely a UTF-16 encoding, so in the end the string value is not "e Request Id" but "e R e q u e s t I D".
Changing the "new String" encoding to "new String(original, StandardCharsets.UTF_16LE)" get the "correct" output of "e Request ID" - but where is the missing
"Duplicat" in the beginning? I'm sure you are getting the (base64 encoded) string of ciphertext by "Array-copying" as well and you are just copy the last 12 (or 24 in UTF-16) bytes of the ciphertext so - what I am believing - you are truncating the ciphertext on C#-side.
output
First 8 bytes is truncating in AES decryption in java where encryption is done in c#
cipherByte length: 48 data: 87e549e68af6c8115061ac0d597113d759de914cd6618556934fced9f1f1a0b366eb4977777a90a363498c6af7fb364f
ivB length: 16 data: 87e549e68af6c8115061ac0d597113d7
data length: 32 data: 59de914cd6618556934fced9f1f1a0b366eb4977777a90a363498c6af7fb364f
original length: 24 data: 650020005200650071007500650073007400200049004400
e R e q u e s t I D
e R e q u e s t I D
change code line
s = new String(original);
to
s = new String(original, StandardCharsets.UTF_16LE);
gives:
e Request ID
code:
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.spec.KeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
public class mainSo {
public static void main(String[] args) {
System.out.println("First 8 bytes is truncating in AES decryption in java where encryption is done in c#");
String cipherstring = "h+VJ5or2yBFQYawNWXET11nekUzWYYVWk0/O2fHxoLNm60l3d3qQo2NJjGr3+zZP";
byte[] cipherByte = Base64.getDecoder().decode(cipherstring);
System.out.println("cipherByte length: " + cipherByte.length + " data: " + bytesToHex(cipherByte));
String cum006333 = decrypt(cipherstring, "KEY#CRISIL123");
System.out.println(cum006333);
}
public static String decrypt(String cipherText, String passphrase) {
String s = null;
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] bytes = Base64.getDecoder().decode(cipherText);
int keySize = 256;
int iterCount = 1000;
int ivLength = 16;
byte[] saltByte = new byte[]{0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76};
byte[] ivB = Arrays.copyOf(bytes, ivLength);
byte[] data = Arrays.copyOfRange(bytes,ivLength, bytes.length);
System.out.println("ivB length: " + ivB.length + " data: " + bytesToHex(ivB));
System.out.println("data length: " + data.length + " data: " + bytesToHex(data));
KeySpec spec = new PBEKeySpec(passphrase.toCharArray(), saltByte, iterCount, keySize);
SecretKey key = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
IvParameterSpec iv = new IvParameterSpec(ivB);
cipher.init(Cipher.DECRYPT_MODE, key, iv);
byte[] original = cipher.doFinal(data);
System.out.println("original length: " + original.length + " data: " + bytesToHex(original));
s = new String(original);
//s = new String(original, StandardCharsets.UTF_16LE);
System.out.println(s);
} catch (Exception ex) {
ex.printStackTrace();
}
return s;
}
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();
}
}
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 . '<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;
}
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.
* <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);
}
}
** 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:
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.
I am looking for any sample java code that will decrypt the messages encrypted using "openssl enc -aes-256-cbc) -a -salt" command provided the key is known.
https://pastebin.com/YiwbCAW8
So far i was able to get the following java code that encrypts and also decrypts the message. But i am not able to decrypt the encrypted message using openssl command. Getting "Bad Magic Number" error. Any idea ?
Encrypt the message using the code >
Encrypt("sample text", "test$password") = "i+5zkPPgnDdV7fr/w8uHkw=="
Decrypt("i+5zkPPgnDdV7fr/w8uHkw==", "test$password") = "sample text"
Decrypt the message using openssl >
F:\cipher>echo i+5zkPPgnDdV7fr/w8uHkw== | openssl aes-256-cbc -a -salt -d
enter aes-256-cbc decryption password:
bad magic number
import java.security.spec.KeySpec;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
public class AES {
private static final byte[] SALT = {
(byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32,
(byte) 0x56, (byte) 0x35, (byte) 0xE3, (byte) 0x03
};
private static final int ITERATION_COUNT = 65536;
private static final int KEY_LENGTH = 256;
private Cipher ecipher;
private Cipher dcipher;
AES(String passPhrase) throws Exception {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(passPhrase.toCharArray(), SALT, ITERATION_COUNT, KEY_LENGTH);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
ecipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
ecipher.init(Cipher.ENCRYPT_MODE, secret);
dcipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv = ecipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
dcipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
}
public String encrypt(String encrypt) throws Exception {
byte[] bytes = encrypt.getBytes("UTF8");
byte[] encrypted = encrypt(bytes);
return Base64.getEncoder().encodeToString(encrypted);
}
public byte[] encrypt(byte[] plain) throws Exception {
return ecipher.doFinal(plain);
}
public String decrypt(String encrypt) throws Exception {
byte[] bytes = Base64.getDecoder().decode(encrypt);
byte[] decrypted = decrypt(bytes);
return new String(decrypted, "UTF8");
}
public byte[] decrypt(byte[] encrypt) throws Exception {
return dcipher.doFinal(encrypt);
}
public static void main(String[] args) throws Exception {
String message = "sample text";
String password = "test$password";
AES encrypter = new AES(password);
String encrypted = encrypter.encrypt(message);
String decrypted = encrypter.decrypt(encrypted);
System.out.println("Encrypt(\"" + message + "\", \"" + password + "\") = \"" + encrypted + "\"");
System.out.println("Decrypt(\"" + encrypted + "\", \"" + password + "\") = \"" + decrypted + "\"");
}
}
You may search stackoverflow for many similar questions.
you have multiple issues in your code:
You use different keys:
In Java you use PBKDF2 to generate an encryption key from the provided password. Openssl uses its EVP_BytesToKey. Search internet for Java implementation. Please note the hash used in the EVP_BytesToKey changed with some openssl version (from MD5 to SHA-1 SHA-256), if someone is having more details, please comment
And you use random IV. you don't pass the IV along the ciphertext, so you may be able to decrypt the ciphertext with the same cipher instance (kkeping the same iv), but lets try your Java code to decrypt your ciphertext other time or with other instance, it won't work. You need to pass IV along the ciphertext (usually it's prepended)
Openssl expect following format:
Salted_<8 byte salt>ciphertext
Salted__<8 byte salt>ciphertext
8 byte salt is a random byte array used to generate the encryption key and IV from the provided password. Try encrypt with openssl with -pparameter, it will print the salt, IV and Key generated so you can check and compare
Using CBC without any integrity check (hmac, ..) may be unsafe in many implementations
Suggestions:
you can find an openssl java library implementing the same required (EVP_BytesToKey)
you can implement EVP_BytesToKey yourself
you can use openssl directly with -K/-ivparameters providing the encryption key and IV (in hex format) instead of password, then openssl expects pure ciphertext (no Salted_ or salt inside the input)
Thanks a lot for the clues. As mentioned, did some search and modified the code from one of the post. I have seen similar code with EVP_BytesToKeys in many places, but took some time to figure out the usage. I am able to decrypt the msg encrypted by openssl.
Trying to search the code for encryption as well. Meanwhile any help of encryption is appreciated as well.
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
public class AES5 {
private static final Charset ASCII = Charset.forName("ASCII");
private static final int INDEX_KEY = 0;
private static final int INDEX_IV = 1;
private static final int ITERATIONS = 1;
private static final int SALT_OFFSET = 8;
private static final int SALT_SIZE = 8;
private static final int CIPHERTEXT_OFFSET = SALT_OFFSET + SALT_SIZE;
private static final int KEY_SIZE_BITS = 256;
/**
* Thanks go to Ola Bini for releasing this source on his blog. The source was
* obtained from here
*
*/
public static byte[][] EVP_BytesToKey(int key_len, int iv_len, MessageDigest md, byte[] salt, byte[] data,
int count) {
byte[][] both = new byte[2][];
byte[] key = new byte[key_len];
int key_ix = 0;
byte[] iv = new byte[iv_len];
int iv_ix = 0;
both[0] = key;
both[1] = iv;
byte[] md_buf = null;
int nkey = key_len;
int niv = iv_len;
int i = 0;
if (data == null) {
return both;
}
int addmd = 0;
for (;;) {
md.reset();
if (addmd++ > 0) {
md.update(md_buf);
}
md.update(data);
if (null != salt) {
md.update(salt, 0, 8);
}
md_buf = md.digest();
for (i = 1; i < count; i++) {
md.reset();
md.update(md_buf);
md_buf = md.digest();
}
i = 0;
if (nkey > 0) {
for (;;) {
if (nkey == 0)
break;
if (i == md_buf.length)
break;
key[key_ix++] = md_buf[i];
nkey--;
i++;
}
}
if (niv > 0 && i != md_buf.length) {
for (;;) {
if (niv == 0)
break;
if (i == md_buf.length)
break;
iv[iv_ix++] = md_buf[i];
niv--;
i++;
}
}
if (nkey == 0 && niv == 0) {
break;
}
}
for (i = 0; i < md_buf.length; i++) {
md_buf[i] = 0;
}
return both;
}
public static byte[][] getKeyIV(byte[] headerSaltAndCipherText, Cipher aesCBC, String password) {
byte[] salt = Arrays.copyOfRange(headerSaltAndCipherText, SALT_OFFSET, SALT_OFFSET + SALT_SIZE);
byte[][] keyAndIV=null;
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
keyAndIV = EVP_BytesToKey(KEY_SIZE_BITS / Byte.SIZE, aesCBC.getBlockSize(), md5, salt,
password.getBytes(ASCII), ITERATIONS);
} catch (Exception e) {e.printStackTrace();}
return keyAndIV;
}
// https://stackoverflow.com/questions/11783062/how-to-decrypt-file-in-java-encrypted-with-openssl-command-using-aes
public static String decrypt(String encryptedMsg, String password) {
String decryptedMsg =null;
byte[] headerSaltAndCipherText = Base64.decodeBase64(encryptedMsg);
byte[] encrypted = Arrays.copyOfRange(headerSaltAndCipherText, CIPHERTEXT_OFFSET, headerSaltAndCipherText.length);
try {
Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
final byte[][] keyAndIV = getKeyIV(headerSaltAndCipherText, aesCBC, password);
SecretKeySpec key = new SecretKeySpec(keyAndIV[INDEX_KEY], "AES");
IvParameterSpec iv = new IvParameterSpec(keyAndIV[INDEX_IV]);
aesCBC.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decrypted = aesCBC.doFinal(encrypted);
decryptedMsg = new String(decrypted, ASCII);
} catch (Exception e) {e.printStackTrace();}
return decryptedMsg;
}
//TODO - Encrypt the msg in same manner as "openssl enc -aes-256-cbc -a -salt"
public static String encrypt(String msg, String password) {
String decryptedMsg =null;
byte[] headerSaltAndCipherText = Base64.decodeBase64(msg);
byte[] encrypted = Arrays.copyOfRange(headerSaltAndCipherText, CIPHERTEXT_OFFSET, headerSaltAndCipherText.length);
try {
Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
final byte[][] keyAndIV = getKeyIV(headerSaltAndCipherText, aesCBC, password);
SecretKeySpec key = new SecretKeySpec(keyAndIV[INDEX_KEY], "AES");
IvParameterSpec iv = new IvParameterSpec(keyAndIV[INDEX_IV]);
aesCBC.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] decrypted = aesCBC.doFinal(encrypted);
decryptedMsg = new String(decrypted, ASCII);
} catch (Exception e) {e.printStackTrace();}
return decryptedMsg;
}
public static void main(String[] args) {
String msg = "the decrypted message is this";
String password = "pass";
System.out.println(encrypt(msg, password));
String encryptedMsg = "U2FsdGVkX190A5FsNTanwTKBdex29SpnH4zWkZN+Ld+MmbJgK4BH1whGIRRSpOJT";
System.out.println(decrypt(encryptedMsg, password));
}
}
Also got an improved solution from the following site. Got the code for both encryption and decryption for now...
http://qaru.site/questions/19874/java-equivalent-of-an-openssl-aes-cbc-encryption
import java.net.URLEncoder;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import static java.nio.charset.StandardCharsets.*;
/**
* Mimics the OpenSSL AES Cipher options for encrypting and decrypting messages using a
* shared key (aka password) with symetric ciphers.
*/
public class OpenSslAesQu {
/** OpenSSL magic initial bytes. */
private static final String SALTED_STR = "Salted__";
private static final byte[] SALTED_MAGIC = SALTED_STR.getBytes(US_ASCII);
public static String encryptAndURLEncode(String password, String clearText) {
String encrypted = null;
try {
encrypted = URLEncoder.encode(encrypt(password, clearText),UTF_8.name());
} catch (Exception e) {e.printStackTrace();}
return encrypted;
}
/**
*
* #param password The password / key to encrypt with.
* #param data The data to encrypt
* #return A base64 encoded string containing the encrypted data.
*/
public static String encrypt(String password, String clearText) {
String encryptedMsg = null;
final byte[] pass = password.getBytes(US_ASCII);
final byte[] salt = (new SecureRandom()).generateSeed(8);
final byte[] inBytes = clearText.getBytes(UTF_8);
final byte[] passAndSalt = array_concat(pass, salt);
byte[] hash = new byte[0];
byte[] keyAndIv = new byte[0];
try {
for (int i = 0; i < 3 && keyAndIv.length < 48; i++) {
final byte[] hashData = array_concat(hash, passAndSalt);
final MessageDigest md = MessageDigest.getInstance("MD5");
hash = md.digest(hashData);
keyAndIv = array_concat(keyAndIv, hash);
}
final byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32);
final byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48);
final SecretKeySpec key = new SecretKeySpec(keyValue, "AES");
final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
byte[] data = cipher.doFinal(inBytes);
data = array_concat(array_concat(SALTED_MAGIC, salt), data);
//return Base64.getEncoder().encodeToString( data );
encryptedMsg = org.apache.commons.codec.binary.Base64.encodeBase64String(data);
} catch(Exception e) {e.printStackTrace();}
return encryptedMsg;
}
/**
* #see http://stackoverflow.com/questions/32508961/java-equivalent-of-an-openssl-aes-cbc-encryption for what looks like a useful answer. The not-yet-commons-ssl also has an implementation
* #param password
* #param source The encrypted data
*/
public static String decrypt(String password, String source) {
String decryptedMsg = null;
final byte[] pass = password.getBytes(US_ASCII);
//final byte[] inBytes = Base64.getDecoder().decode(source);
final byte[] inBytes = Base64.decodeBase64(source);
final byte[] shouldBeMagic = Arrays.copyOfRange(inBytes, 0, SALTED_MAGIC.length);
if (!Arrays.equals(shouldBeMagic, SALTED_MAGIC)) {
throw new IllegalArgumentException("Initial bytes from input do not match OpenSSL SALTED_MAGIC salt value.");
}
final byte[] salt = Arrays.copyOfRange(inBytes, SALTED_MAGIC.length, SALTED_MAGIC.length + 8);
final byte[] passAndSalt = array_concat(pass, salt);
byte[] hash = new byte[0];
byte[] keyAndIv = new byte[0];
try {
for (int i = 0; i < 3 && keyAndIv.length < 48; i++) {
final byte[] hashData = array_concat(hash, passAndSalt);
final MessageDigest md = MessageDigest.getInstance("MD5");
hash = md.digest(hashData);
keyAndIv = array_concat(keyAndIv, hash);
}
final byte[] keyValue = Arrays.copyOfRange(keyAndIv, 0, 32);
final SecretKeySpec key = new SecretKeySpec(keyValue, "AES");
final byte[] iv = Arrays.copyOfRange(keyAndIv, 32, 48);
final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
final byte[] clear = cipher.doFinal(inBytes, 16, inBytes.length - 16);
decryptedMsg = new String(clear, UTF_8);
} catch (Exception e) {e.printStackTrace();}
return decryptedMsg;
}
private static byte[] array_concat(final byte[] a, final byte[] b) {
final byte[] c = new byte[a.length + b.length];
System.arraycopy(a, 0, c, 0, a.length);
System.arraycopy(b, 0, c, a.length, b.length);
return c;
}
public static void main(String[] args) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
String msg = "the decrypted message is this";
String password = "pass";
System.out.println(">> "+encrypt(password,msg));
//System.out.println("<< "+decrypt(encrypt(msg, password), password));
String encryptedMsg = "U2FsdGVkX190A5FsNTanwTKBdex29SpnH4zWkZN+Ld+MmbJgK4BH1whGIRRSpOJT";
String encryptedMsg2 = "U2FsdGVkX1/B6oOznz5+nd7W/qXwXI7G7rhj5o9pjx8MS0TXp9SNxO3AhM9HBJ/z";
System.out.println(decrypt(password,encryptedMsg));
System.out.println(decrypt(password,encryptedMsg2));
System.out.println(decrypt(password,encrypt(password,msg)));
}
}
I am using Java Program to encrypt the data and sending the encrypted data to Linux machine where data should be decrypted back to original text using OpenSSL.
If i do encryption and decryption in Java Program, it is working fine. But on Linux machine,data is not getting decrypted.
I m getting below exception:
bad decrypt
140209007396768:error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length:evp_enc.c:597:
.
Below is my Java Code:
public class EncryptionTest{
Cipher c;
static SecretKey secKey;
static IvParameterSpec iv;
public static void main(String[] args) throws Exception
{
KeyGenerator keygen=KeyGenerator.getInstance("DES");
secKey=keygen.generateKey();
byte[] encoded = secKey.getEncoded();
String KeyString=bytesToHex(encoded);
System.out.println(KeyString);
SecureRandom rnd = new SecureRandom();
iv = new IvParameterSpec(rnd.generateSeed(8));
byte[] ivencoded=iv.getIV();
String ivString=bytesToHex(ivencoded);
System.out.println(ivString);
new EncryptionTest().start();
}
// encoded the key to hex so that i can use at Linux machine to decrypt the data.
public static String bytesToHex(byte[] bytes) {
char[] hexArray = "0123456789ABCDEF".toCharArray();
char[] hexChars = new char[bytes.length * 2];
for ( int j = 0; j < bytes.length; j++ ) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
public void start() throws Exception
{
System.out.println("Encryption/Decryption App");
c = Cipher.getInstance("DES/CBC/PKCS5Padding");
String input ="Data to be ecrypted";
byte[] text = input.getBytes();
System.out.println("--------------------------------------------");
System.out.println("Text : " + new String(text));
byte[] textEncrypted = encrypt(text, c);
String encryptedString=bytesToHex(textEncrypted);
System.out.println("Text Encrypted : " + encryptedString);
byte[] textDecrypted = decrypt(textEncrypted, c);
System.out.println("Text Decrypted : " + new String(textDecrypted));
System.out.println("--------------------------------------------");
}
public byte[] encrypt(byte[] b, Cipher c) throws Exception
{
c.init(Cipher.ENCRYPT_MODE, secKey,iv);
byte[] encryptedText = null;
try {
encryptedText = c.doFinal(b);
} catch (IllegalBlockSizeException e) {
System.out.println("ERROR - error occured");
System.exit(0);
}
return encryptedText;
}
public byte[] decrypt(byte[] b, Cipher c) throws Exception
{
c.init(Cipher.DECRYPT_MODE, secKey,iv);
byte[] decryptedText = c.doFinal(b);
return decryptedText;
}
}
At Linux Machine:openssl enc -des-cbc -d -in test.txt -out received.txt -iv "ivKeyGenerated from Java" -K "key Generated from Java"
But Decryption is not getting happened at Linux machine.
Please suggest where i am doing wrong.
I'm trying to take in a long string and encrypt it using the following code:
import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import java.io.*;
public class AESEncrypt {
/**
* Turns array of bytes into string
*
* #param buf
* Array of bytes to convert to hex string
* #return Generated hex string
*/
public static String asHex(byte buf[]) {
StringBuffer strbuf = new StringBuffer(buf.length * 2);
int i;
for (i = 0; i < buf.length; i++) {
if (((int) buf[i] & 0xff) < 0x10)
strbuf.append("0");
strbuf.append(Long.toString((int) buf[i] & 0xff, 16));
}
return strbuf.toString();
}
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character
.digit(s.charAt(i + 1), 16));
}
return data;
}
public static void main(String[] args) throws Exception {
String message = "Test text!";
// Get the KeyGenerator
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128); // 192 and 256 bits may not be available
// Generate the secret key specs.
SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
System.out.println("Key: " + asHex(raw));
// Instantiate the cipher
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal((args.length == 0 ? message : args[0]).getBytes());
System.out.println("encrypted string: " + asHex(encrypted));
}
}
However, I would like to encrypt word by word and print out the encrypted text as such:
Original string -> Test text!
Encrypted string -> 29f84h2f 23f9f92jf3
I couldn't find any examples online that could help me out. Is there anyway I can achieve this?
AES is a block cypher, and it uses 16 byte blocks. It does not work in words, but in fixed blocks. If you want to split up your text into varying size words then you will likely get closer to what you want by using either a stream cypher, such as RC4, or alternatively AES in CTR mode, which effectively turns AES into a stream cypher. Stream cyphers do not work in blocks, but in bytes. You can take 3 bytes off the stream for a 3 letter word, or nine bytes off the stream for a 9 letter word.
You will need to work out what to do with spaces, punctuation etc between words. You will also need to think about how often you re-key the cypher. Do you want to re-key for every individual word, or do you just re-key at the start of each string? As with any stream cypher, never ever use the same key twice.
Try as below example; Actually, It just need to used StringTokenizer. Firstly you have to token your target string. After that, encrypt the token string.
import java.util.StringTokenizer;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
public class AES {
public static String asHex(byte[] buf) {
StringBuffer strbuf = new StringBuffer(buf.length * 2);
int i;
for (i = 0; i < buf.length; i++) {
if (((int)buf[i] & 0xff) < 0x10)
strbuf.append("0");
strbuf.append(Long.toString((int)buf[i] & 0xff, 16));
}
return strbuf.toString();
}
public static void main(String[] args) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128); // 192 and 256 bits may not be available
// Generate the secret key specs.
SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
String target = "This is just an example";
StringTokenizer token = new StringTokenizer(target);
while(token.hasMoreTokens()) {
String temp = token.nextToken();
byte[] encrypted = cipher.doFinal((args.length == 0 ? temp : args[0]).getBytes());
System.out.println(asHex(encrypted) + " ");
}
}
}
Output :
d40186eab04d10e299801e7ad9046c06 6a71265c768a3b6e1f1a8f891d621c1d 735e3f54c8ad7242466e3517e8dd1659 5216643345db0f0c12f65c66c5363be3 b823355d5bb31bf092df98e18fa8001c