I am trying to reproduce the following encryption/decryption algorithm in Java, but I can't find the alternatives for multiple methods such as Rfc2898DeriveBytes() and RijndaelManaged(). How do I do this?
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace CryptingTest
{
public static class StringCipher
{
// This constant is used to determine the keysize of the encryption algorithm in bits.
// We divide this by 8 within the code below to get the equivalent number of bytes.
private const int Keysize = 128;
// This constant determines the number of iterations for the password bytes generation function.
private const int DerivationIterations = 1000;
public static string Encrypt(string plainText, string passPhrase)
{
// Salt and IV is randomly generated each time, but is preprended to encrypted cipher text
// so that the same Salt and IV values can be used when decrypting.
var saltStringBytes = Generate128BitsOfRandomEntropy();
var ivStringBytes = Generate128BitsOfRandomEntropy();
var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
{
var keyBytes = password.GetBytes(Keysize / 8);
using (var symmetricKey = new RijndaelManaged())
{
symmetricKey.BlockSize = 128;
symmetricKey.Mode = CipherMode.CBC;
symmetricKey.Padding = PaddingMode.PKCS7;
using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes))
{
using (var memoryStream = new MemoryStream())
{
using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock();
// Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes.
var cipherTextBytes = saltStringBytes;
cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
memoryStream.Close();
cryptoStream.Close();
return Convert.ToBase64String(cipherTextBytes);
}
}
}
}
}
}
public static string Decrypt(string cipherText, string passPhrase)
{
// Get the complete stream of bytes that represent:
// [32 bytes of Salt] + [16 bytes of IV] + [n bytes of CipherText]
var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText);
// Get the saltbytes by extracting the first 16 bytes from the supplied cipherText bytes.
var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray();
// Get the IV bytes by extracting the next 16 bytes from the supplied cipherText bytes.
var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray();
// Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string.
var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray();
using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations))
{
var keyBytes = password.GetBytes(Keysize / 8);
using (var symmetricKey = new RijndaelManaged())
{
symmetricKey.BlockSize = 128;
symmetricKey.Mode = CipherMode.CBC;
symmetricKey.Padding = PaddingMode.PKCS7;
using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes))
{
using (var memoryStream = new MemoryStream(cipherTextBytes))
{
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
{
var plainTextBytes = new byte[cipherTextBytes.Length];
var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length);
memoryStream.Close();
cryptoStream.Close();
return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount);
}
}
}
}
}
}
private static byte[] Generate128BitsOfRandomEntropy()
{
var randomBytes = new byte[16]; // 16 Bytes will give us 128 bits.
using (var rngCsp = new RNGCryptoServiceProvider())
{
// Fill the array with cryptographically secure random bytes.
rngCsp.GetBytes(randomBytes);
}
return randomBytes;
}
}
}
Any help would be really appreciated.
Here's a code snippet from what I tried so far but it's still off:
private static byte[] Generate128BitsOfRandomEntropy()
{
var randomBytes = new byte[16]; // 16 Bytes will give us 128 bits.
SecureRandom rngCsp = new SecureRandom();
// Fill the array with cryptographically secure random bytes.
rngCsp.nextBytes(randomBytes);
return randomBytes;
}
public static String encrypt(String plainText, String passPhrase)
{
try
{
var saltStringBytes = Generate128BitsOfRandomEntropy();
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec pbeKeySpec = new PBEKeySpec(passPhrase.toCharArray(), saltStringBytes, 1000, 384);
Key secretKey = factory.generateSecret(pbeKeySpec);
byte[] key = new byte[16];
byte[] iv = new byte[16];
System.arraycopy(secretKey.getEncoded(), 0, key, 0, 16);
System.arraycopy(secretKey.getEncoded(), 16, iv, 0, 16);
SecretKeySpec secret = new SecretKeySpec(key, "AES");
AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret, ivSpec);
return Base64.getEncoder().encodeToString(cipher.doFinal(plainText.getBytes("UTF-8")));
}
catch (Exception e)
{
System.out.println("Error while encrypting: " + e.toString());
}
return null;
}
Rfc2898DeriveBytes implements PBKDF2, and RijndaelManaged with a block size of 128 bits implements AES. Both seem to be applied correctly in the Java code.
However, there are differences in determining the IV and regarding concatenation: In the C# code, the salt and IV are determined randomly and concatenated with the ciphertext at the end.
In the Java code only the salt is determined randomly, the IV is derived together with the key and the concatenation is missing.
I.e. the encrypt() method in the Java code could be changed for instance as follows, so that a decryption with the C# code is possible:
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
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;
...
byte[] salt = Generate128BitsOfRandomEntropy();
byte[] iv = Generate128BitsOfRandomEntropy();
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec pbeKeySpec = new PBEKeySpec(passPhrase.toCharArray(), salt, 1000, 128);
SecretKey secretKey = factory.generateSecret(pbeKeySpec);
SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");
AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivSpec);
byte[] ciphertext = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
byte[] saltIvCiphertext = ByteBuffer.allocate(salt.length + iv.length + ciphertext.length).put(salt).put(iv).put(ciphertext).array();
return Base64.getEncoder().encodeToString(saltIvCiphertext);
Note that for PBKDF2, an iteration count of 1000 is generally too low.
Related
I need to convert java code for encryption and decryption using AES/CBC/PKCS5Padding algorithm to dart code.
The java code of AES/CBC/PKCS5Padding encryption and decryption:
package test_Terminal.classes;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
/**
*
* #author jeena
*/
public class IOTEncodingDecoding {
SecretKeySpec secretKeySpec;
IvParameterSpec ivSpec;
String EncryptionKey = "733D3A17-D8A0-454B-AD22-88608FD0C46A";
String saltString = "FA9A4D0F-5523-4EEF-B226-9A3E8F14FEF8";
String algorithm = "AES/CBC/PKCS5Padding";
int encoding_mode;
test_Terminal.classes.general General = new test_Terminal.classes.general();
void setSecretKey() {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec pbeKeySpec = new PBEKeySpec(EncryptionKey.toCharArray(), saltString.getBytes(StandardCharsets.UTF_16LE), 1000, 384);
byte[] derivedData = factory.generateSecret(pbeKeySpec).getEncoded();
byte[] key = new byte[32];
byte[] iv = new byte[16];
System.arraycopy(derivedData, 0, key, 0, key.length);
System.arraycopy(derivedData, key.length, iv, 0, iv.length);
secretKeySpec = new SecretKeySpec(key, "AES");
ivSpec = new IvParameterSpec(iv);
} catch (Exception e) {
General.LogException("setSecretKey", e);
}
}
public String encrypt(String input) {
try {
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivSpec);
byte[] cipherText ;
if(encoding_mode==1)
cipherText = cipher.doFinal(input.getBytes(StandardCharsets.UTF_16LE));
else
cipherText = cipher.doFinal(input.getBytes());
return Base64.getEncoder().encodeToString(cipherText);
} catch (Exception e) {
General.LogException("encrypt", e);
}
return "";
}
public String decrypt(String cipherText) {
try {
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivSpec);
byte[] plainText = cipher.doFinal(Base64.getDecoder().decode(cipherText));
if(encoding_mode==1)
return new String(plainText, StandardCharsets.UTF_16LE);
else
return new String(plainText);
} catch (Exception e) {
General.LogException("decrypt", e);
General.LogActivity("decrypt", e.getMessage());
}
return "Ticket format error";
}
public void setMode() {
setSecretKey();
}
}
I need to get the following result:
Input(PlainText):C123492349C1CT20230206130645.
Output(Encrypted string):8tyHRaQCsxmmGW2xPBFYx/PALmvHkmjx/TzaXC2rIv0=
This is the dart code that I've got so far for decryption, but I'm getting error.
Uint8List? decrypt(String ciphertext, String password) {
Uint8List rawCipher = base64.decode(ciphertext);
var salt = rawCipher.sublist(0, 0 + 8);
var iv = rawCipher.sublist(8, 8 + 16);
var encrypted = rawCipher.sublist(8 + 16);
Uint8List key = generateKey(password, salt);
print('key => $key');
CBCBlockCipher cipher = CBCBlockCipher(AESEngine());
ParametersWithIV<KeyParameter> params =
ParametersWithIV<KeyParameter>(KeyParameter(key), iv);
PaddedBlockCipherParameters<ParametersWithIV<KeyParameter>, Null>
paddingParams =
PaddedBlockCipherParameters<ParametersWithIV<KeyParameter>, Null>(
params, null);
PaddedBlockCipherImpl paddingCipher =
PaddedBlockCipherImpl(PKCS7Padding(), cipher);
paddingCipher.init(false, paddingParams);
var val = paddingCipher.process(encrypted);
String res = String.fromCharCodes(val);
debugPrint('res => $res');
return val;
}
Uint8List generateKey(String passphrase, Uint8List salt) {
final derivator = PBKDF2KeyDerivator(HMac(SHA1Digest(), 64))
..init(Pbkdf2Parameters(salt, 1024, 16));
return derivator.process(utf8.encode(passphrase) as Uint8List);
}
I got this code from
The Exception that I'm getting is:
Exception has occurred.
ArgumentError (Invalid argument(s): Input data length must be a multiple of cipher's block size)
I think the values inside rawCipher.sublist() function is wrong. I'm stuck on this problem for few days, please help.
Both codes differ:
Regarding encodings: The Dart code does not consider the UTF-16 LE encoding of the salt. Furthermore, the encoding of the plaintext is unclear. For encoding_mode==1 it is UTF-16LE, otherwise it corresponds to the platform encoding in your environment (which only you know).
Regarding PBKDF2: The Java code derives key and IV from a static salt (note that a static salt is a vulnerability), while the Dart code assumes a concatenation in the order salt|IV|ciphertext during encryption (using a random 8 bytes salt and a random IV).
Also, different iteration counts are used: 1000 in the Java code, 1024 in the Dart code (note that both values are generally much too small for PBKDF2).
The differences can be fixed as follows:
Regarding encodings: In the Dart code, the salt must first be UTF-16 LE encoded: Since the utf package is deprecated, see e.g. here for a UTF-16 LE encoding and here for the decoding. The encoding can be adapted to:
Uint8List encodeUtf16LE(String salt) {
var byteData = ByteData(salt.codeUnits.length * 2);
for (var i = 0; i < salt.codeUnits.length; i += 1) {
byteData.setUint16(i * 2, salt.codeUnits[i], Endian.little);
}
return byteData.buffer.asUint8List();
}
Moreover, from the sample data it can be concluded (by testing) that the plaintext in the Java code has been encoded with UTF-8.
Regarding PBKDF2: In the Dart code, key and IV must be derived from the static salt applied in the Java code.
Also, the parameters from the Java code must be applied (digest: SHA-1, iteration count: 1000, keysize: 32 + 16 = 48 bytes):
Uint8List generateKey(String passphrase, Uint8List salt) {
final derivator = PBKDF2KeyDerivator(HMac(SHA1Digest(), 64))
..init(Pbkdf2Parameters(salt, 1000, 32 + 16));
return derivator.process(utf8.encode(passphrase) as Uint8List);
}
With these changes, key and IV can be derived as follows:
var salt = "FA9A4D0F-5523-4EEF-B226-9A3E8F14FEF8";
var passphrase = "733D3A17-D8A0-454B-AD22-88608FD0C46A";
var saltBytes = encodeUtf16LE(salt);
var keyIv = generateKey(passphrase, saltBytes);
var key = keyIv.sublist(0, 32);
var iv = keyIv.sublist(32, 32 + 16);
The decryption code can be applied unchanged, for decoding use utf8.decode() instead of String.fromCharCodes().
import 'dart:convert';
import 'dart:typed_data';
import 'package:pointycastle/export.dart';
...
var ciphertext = "8tyHRaQCsxmmGW2xPBFYx/PALmvHkmjx/TzaXC2rIv0=";
var encrypted = base64.decode(ciphertext);
var paddingCipher = PaddedBlockCipherImpl(PKCS7Padding(), CBCBlockCipher(AESEngine()))
..init(false, PaddedBlockCipherParameters(ParametersWithIV(KeyParameter(key), iv), null));
var decryptedBytes = paddingCipher.process(encrypted);
var decrypted = utf8.decode(decryptedBytes); // C123492349C1CT20230206130645
So, we are using AES GCM encryption & decryption in nodejs as follows,
We need to use this in Java. So one can encrypt in Java and decrypt in nodeJs and vice versa.
here is encrypt decrypt function in node
const encrypt = (text, masterkey) => {
// random initialization vector
const iv = crypto.randomBytes(16);
// random salt
const salt = crypto.randomBytes(64);
// derive encryption key: 32 byte key length
// in assumption the masterkey is a cryptographic and NOT a password there is no need for
// a large number of iterations. It may can replaced by HKDF
// the value of 2145 is randomly chosen!
const key = crypto.pbkdf2Sync(masterkey, salt, 2145, 32, 'sha512');
// AES 256 GCM Mode
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
// encrypt the given text
const encrypted = Buffer.concat([
cipher.update(text, 'utf8'),
cipher.final()
]);
// extract the auth tag
const tag = cipher.getAuthTag();
// generate output
return Buffer.concat([salt, iv, tag, encrypted]).toString('base64');
};
const decrypt = (encdata, masterkey) => {
// base64 decoding
const bData = Buffer.from(encdata, 'base64');
// convert data to buffers
const salt = bData.slice(0, 64);
const iv = bData.slice(64, 80);
const tag = bData.slice(80, 96);
const text = bData.slice(96);
// derive key using; 32 byte key length
const key = crypto.pbkdf2Sync(masterkey, salt, 2145, 32, 'sha512');
// AES 256 GCM Mode
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(tag);
// encrypt the given text
return decipher.update(text, 'binary', 'utf8') + decipher.final('utf8');
};
Here's What I have done in Java, but getting input too short exception.
I have also tried to mock above nodeJs encrypt function, but it seems to be not working.
I have got initialization vector(IV), salt, key as same as nodejs (in java it is signed 128 bits)
public static String decrypt(String encData, String masterKey)
{
var cipherText = Base64.getDecoder().decode(encData.getBytes(StandardCharsets.UTF_8));
var salt = Arrays.copyOfRange(cipherText, 0, 64);
var iv = Arrays.copyOfRange(cipherText, 64, 80);
var tag = Arrays.copyOfRange(cipherText, 80, 96);
var ciphertext = Arrays.copyOfRange(cipherText, 96, cipherText.length);
var key = getKeyFromPassword(masterKey, salt);
//GCM_TAG_LENGTH = 16
return helper("AES/GCM/NoPadding", ciphertext, tag, key, new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv));
}
public static SecretKey getKeyFromPassword(String masterKey, byte[] salt) {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
KeySpec spec = new PBEKeySpec(masterKey.toCharArray(), salt, 2145, 256);
SecretKey secret = new SecretKeySpec(factory.generateSecret(spec)
.getEncoded(), "AES");
return secret;
}
public static String helper(String algorithm, byte[] cipherText, SecretKey key,
GCMParameterSpec gcmParameterSpec){
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, key, gcmParameterSpec);
cipher.update(cipherText);
byte[] plainText = cipher.doFinal(tag);
return new String(plainText, StandardCharsets.UTF_8);
}
UPDATE: above code works for decryption in java
But now need to encrypt in java
here's what i am doing, but getting Tag mismatch! exception while decryption.I also change the order of tag & cipherText but still same error occured.
public static String encrypt(String text, String masterKey)
{
var iv = generateIv(16);
var salt = generateIv(64);
var key = getKeyFromPassword(masterKey, salt);
var cipher = helper1("AES/GCM/NoPadding", text, key, new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv));
var outputStream = new ByteArrayOutputStream();
var tag = Arrays.copyOfRange(cipher, 0, 16);
var ciphertext = Arrays.copyOfRange(cipher, 16, cipher.length);
outputStream.write(salt);
outputStream.write(iv);
outputStream.write(tag);
outputStream.write(ciphertext);
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
}
public static byte[] generateIv(int N) {
byte[] iv = new byte[N];
new SecureRandom().nextBytes(iv);
return iv;
}
public static byte[] helper1(String algorithm, String input, SecretKey key,
GCMParameterSpec gcmParameterSpec){
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, key, gcmParameterSpec);
return cipher.doFinal(input.getBytes());
}
public static SecretKey getKeyFromPassword(String masterKey, byte[] salt) {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
KeySpec spec = new PBEKeySpec(masterKey.toCharArray(), salt, 2145, 256);
SecretKey secret = new SecretKeySpec(factory.generateSecret(spec)
.getEncoded(), "AES");
return secret;
}
So, I am putting Java version of encrypt & decrypt functions here
public static byte[] generateIv(int N) {
byte[] iv = new byte[N];
new SecureRandom().nextBytes(iv);
return iv;
}
public static SecretKey getKeyFromPassword(String masterKey, byte[] salt) {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
KeySpec spec = new PBEKeySpec(masterKey.toCharArray(), salt, 2145, 256);
SecretKey secret = new SecretKeySpec(factory.generateSecret(spec)
.getEncoded(), "AES");
return secret;
}
public static byte[] encryptHelper(String algorithm, String input, SecretKey key,
GCMParameterSpec gcmParameterSpec){
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, key, gcmParameterSpec);
return cipher.doFinal(input.getBytes());
}
#SneakyThrows
public static String decryptHelper(String algorithm, byte[] cipherText, byte[] tag,SecretKey key,
GCMParameterSpec gcmParameterSpec){
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, key, gcmParameterSpec);
cipher.update(cipherText);
byte[] plainText = cipher.doFinal(tag);
return new String(plainText, StandardCharsets.UTF_8);
}
public static String encrypt(String text, String masterKey)
{
var iv = generateIv(16);
var salt = generateIv(64);
var key = getKeyFromPassword(masterKey, salt);
var cipher = encryptHelper("AES/GCM/NoPadding", text, key, new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv));
var outputStream = new ByteArrayOutputStream();
var ciphertext = Arrays.copyOfRange(cipher, 0, text.length());
var tag = Arrays.copyOfRange(cipher, text.length(), cipher.length);
outputStream.write(salt);
outputStream.write(iv);
outputStream.write(tag);
outputStream.write(ciphertext);
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
}
public static String decrypt(String encData, String masterKey)
{
var cipherText = Base64.getDecoder().decode(encData.getBytes(StandardCharsets.UTF_8));
var salt = Arrays.copyOfRange(cipherText, 0, 64);
var iv = Arrays.copyOfRange(cipherText, 64, 80);
var tag = Arrays.copyOfRange(cipherText, 80, 96);
var ciphertext = Arrays.copyOfRange(cipherText, 96, cipherText.length);
var key = getKeyFromPassword(masterKey, salt);
return decryptHelper("AES/GCM/NoPadding", ciphertext, tag, key, new GCMParameterSpec(GCM_TAG_LENGTH * 8, iv));
}
I'm encrypting a JSON message and sending it to the user. When they decrypt it, some of the messages show special characters at the beginning of the messages.
When i tried to decrypt from my side it's working fine. If they are decrypting it it shows special characters, but only for some messages as some are decrypting just fine.
Below is the Java code I am using to encrypt the messages and also adding code they are using to decrypt in .NET. Please help me understand this situation and why it's happening.
JAVA CODE(Encryption):
package com.kcs.mule.encryption;
import javax.crypto.Cipher;
import org.apache.commons.codec.binary.Base64;
import org.mule.api.MuleEventContext;
import org.mule.api.lifecycle.Callable;
import java.security.AlgorithmParameters;
import java.security.SecureRandom;
import java.util.Arrays;
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 AESEncryption implements Callable {
private static final String password = "fvtQhQcKZVWMCXRLbqmRgfEBXYWshTEP";
private static int pswdIterations = 65536;
private static int keySize = 256;
private static byte[] ivBytes;
#Override
public Object onCall(MuleEventContext eventContext) throws Exception {
String plainText = eventContext.getMessageAsString();
byte[] saltBytes = password.getBytes("UTF-8");
// Derive the key
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), saltBytes, pswdIterations, keySize);
SecretKey secretKey = factory.generateSecret(spec);
SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES");
// encrypt the message
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
ivBytes = params.getParameterSpec(IvParameterSpec.class).getIV();
byte[] encryptedTextBytes = cipher.doFinal(plainText.getBytes("UTF-8"));
byte[] encryptedOutput = new byte[ivBytes.length+encryptedTextBytes.length];
System.arraycopy(ivBytes, 0, encryptedOutput, 0, ivBytes.length);
System.arraycopy(encryptedTextBytes,0,encryptedOutput,ivBytes.length,encryptedTextBytes.length);
return encryptedOutput;
}
}
DOT Net Code(Decryption code):
byte[] saltBytes = key;
PBEKeySpec keySpec = new PBEKeySpec(key, saltBytes, 65536);
var keyHash = keySpec.GetBytes(32);
using (Aes aesCrypto = Aes.Create())
{
//set the BlockSize and the KeySize before you set the Key and the IV
//to avoid padding exceptions.
aesCrypto.BlockSize = 128;
aesCrypto.KeySize = 256; // AES256
aesCrypto.Key = keyHash;
byte[] cipherTextCombined = request.MessageBytes;
byte[] IV = new byte[aesCrypto.BlockSize / 8];
byte[] cipherText = new byte[cipherTextCombined.Length - IV.Length];
Array.Copy(cipherTextCombined, IV, IV.Length);
Array.Copy(cipherTextCombined, IV.Length, cipherText, 0, cipherText.Length);
aesCrypto.IV = IV; //Initialization vector
aesCrypto.Mode = CipherMode.CBC; //Cipher Block Chaining mode
aesCrypto.Padding = PaddingMode.PKCS7;
// Create a decryptor to perform the stream transform.
ICryptoTransform decryptor = aesCrypto.CreateDecryptor(aesCrypto.Key, aesCrypto.IV);
// Create the streams used for decryption.
using (var msDecrypt = new MemoryStream(cipherText))
{
using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (var srDecrypt = new StreamReader(csDecrypt, new UTF8Encoding(false)))
{
// Read the decrypted bytes from stream to string.
response.MessageText = srDecrypt.ReadToEnd();
}
}
}
}
}
Actual result:
����wߞ*�/�r5le": {
"System": "KCS",
"Train": {
"TrainNumber": "36181542",
"TrainID": "G-CDMY -26",
Expected Result:
TrainSchedule: {
"System": "KCS",
"Train": {
"TrainNumber": "36181542",
"TrainID": "G-CDMY -26",
It's possible You are not encrypting the string "TrainSchedule" but they are decrypting TrainSchedule along with the payload.
Either encrypt the header or tell them to separate the payload from the header before decrypting it and concatenate it after.
Or, they are only decrypting the payload and ignoring the header. Without looking at their code it's impossible to know.
I'm trying to replicate a Java-based encryption scheme in Node.js but unfortunately I'm getting inconsistent results.
Here's the Java method:
private Transfer encrypt(byte[] salt, String ticketNumber, String data) throws Exception {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(ticketNumber.toCharArray(), salt, 1000, 128);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
String encoded = java.util.Base64.getEncoder().encodeToString(secret.getEncoded());
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
byte[] iv ={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
IvParameterSpec ips = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, secret, ips);
AlgorithmParameters params = cipher.getParameters();
Transfer myRetVal = new Transfer();
byte[] ivBytes = params.getParameterSpec(IvParameterSpec.class).getIV();
//Set some decrypt information
myRetVal.setIv(Base64.encodeBase64String(ivBytes));
//Set the attendee data
myRetVal.setData(Base64.encodeBase64String(cipher.doFinal(data.getBytes())));
//Set the hashed Ticket number
myRetVal.setTicketNumberHashed(Base64.encodeBase64String(getHash(hashIterations, ticketNumber, salt)));
return myRetVal;
}
And my Node version:
exports.getEncryptedString = function(salt, password, data) {
var iv = new Buffer('0000000000000000');
var key = crypto.pbkdf2Sync(password, salt, 1000, 16, 'sha1');
var cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
return cipher.update(data, 'utf8', 'base64') + cipher.final('base64');
};
When I pass both functions the string "SomeJSON" and the same key I get different encrypted results.
From Java: ENnQzWowzrl7LQchRmL7sA==
From Node: TGreJNmQH92gHb1bSy4xAA==
I can't figure out what is different in my Node implementation.
new Buffer('0000000000000000') uses utf8 encoding by default, but "0" in UTF-8 is the byte 0x30 whereas in Java you're using 0x00 bytes for the IV. What you want is either
var iv = new Buffer('00000000000000000000000000000000', 'hex');
or
var iv = new Buffer(16);
iv.fill(0);
After you've done your tests, you should change the procedure to generate a new IV for every encryption. The IV doesn't have to be secret, so you can simply prepend it to the ciphertext. When you want to decrypt it later, you can slice the IV off (16 bytes for AES) and use it during decryption.
I am having trouble mapping the following JDK JCE encryption code to Bouncy Castles Light-weight API:
public String dec(String password, String salt, String encString) throws Throwable {
// AES algorithm with CBC cipher and PKCS5 padding
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "BC");
// Construct AES key from salt and 50 iterations
PBEKeySpec pbeEKeySpec = new PBEKeySpec(password.toCharArray(), toByte(salt), 50, 256);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithSHA256And256BitAES-CBC-BC");
SecretKeySpec secretKey = new SecretKeySpec(keyFactory.generateSecret(pbeEKeySpec).getEncoded(), "AES");
// IV seed for first block taken from first 32 bytes
byte[] ivData = toByte(encString.substring(0, 32));
// AES encrypted data
byte[] encData = toByte(encString.substring(32));
cipher.init( Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec( ivData ) );
return new String( cipher.doFinal( encData ) );
}
The above works great, but is not very portable due to Oracle's restriction on encryption strengths. I've made several attempts at porting to Bouncy Castles Light-weight API but without success.
public String decrypt1(String password, String salt, String encString) throws Exception {
byte[] ivData = toByte(encString.substring(0, 32));
byte[] encData = toByte(encString.substring(32));
PKCS12ParametersGenerator gen = new PKCS12ParametersGenerator(new SHA256Digest());
gen.init(password.getBytes(), toByte(salt), 50);
CBCBlockCipher cbcBlockcipher = new CBCBlockCipher(new RijndaelEngine(256));
CipherParameters params = gen.generateDerivedParameters(256, 256);
cbcBlockcipher.init(false, params);
PaddedBufferedBlockCipher aesCipher = new PaddedBufferedBlockCipher(cbcBlockcipher, new PKCS7Padding());
byte[] plainTemp = new byte[aesCipher.getOutputSize(encData.length)];
int offset = aesCipher.processBytes(encData, 0, encData.length, plainTemp, 0);
int last = aesCipher.doFinal(plainTemp, offset);
byte[] plain = new byte[offset + last];
System.arraycopy(plainTemp, 0, plain, 0, plain.length);
return new String(plain);
}
The above attempt results in a org.bouncycastle.crypto.DataLengthException: last block incomplete in decryption.
I have searched for examples online, but there isn't many examples of providing your own IV data for 256bit AES with CBC using PKCS5/PKCS7 as padding.
NB: The toByte function converts a String into a byte array using base64 or similar.
This should work for you:
public String dec(String password, String salt, String encString)
throws Exception {
byte[] ivData = toByte(encString.substring(0, 32));
byte[] encData = toByte(encString.substring(32));
// get raw key from password and salt
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(),
toByte(salt), 50, 256);
SecretKeyFactory keyFactory = SecretKeyFactory
.getInstance("PBEWithSHA256And256BitAES-CBC-BC");
SecretKeySpec secretKey = new SecretKeySpec(keyFactory.generateSecret(
pbeKeySpec).getEncoded(), "AES");
byte[] key = secretKey.getEncoded();
// setup cipher parameters with key and IV
KeyParameter keyParam = new KeyParameter(key);
CipherParameters params = new ParametersWithIV(keyParam, ivData);
// setup AES cipher in CBC mode with PKCS7 padding
BlockCipherPadding padding = new PKCS7Padding();
BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(
new CBCBlockCipher(new AESEngine()), padding);
cipher.reset();
cipher.init(false, params);
// create a temporary buffer to decode into (it'll include padding)
byte[] buf = new byte[cipher.getOutputSize(encData.length)];
int len = cipher.processBytes(encData, 0, encData.length, buf, 0);
len += cipher.doFinal(buf, len);
// remove padding
byte[] out = new byte[len];
System.arraycopy(buf, 0, out, 0, len);
// return string representation of decoded bytes
return new String(out, "UTF-8");
}
I assume that you're actually doing hex encoding for toByte() since your code uses 32 characters for the IV (which provides the necessary 16 bytes). While I don't have the code you used to do the encryption, I did verify that this code will give the same decrypted output as your code.