I stumbled upon the problem that the IV value that you pass to CryptoJS.AES.encrypt function seems to be ignored. Following code:
import CryptoJS from "crypto-js";
const text = "This is secret text!";
const key = "abc";
const iv = CryptoJS.lib.WordArray.random(2);
const encryptedText = CryptoJS.AES.encrypt(text, key, { iv }).toString();
console.log(encryptedText);
const decryptedText = CryptoJS.AES.decrypt(encryptedText, key, { iv: 'whatever' }).toString(CryptoJS.enc.Utf8);
console.log(decryptedText);
Produces following output:
U2FsdGVkX1//3KxGM2FnVV4qR5KmWBxP/xyI1+YITvdQeXqrUBH7spIvi/Ny+7S2
This is secret text!
So it seems the function is able to decrypt the text no matter what IV value you are passing to the function.
What am I doing wrong here?
You can specify any IV in your code when encrypting and decrypting, because the IV is simply not used. Why is that?
Depending on the type of the key material, CryptoJS uses a key (if a WordArray is passed) or a password (if a string is passed). You pass a string, so the key material is interpreted as password (which is why key should better be called password). In this case, CryptoJS uses a key derivation function (EVP_BytesToKey()) to derive the key and the IV. The explicitly passed IV is completely ignored. This applies to both encryption and decryption. Therefore, if a password is applied, no IV should be used (and the one in the code should be removed).
The derived key and IV can be picked from the CipherParams object returned by CryptoJS.AES.encrypt() (s. here): The key can be read from the property key, the IV from the property iv and the ciphertext from the property ciphertext, s. here.
When you pass a key instead of a password, you must also pass an IV (otherwise an error message is displayed). If you specify a wrong IV during decryption, this will result in a corrupted first block for CBC (the default mode). The details are described in the other answer.
If you want to check this, you should use a Latin1 decoding (or hex encoding) instead of a UTF-8 decoding for the decrypted data, because the corrupted data is generally not Utf-8 compatible (and CryptoJS will display an error message).
Note that short plaintexts with a ciphertext of only one block in size will generally corrupt the padding, which in turn corrupts the data during unpadding, making the data non-displayable. However, it is possible to display the padded data by disabling the default PKCS#7 padding: padding: CryptoJS.pad.NoPadding).
The following code shows encryption/decryption with a password (1), with a key and IV (2) and the corruption of the first block for CBC when decrypting with a wrong IV (3):
var text = "The quick brown fox jumps over the lazy dog";
// (1) Encryption/Decryption using a password (key and IV implicitly derived via EVP_BytesToKey)
var password = "abc"; // This is a password, not a key!
var iv = CryptoJS.lib.WordArray.random(16);
var encryptedOpenSSLB64 = CryptoJS.AES.encrypt(text, password, {iv: iv}).toString(); // iv ignored (since derived via key derivation) and should be removed!
var decrypted = CryptoJS.AES.decrypt(encryptedOpenSSLB64, password, {iv: iv}); // iv ignored (since derived via key derivation) and should be removed!
console.log(encryptedOpenSSLB64);
console.log(decrypted.toString(CryptoJS.enc.Utf8));
// (2) Encryption/Decryption using a key and an IV
var key = CryptoJS.lib.WordArray.random(32);
var iv = CryptoJS.lib.WordArray.random(16);
var ciphertextB64 = CryptoJS.AES.encrypt(text, key, {iv : iv}).toString(); // iv required!
var decrypted = CryptoJS.AES.decrypt(ciphertextB64, key, {iv : iv}); // iv required!
console.log(ciphertextB64);
console.log(decrypted.toString(CryptoJS.enc.Utf8));
// (3) Corruption of the first block for CBC, if a wrong IV is applied during decryption
var wrongIv = CryptoJS.lib.WordArray.random(16);
var decrypted = CryptoJS.AES.decrypt(ciphertextB64, key, {iv : wrongIv}); // wrong IV corrupts first block for CBC
console.log(decrypted.toString(CryptoJS.enc.Latin1));
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
For completeness: In your code the CipherParams object is converted to the Base64 encoded OpenSSL format with toString(). The OpenSSL format consists of the ASCII encoding of Salted__ followed by an 8 bytes salt and the actual ciphertext. Because of the constant prefix Salted__ the Base64 encoded data always starts with U2FsdGVkX1 (like your ciphertext).
The random salt is generated during encryption. Based on salt and password, the key derivation function is used to derive key and IV. In order to be able to reconstruct key and IV during decryption, the salt is required. The salt is not secret, so it is usually concatenated with the ciphertext (as in the OpenSSL format described above). The salt is also contained in the salt property of the CipherParams object.
If no password but a key is applied, there is no salt and toString() returns only the Base64 encoding of the ciphertext property. This is also illustrated in the above code snippet.
CryptoJS.AES.decrypt() requires a CipherParams object (s. here). Alternatively (as in your code) the Base64 encoding created with toString() can be passed. This is implicitly converted into a CipherParams object.
CryptoJS is using AES-CBC. CBC is not an authenticated mode of operation, so it doesn't offer message integrity / authenticity. CBC only offers confidentiality, and only that while it is used correctly and doesn't allow for, for instance, padding oracle attacks.
Actually, the IV in CBC mode only changes the first block of plaintext during decryption, and only those bytes where the IV differs. So all the rest of the blocks simply decrypt as you'd expect. This is because the IV is only the initialization vector. The other "vectors" that are XOR'ed with the plaintext before AES block encryption are the previous blocks of ciphertext. Probably best to have a look at the Wikipedia page explaining the modes of operation.
Beware that the line:
const encryptedText = CryptoJS.AES.encrypt(text, key, { iv }).toString();
is not correct: iv should be a named field:
const encryptedText = CryptoJS.AES.encrypt(text, key, { iv: iv }).toString();
It's not entirely clear if this is deliberate or not from the rest of the context in the question.
I want decode an encrypted string using cryptoJS. I get how to decode the encrypted object but couldnt understand how to decrypt the string.
Heres what I tried:
var enc = CryptoJS.AES.encrypt('hellloooo', 'secretpassphrase');
console.log('encrypted', enc.salt.toString());
console.log('decrypted', CryptoJS.AES.decrypt(CryptoJS.enc.salt.parse(enc.salt.toString()), 'secretpassphrase').toString(CryptoJS.enc.Utf8));
The salt is some random value that is randomly generated during encryption in order to derive the actual key and IV from the given password. It doesn't hold the secret, so trying to decrypt it won't give you anything useful.
Here are two ways to decrypt the ciphertext
CryptoJS.AES.decrypt(enc, 'secretpassphrase').toString(CryptoJS.enc.Utf8);
CryptoJS.AES.decrypt(enc.toString(), 'secretpassphrase').toString(CryptoJS.enc.Utf8);
The salt is still present in the enc object, so the decrypt() function can use it to recreate the key and IV to decrypt the string.
I am having issues in turning Texture2D type image to bytes and then to string. When I do the following:
var myTextureBytes : byte[] = myTexture.EncodeToPNG();
Debug.Log(System.Text.Encoding.UTF8.GetString(myTextureBytes));
I just get a log output of "�PNG". Why is it so short? Whats the question mark? Shouldn't Unity be able to interpret UTF-8 chars? Also when I send that to my NodeJS server it says SyntaxError: Unexpected token and crashes the server.
the problem is that the bytes of PNG representation of the texture is not UTF-8 encoded, which is only for text.
To convert binary data to a string I would recommend base64 encoding.
var myTextureBytes : byte[] = myTexture.EncodeToPNG();
var myTextureBytesEncodedAsBase64 : String = System.Convert.ToBase64String(myTextureBytes);
have you tried using Default encoding?
Debug.Log(System.Text.Encoding.Default.GetString(myTextureBytes));
I have a code snippet on javascript that I'm trying to convert to python
var cipherAlgorithm = 'aes256';
var decipher = crypto.createDecipher(cipherAlgorithm, cipher);
password = decipher.update(encryptedpass, 'hex', 'utf8')+ decipher.final('utf8');
I'm trying to rewrite that in python using pythoncrypto and I'm just getting the wrong values no matter what I do
cyphertext=faaafaaa"
cipher=AES.new(key, AES.MODE_CBC, "\0"*16)
cipher.decrypt(cypertext)
and it returns the wrong value. (didn't decrypt correctly)
in the javascript code (I'm not a js expert ) I noticed that the output encoding is utf8
so I tries something like this
unicode(cipher.decrypt(cyphertext), "utf-8")
but I'm getting an error
'utf8' codec can't decode byte 0x81 in position 6: invalid start byte
what's the solution?
i am new in SJCL crypto library, i am doing the following for encrypting the plain text using 256 bit key in
var h = sjcl.codec.hex ;
salt = h.fromBits(sjcl.random.randomWords('10','0'));
var encryptedMessage = sjcl.encrypt(password,message,{count:2048,salt:salt,ks:256});
but i am unable to decrypt the same cipher , i want to know how to decrypt this cipher .
well after so many hit and try i found this line working for me.
sjcl.decrypt(password,encMessage,{count:2048,ks:256});