marios390 marios390 - 2 months ago 40
Javascript Question

CryptoJs's decrypt method returns an empty string

I am trying to encrypt/decrypt using AES256 using Java for encryption and CryptoJS for decryption. Encryption is tested in Java is working fine but the decryption method in JavaScript is returning an empty string. Please note in order to test JavaScript I printed out in tmp file the values for data, IV and salt and then hardcoded in JS. (Note: format in file is: data (byte[] base64) , Iv(string base64) and salt(string base64) ).

Here is the code in java:

public byte[] encrypt(String plainText) throws Exception {
//get salt
salt = generateSalt();
byte[] saltBytes = salt.getBytes("UTF-8");

// Derive the key
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
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"));
return Base64.encode(encryptedTextBytes);
}


what is wrong with the decryption code in JavaScript below?

// the password that user provides
var userPass = document.getElementById("password").value;
console.log("user pass : " + userPass);
// hash contains 5 bytes
var hashedPass = CryptoJS.SHA1(userPass);
console.log("hashed pass : " + hashedPass.toString(CryptoJS.enc.Base64) + " | array length " + hashedPass.words.length + " | " + typeof(hashedPass));
// use only 4 bytes (128 bits) from the hashed pass
// (same as used in java when encrypting)
/////////////////////////var hashed4bytes = CryptoJS.lib.WordArray.create(hashedPass.words.slice(0,4));
//console.log( "hashed4bytes encoded 64 = " + hashed4bytes.toString(CryptoJS.enc.Base64));

// get the encrypted msg
var encMsg64 = document.getElementById("themessage").innerHTML;
encMsg64 = encMsg64.toString( CryptoJS.enc.Base64);
//var encMsg = CryptoJS.enc.Base64.parse(encMsg64);
var salt =CryptoJS.enc.Base64.parse("EAWnOgxUDuvhWqrSUsugq1umMpI=");
var iv =CryptoJS.enc.Base64.parse("xWpmXNbmbFjmWBUajuWYXQ==");
//var salt = "EAWnOgxUDuvhWqrSUsugq1umMpI=";
//var iv = "xWpmXNbmbFjmWBUajuWYXQ==";
console.log('salt '+ salt );
console.log('iv '+ iv );


var key = CryptoJS.PBKDF2(hashedPass, salt, { keySize: 256/32, iterations: 1000 });
console.log( 'key '+ key);

var decText = '';
var ok = true;
try {
debugger;
var decMsg = CryptoJS.AES.decrypt( encMsg64, key, {
iv:iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
} );
console.log( "decryptedData = " + decMsg );

// convert to UTF8 string
decText = decMsg.toString( CryptoJS.enc.Utf8 );
console.log( "decryptedText = " + decText );

if (decText == '') {
ok = false;
}
}
catch (e) {
//console.log("Error when decrypting: " + e.message)
ok = false;
}


after mafe the changed issue still persists
Here is complete code after the change


JAVA

public class AES256EncryptionServiceBean implements EncryptionService {

private static final Logger LOGGER = LoggerFactory
.getLogger(AES256EncryptionServiceBean.class);
private String salt = null; //get bytes out of UTF-8 for decryption
private static final int PSWDITERATIONS = 1000;//65536;
private static final int KEYSIZE = 256;
private static final String AES_ALGO = "AES";
private static final String SHA1_ALGO = "PBKDF2WithHmacSHA1";
private static final String AES_CBC_PKCS5_TRANSFORM = "AES/CBC/PKCS5Padding";
private byte[] Iv;

/**
* Encrypts the data with AES-256 algorithm Encrypted data will be encoded
* with base64 algorithm and the returned. Initial vector is being used
* during encryption along with CBC encryption mode.
*
* output format: [algo indicator(1char)][Initialization vector()][salt()][encoded data(variable size)]
*/
@Override
public byte[] encrypt(String password, byte[] data) throws PibException {
byte[] encodedData = null;
try {
byte[] encryptedData = encryptCBC256Bits(password, data);
encodedData = Base64.encodeBase64(encryptedData);
/*String finalStr=null;
String algo256 = "2";
String datastr = Base64.encodeBase64String(encryptedData);
String ivstr = new String(Iv);
finalStr = algo256 +ivstr+salt+datastr;

encodedData = finalStr.getBytes();
*/
} catch (Exception e) {
throw ExceptionFactory.createPibException(
MessageCodes.PIB_ENCRYPTION_FAILED, e, LOGGER);
}
return encodedData;
}

/**
* Encrypts the input data with AES CBC transformation using 256 bits (32
* bytes) Key is generated based on the provided password and random salt.
* Salt is the extra bits added to the password to ensure every key is
* unique SHA1 hashing is also participate in key generation.
*
* @throws PibException
*
*/
private byte[] encryptCBC256Bits(String password, byte[] data)
throws PibException {

salt = generateSalt();
byte[] saltBytes = salt.getBytes(StandardCharsets.UTF_8);
byte[] encryptedTextBytes = null;

// Derive the key

try {
SecretKeyFactory factory = SecretKeyFactory.getInstance(SHA1_ALGO);
// Password based key specification
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), saltBytes,
PSWDITERATIONS, KEYSIZE);
SecretKey secretKey = factory.generateSecret(spec);
SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(),
AES_ALGO);

