I am trying to encrypt and decrypt a string of data using 3DES and it is working fine. However I want that the size of the data after encryption to be limited to length of 16 bits.
This is the code I am referring from https://gist.github.com/riversun/6e15306cd6e3b1b37687a0e5cec1cef1 :
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class DesedeCrypter {
private static final String CRYPT_ALGORITHM = "DESede";
private static final String PADDING = "DESede/CBC/NoPadding";
private static final String CHAR_ENCODING = "UTF-8";
private static final byte[] MY_KEY = "5oquil2oo2vb63e8ionujny6".getBytes();//24-byte
private static final byte[] MY_IV = "3oco1v52".getBytes();//8-byte
public static void main(String[] args) {
String srcText = "M3A1B2C3D4HHG393";
final DesedeCrypter crypter = new DesedeCrypter();
String encryptedText = crypter.encrypt(srcText);
System.out.println("sourceText=" + srcText + " -> encryptedText=" + encryptedText + "\n");
System.out.println("encrypted-text=" + encryptedText + " -> decrypted-text(source text)="
+ crypter.decrypt(encryptedText));
}
public String encrypt(String text) {
String retVal = null;
try {
final SecretKeySpec secretKeySpec = new SecretKeySpec(MY_KEY, CRYPT_ALGORITHM);
final IvParameterSpec iv = new IvParameterSpec(MY_IV);
final Cipher cipher = Cipher.getInstance(PADDING);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv);
final byte[] encrypted = cipher.doFinal(text.getBytes(CHAR_ENCODING));
retVal = new String(encodeHex(encrypted));
} catch (Exception e) {
e.printStackTrace();
}
return retVal;
}
public String decrypt(String text) {
String retVal = null;
try {
final SecretKeySpec secretKeySpec = new SecretKeySpec(MY_KEY, CRYPT_ALGORITHM);
final IvParameterSpec iv = new IvParameterSpec(MY_IV);
final Cipher cipher = Cipher.getInstance(PADDING);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv);
final byte[] decrypted = cipher.doFinal(decodeHex(text.toCharArray()));
retVal = new String(decrypted, CHAR_ENCODING);
} catch (Exception e) {
e.printStackTrace();
}
return retVal;
}
private byte[] decodeHex(char[] data) throws Exception {
int len = data.length;
if ((len & 0x01) != 0) {
throw new Exception("Odd number of characters.");
}
byte[] out = new byte[len >> 1];
// two characters form the hex value.
for (int i = 0, j = 0; j < len; i++) {
int f = toDigit(data[j], j) << 4;
j++;
f = f | toDigit(data[j], j);
j++;
out[i] = (byte) (f & 0xFF);
}
return out;
}
private int toDigit(char ch, int index) throws Exception {
int digit = Character.digit(ch, 16);
if (digit == -1) {
throw new Exception("Illegal hexadecimal character " + ch + " at index " + index);
}
return digit;
}
private char[] encodeHex(byte[] data) {
final char[] DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
int l = data.length;
char[] out = new char[l << 1];
// two characters form the hex value.
for (int i = 0, j = 0; i < l; i++) {
out[j++] = DIGITS[(0xF0 & data[i]) >>> 4];
out[j++] = DIGITS[0x0F & data[i]];
}
return out;
}
}
Currently this is the output I am getting:
sourceText=M3A1B2C3D4HHG393 -> encryptedText=afc1d48ea5cc703253cbc1a88a198103
encrypted-text=afc1d48ea5cc703253cbc1a88a198103 -> decrypted-text(source text)=M3A1B2C3D4HHG393
Is there any way that the size of the encryptedText be limited to 16 as I want to add the encrypted text back into a message which has 16 digits space for encrypted text.
Please suggest some way or any other change that is required to achieve this. Thanks !
For one, I highly recommend not supporting (3)DES anymore, as it's officially unsecure in favour of AES/ChaCha, which I must say before answering this question.
3DES has a block size of 64 bits (or 8 bytes). With that also comes that encryption ≠ compression. So if you want to encrypt 16 bytes, provide 16 bytes of input, unless:
You apply a padding scheme (which it appears you're not doing)(taken from the DESede/CBC/NoPadding
You apply a (random) initialisation vector (which it appears you're doing)
The latter one should, but I'm not to sure of Android's implementation, create a 64 bits (8 byte) iv, as the iv should be as big as the block size of the cipher (again, 64 bits for 3DES).
So if you want 16 bytes of output, you can, and should, only provide 8 bytes to encrypt. Now, if 8 bytes is not enough, you might choose to drop the iv from being in the ciphertext, which you could do if you use a fixed iv (such as an all zero iv) with random keys, as reusing the same key with iv is not secure.
Now if you do take security in consideration, keep in mind that AES has a block size of 16 bytes. AES and ChaCha come with the same constraints regarding the iv.
Then you might also might want to consider changing the message (protocol) instead, so it can take more than 16 bytes of data or use those 16 bytes in such a way that it indicates that there is more data to handle, as like an attachment to an e-mail.
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 using following code block to generate MD5 hashes:
public static String encode(String data) throws Exception {
/* Check the validity of data */
if (data == null || data.isEmpty()) {
throw new IllegalArgumentException("Null value provided for "
+ "MD5 Encoding");
}
/* Get the instances for a given digest scheme MD5 or SHA */
MessageDigest m = MessageDigest.getInstance("MD5");
/* Generate the digest. Pass in the text as bytes, length to the
* bytes(offset) to be hashed; for full string pass 0 to text.length()
*/
m.update(data.getBytes(), 0, data.length());
/* Get the String representation of hash bytes, create a big integer
* out of bytes then convert it into hex value (16 as input to
* toString method)
*/
String digest = new BigInteger(1, m.digest()).toString(16);
return digest;
}
When I run the above code segment with String data as [12, B006GQIIEM, MH-ANT2000], the output is a 31 character hash - 268d43a823933c9dafaa4ac0e756d6a.
Is there any problem with the MD5 hash function or there is some problem in the code above?
The only issue in your code is when MSB is less than Ox10, the result hash string will only have 31 bytes, instead of 32 bytes, missing the leading zero.
Create your md5 string in this way:
byte messageDigest[] = m.digest();
hexString = new StringBuffer();
for (int i=0;i<messageDigest.length;i++) {
String hex=Integer.toHexString(0xFF & messageDigest[i]);
if(hex.length()==1)
hexString.append('0');
hexString.append(hex);
}
You can try this:
...
String digest = String.format("%032x", new BigInteger(1, m.digest()));
Note: it is "%032x", not "%32x".
This is how I use MD5 hash. Calculate MD5 hash from string and return 32-byte hexadecimal representation.
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MySimpleMD5 {
private static String convertToHex(byte[] data) {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < data.length; i++) {
int halfbyte = (data[i] >>> 4) & 0x0F;
int two_halfs = 0;
do {
if ((0 <= halfbyte) && (halfbyte <= 9))
buf.append((char) ('0' + halfbyte));
else
buf.append((char) ('a' + (halfbyte - 10)));
halfbyte = data[i] & 0x0F;
} while(two_halfs++ < 1);
}
return buf.toString();
}
public static String MD5(String text)
throws NoSuchAlgorithmException, UnsupportedEncodingException {
MessageDigest md;
md = MessageDigest.getInstance("MD5");
byte[] md5hash = new byte[32];
md.update(text.getBytes("iso-8859-1"), 0, text.length());
md5hash = md.digest();
return convertToHex(md5hash);
}
}
You can also try this:
private static String getMd5Hash(String input) throws NoSuchAlgorithmException {
MessageDigest m = MessageDigest.getInstance("MD5");
byte[] data = m.digest(EncodingUtils.getBytes(input, "UTF8"));
StringBuilder sBuilder = new StringBuilder();
for (int i = 0; i < data.length; i++) {
for (byte b : data) {
if(b == 0x00){
sBuilder.append("00");
} else if ((b & 0x0F) == b) {
sBuilder.append("0");
break;
} else {
break;
}
}
BigInteger bigInt = new BigInteger(1, data);
sBuilder.append(bigInt.toString(16));
}
// Return the hexadecimal string.
return sBuilder.toString().substring(0, 32);
}
I use Java and Log4j..
I want to log a string with german special characters, like for example Ü Ä ä etc..
But in my LogFile it appears like this:
<E4><FC><F6>
log4j.properties
log4j.rootLogger = ALL, rollingFile
log4j.appender.rollingFile=org.apache.log4j.RollingFileAppender
log4j.appender.rollingFile.File=/home/tomcat/logs/debug.log
log4j.appender.rollingFile.MaxFileSize=10MB
log4j.appender.rollingFile.MaxBackupIndex=2
log4j.appender.rollingFile.layout = org.apache.log4j.PatternLayout
log4j.appender.rollingFile.layout.ConversionPattern=%d %p %c - %m%n
log4j.appender.rollingFile.encoding=UTF-8
You should try the below code for storing and retriving the values in the unicode format
import java.io.UnsupportedEncodingException;
public class Test {
public static void printBytes(byte[] array, String name) {
for (int k = 0; k < array.length; k++) {
System.out.println(name + "[" + k + "] = " + "0x"
+ UnicodeFormatter.byteToHex(array[k]));
}
}
public static void main(String[] args) {
System.out.println(System.getProperty("file.encoding"));
String original = new String("A" + "\u00ea" + "\u00f1" + "\u00fc" + "C");
System.out.println("original = " + original);
System.out.println();
try {
byte[] utf8Bytes = original.getBytes("UTF8");
byte[] defaultBytes = original.getBytes();
String roundTrip = new String(utf8Bytes, "UTF8");
System.out.println("roundTrip = " + roundTrip);
System.out.println();
printBytes(utf8Bytes, "utf8Bytes");
System.out.println();
printBytes(defaultBytes, "defaultBytes");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
} // main
}
class UnicodeFormatter {
static public String byteToHex(byte b) {
// Returns hex String representation of byte b
char hexDigit[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f' };
char[] array = { hexDigit[(b >> 4) & 0x0f], hexDigit[b & 0x0f] };
return new String(array);
}
static public String charToHex(char c) {
// Returns hex String representation of char c
byte hi = (byte) (c >>> 8);
byte lo = (byte) (c & 0xff);
return byteToHex(hi) + byteToHex(lo);
}
}
Output
Cp1252
original = AêñüC
roundTrip = AêñüC
utf8Bytes[0] = 0x41
utf8Bytes[1] = 0xc3
utf8Bytes[2] = 0xaa
utf8Bytes[3] = 0xc3
utf8Bytes[4] = 0xb1
utf8Bytes[5] = 0xc3
utf8Bytes[6] = 0xbc
utf8Bytes[7] = 0x43
defaultBytes[0] = 0x41
defaultBytes[1] = 0xea
defaultBytes[2] = 0xf1
defaultBytes[3] = 0xfc
defaultBytes[4] = 0x43
According to the most posted issues about encoding with Log4J there doesnt seem to be any known issues, therefor i assume you are using a wrong encoding while opening the file, try to check the editor and system encoding maby you will find there an issue.
I tried using salt to decrypt a AES encrypted message but it always returns null value. Can anyone look at it where am i doing wrong?
public static String decryptMessage(String encryptedMessage, String salt) {
String decryptedMessage = null;
String valueToDecrypt = encryptedMessage;
try {
Key key = generateKey();
Cipher c = Cipher.getInstance(ALGORITHM);
c.init(Cipher.DECRYPT_MODE, key);
for (int i = 0; i < ITERATIONS; i++) {
byte[] decodedValue = Base64.decodeBase64(valueToDecrypt);
byte[] decVal = c.doFinal(decodedValue);
decryptedMessage = new String(decVal).substring(salt.length());
valueToDecrypt = decryptedMessage;
}
} catch (Exception e) {
e.printStackTrace();
}
return decryptedMessage;
}
**EDIT:**
Here is corresponding encryption method which i assume, works.
private static final String ALGORITHM = "AES";
private static final int ITERATIONS = 5;
private static final byte[] keyValue = new byte[] { '1', '2', '3', '4',
'5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6' };
public static String encryptMessage(String message, String salt) {
String encMessage = null;
byte[] encVal = null;
String messageWithSalt = null;
try {
Key key = generateKey();
Cipher c = Cipher.getInstance(ALGORITHM);
c.init(Cipher.ENCRYPT_MODE, key);
for (int i = 0; i < ITERATIONS; i++) {
messageWithSalt = salt + encMessage;
encVal = c.doFinal(messageWithSalt.getBytes());
byte[] encryptedValue = new Base64().encode(encVal);
encMessage = new String(encryptedValue);
}
} catch (Exception e) {
e.printStackTrace();
}
return encMessage;
}
PS: ITERATION is NOT 0;
I think your method might be chronically broken or misnamed. You are generating a key each time you decrypt, whereas you should have a pre-existing key that matches the one used for encryption.
You also pass in a "salt" value - note: this is a term normally reserved for hashing - which you then completely ignore, except to use the size as a truncation length on your result.
Certainly what I see above is not decrypting anything in a sensible fashion. If you can describe exactly what you wanted to achieve, we can possibly correct the code or point you at the peer-reviewed method for performing that task (which may already be implemented in the standard libs).
Well, i found the error. It was in the encryption method. encMessage was null before the encryption process begins. String encMessage = message did the trick. So the encryption method is:
public static String encryptMessage(String message, String salt) {
String encMessage = message;
byte[] encVal = null;
String messageWithSalt = null;
try {
Key key = generateKey();
Cipher c = Cipher.getInstance(ALGORITHM);
c.init(Cipher.ENCRYPT_MODE, key);
for (int i = 0; i < ITERATIONS; i++) {
messageWithSalt = salt + encMessage;
encVal = c.doFinal(messageWithSalt.getBytes());
byte[] encryptedValue = new Base64().encode(encVal);
encMessage = new String(encryptedValue);
}
} catch (Exception e) {
e.printStackTrace();
}
return encMessage;
}