As a newbie to Cryptography, I’m trying to reproduce the same default behavior of the AES256TextEncryptor Class of the jasypt-library with the CrpytoJS library. This is my Java method, that basically takes in two arguments – the message that I want to encrypt as well as my secret paraphrase:
private String encryptWithAes256(String messageToBeEncrypted, String encryptorSecret) { AES256TextEncryptor encryptor = new AES256TextEncryptor(); encryptor.setPassword(encryptorSecret); return encryptor.encrypt(messageToBeEncrypted); }
When encrypting the messageToBeEncrypted with this code, the resulting encrypted message is fine. What I found out is that the AES256TextEncryptor, which internally uses the StandardPBEStringEncryptor as a encryptor, seems to use the PBEWithHMACSHA512AndAES_256 algorithm as a default.
How can I reproduce the same encryption behavior with CrpytoJS? When I’m trying to encrypt the message with CryptoJS in the way it’s documented here, the result is totally different from what I expect it to be.
Based on Topaco’s comment, I came up with the following JavaScript Code to mimic the Java code:
function encryptWithAes256(messageToEncrypt, encryptorKey){ // Generate random 16 bytes salt var salt = CryptoJS.lib.WordArray.random(128/8); // Derive key var key = CryptoJS.PBKDF2(encryptorKey, salt, { keySize: 256/32, iterations: 1000 }); console.log("derived key: " + key); // Generate random 16 bytes init vector (iv) var iv = CryptoJS.lib.WordArray.random(128/8); var cipherText = CryptoJS.AES.encrypt(messageToEncrypt, key, {iv: iv}); console.log("aes encrypted text: "+ salt.toString() + iv.toString() + cipherText.toString()); }
The generated result still seems not be as expected though, as it’s length is 88 characters, whereas the Java code generates a 64 character long encrypted message.
Advertisement
Answer
The posted code is close to the required result. The following still needs to be corrected:
- PBKDF2 applies SHA1 by default, which means SHA512 must be explicitly specified.
- The concatenation must be done on a binary level and not with the hex and Base64 encoded data.
If this is fixed, a possible implementation is:
function encryptWithAes256(messageToEncrypt, encryptorKey){ // Generate random 16 bytes salt var salt = CryptoJS.lib.WordArray.random(128/8); // Derive key var key = CryptoJS.PBKDF2( encryptorKey, salt, { keySize: 256/32, iterations: 1000, hasher: CryptoJS.algo.SHA512 } // Apply SHA512 ); console.log("derived key:n" + key); // Generate random 16 bytes init vector (iv) var iv = CryptoJS.lib.WordArray.random(128/8); // Encrypt var cipherText = CryptoJS.AES.encrypt(messageToEncrypt, key, {iv: iv}); // Concatenate var encryptedData = salt.clone().concat(iv).concat(cipherText.ciphertext); // Concatenate on binary level var encryptedDataB64 = encryptedData.toString(CryptoJS.enc.Base64); // Base64 encode the result console.log("aes encrypted text:n", encryptedDataB64.replace(/(.{56})/g,'$1n')); } encryptWithAes256('The quick brown fox jumps over the lazy dog', 'my passphrase');
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
Since because of the random salt and IV always different data is generated, a test of the implementation is not possible by comparing the data. Instead, it must be checked whether the data generated with the CryptoJS code is decryptable with the Jasypt counterpart for decryption:
private static String decryptWithAes256(String ciphertextToBeDecrypted, String encryptorSecret) { AES256TextEncryptor encryptor = new AES256TextEncryptor(); encryptor.setPassword(encryptorSecret); return encryptor.decrypt(ciphertextToBeDecrypted); }
which is indeed the case with the above CryptoJS implementation.