// encrypt the data
Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5_TRANSFORM);
// SecureRandom random = new SecureRandom();
// byte[] ivTemp = new byte[16];
// random.nextBytes(ivTemp);
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
Iv = params.getParameterSpec(IvParameterSpec.class).getIV();
encryptedTextBytes = cipher.doFinal(data);

} catch (NoSuchAlgorithmException | InvalidKeySpecException
| NoSuchPaddingException | InvalidKeyException
| InvalidParameterSpecException | IllegalBlockSizeException
| BadPaddingException e) {
throw ExceptionFactory.createPibException(
MessageCodes.PIB_ENCRYPTION_FAILED, e, LOGGER);
}

return encryptedTextBytes;
}

private String generateSalt() {

SecureRandom random = new SecureRandom();
byte bytes[] = new byte[20];
random.nextBytes(bytes);
String s = new String(bytes);
return s;

}

public String getSalt() {
return salt;
}

public byte[] getIv() {
return Iv;
}

}


Javascript

function decryptMsg256() {

// the password that user provides
var userPass = document.getElementById("password").value;
console.log("user pass : " + userPass);


// get the encrypted msg
var encMsg64 = document.getElementById("themessage").innerHTML;
var encMsg = CryptoJS.enc.Base64.parse(encMsg64);
var salt =CryptoJS.enc.Utf8.parse("?E€O5?…°®I^y??O:n");
var iv =CryptoJS.enc.Utf8.parse("S;Ui?¨=ENzI—$");

console.log('salt '+ salt );
console.log('iv '+ iv );


var key = CryptoJS.PBKDF2("password", salt, { keySize: 256/32, iterations: 1000 });
console.log( 'key '+ key);

var decText = '';
var ok = true;
try {
debugger;
var decMsg = CryptoJS.AES.decrypt( encMsg, key, {
iv:iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
} );
console.log( "decryptedData = " + decMsg );

// convert to UTF8 string
decText = decMsg.toString( CryptoJS.enc.Utf8 );
console.log( "decryptedText = " + decText );

if (decText == '') {
ok = false;
}
}
catch (e) {
//console.log("Error when decrypting: " + e.message)
ok = false;
}


I can not understands what is wrong please help
CipherText,Salt and Iv is retrieved as follows:

public void testEncryption_WriteToFile() throws Exception {

byte[] data = IOUtils.toByteArray(this.getClass().getClassLoader()
.getResourceAsStream(SOME_FILE_NAME));

byte[] encryptedData = this.encryptionService.encrypt(PASSWORD, data);
byte[] initial_vector = ((AES256EncryptionServiceBean) encryptionService)
.getIv();
String salt = ((AES256EncryptionServiceBean) encryptionService)
.getSalt();


IOUtils.write(encryptedData, new FileOutputStream(
"C:\\Temp\\data.encrypted"));
/*IOUtils.write(new String(encryptedData), new FileOutputStream(
"C:\\Temp\\data[byte32string].encrypted"));
*/
IOUtils.write(Base64.encodeBase64String(salt.getBytes(StandardCharsets.UTF_8)), new FileOutputStream(
"C:\\Temp\\salt.encrypted"));
/*IOUtils.write(salt.getBytes(StandardCharsets.UTF_8), new FileOutputStream(
"C:\\Temp\\salt.encrypted"));
*/
IOUtils.write(Base64.encodeBase64String(initial_vector), new FileOutputStream(
"C:\\Temp\\iv.encrypted"));
/*IOUtils.write(initial_vector, new FileOutputStream(
"C:\\Temp\\iv.encrypted"));*/


}

Answer

CryptoJS.PBKDF2 uses SHA1 by default. So as long as the same password, salt, keysize and iteration count is supplied, it will produce the same key. The problem is that in JavaScript you additionally hash the password with SHA1. Don't do that and pass the password directly into PBKDF2 in the same way you do this in Java.

The second problem is that the ciphertext should be in the native format of CryptoJS when trying to decrypt. Since you get the base 64 encoded ciphertext from Java, you have to decode it as such. Uncomment the line:

var encMsg = CryptoJS.enc.Base64.parse(encMsg64);

and don't do encMsg64 = encMsg64.toString( CryptoJS.enc.Base64); since this will encode the already encoded ciphertext again.


For the updated code, you cannot print your key and salt simply as a string and expect it to work in JavaScript. Those are byte[] for a reason. They contain unprintable characters which will be lost when you try to parse it in JavaScript. You have to encode all the byte[] values that you want to transport from Java to JavaScript as Base64 and then decode them in JavaScript.

Comments