I'm writing a system, where a user can write something (via. mobile browser), and that "String" will be encrypted with a password, chosen by the user. Since unicode emojis are often used, they have to be supported too.
As lib for the crypto, I choose CryptoJs - so that the crypto can be done local on the devices.
Currently, when I encrypt a string, and decrypt the same sting, all emojis disappear/ are replaced with random chars.
var key = "123";
var content = "secret text with an emoji, 🎮";
var encrypted = aes_encrypt(key, content); //U2FsdGVkX19IOHIt+eRkaOcmNuZrc1rkU7JepL4iNdUknzhDaLOnSjYBCklTktSe
var decrypted = aes_decrypt(key, encrypted);//secret text with an emoji, Ø<ß®
I'm using a pair of helper functions like this:
function aes_encrypt(key, content){
var key_string = key + "";
var content_string = ascii_to_hex(content) + "";
var key_sha3 = sha3(key_string);
var encrypted = CryptoJS.AES.encrypt(content_string, key_sha3, {
mode: CryptoJS.mode.CTR, padding: CryptoJS.pad.Iso10126});
return encrypted + "";
};
Can anybody please tell me what I'm doing wrong?
Warning: It is extremely difficult to get cryptographic code right. It can be even harder in JavaScript, where you often lack control over the execution environment and (as discussed below) a lack of language support has led to inconsistent conventions. I have not done enough research about the CryptoJS library to know about its design or security, or whether it is being used safely in this context.
Please do not rely on any of this code to be genuinely secure without a professional audit.
A common issue when working with cryptographic code in JavaScript has been that there was no built-in way to represent binary data. This has been resolved in modern engines (with types Blobs
and TypedArrays
in the browser and Buffers
in Node.js), but there is still a lot code that doesn't take advantage of this for historical or compatibility reasons.
Without these built-in types, one common convention (used by the built-in atob
and btoa
functions) is to use the built-in string type to hold binary data. A JavaScript string is really a list of two-byte values (usually containing UCS-2/UTF-16-encoded Unicode characters). Users wanting to store binary data will often just use the lower byte, ignoring the higher byte entirely.
If you're only handling ASCII-compatible data, you might get away with ignoring these details when using code like this (i.e. things will work -- but there may be subtle security consequences). This is because text encoded as ASCII looks the same as text encoded as UTF-16 with the high bytes stripped out. But when you venture beyond that, you need to do some encoding.
The most correct thing (aside from using a real binary type) to do would be to take the input string of characters, encode it to UTF-8, and put that data in the lower bytes of an output string. However, JavaScript doesn't provide a built-in function to do that. As a crude but simple alternative, the encodeURIComponent
function will encode any valid unicode string into a UTF-8 based representation of entirely URL-safe characters, which are all ASCII-compatible. In the case of your code, that would mean something like this:
var key = "123";
var content = "secret text with an emoji, 🎮";
var encrypted = aes_encrypt(key, encodeURIComponent(content));
var decrypted = decodeURIComponent(aes_decrypt(key, encrypted));
If you have a lot of non-URL-safe characters, this could result in the encoded data being much larger than necessary, but it should be safe. Also, encodeURIComponent
will apparently throw an error for strings that contain "unpaired surrogate characters". I don't think these should occur in ordinary input, but someone could craft them.
I expect that there is a more-correct way to handle things like this in CryptoJS, but I am unaware of it. Please consider looking into this further if you're planning to deploy this code for public use.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With