Maybe you followed my series on symmetric encryption with AES in PHP. You can check it out here if you’re interested! What if I told you, that you don’t even need a server to do that but might as well encrypt text using AES right in your browser? Sounds crazy? Well, you’re in for a treat!
I can’t stress this enough, so here I go again: when it comes to cryptography, there are two main rules:
Stick to standards wherever possible!
Don’t do it unless you know what you’re doing!
You can download the project here. There really are plenty of standards included in the library, such as MD5, HMAC and the entire SHA-family. We don’t need all those files though. To keep our project clean, we just include the ones we actually use. These are:
We’re going to build a helper class for using the provided functionality a little more conveniently.
One important thing about CryptoJS: it works with ‘Words’ or ‘WordArrays’ in some places. Words are 32bit long, calling for conversion here and there. So everytime you see a ’32’ in the code, it’s because of that.
Here’s the base for this project:
Just like in the PHP-version we’re defining the size of blocks and the key as well as the iteration count. I chose the same values as we did for PHP to have the output transferable inbetween platforms. Note that blockSize and keySize are set in Bits, not Bytes as we did in PHP!
Let’s start with encryption. I tried to keep the structure of the code similar to the PHP version to make it easier to follow. I’m not going to cover all the details of what’s happening – you can read about that here.
CryptoJS provides its own method to get random data for our IV and salt. Unfortunately, it takes its values in bytes, so we convert.
Generating a key
The code for generating a key is a bit bulky and we’re going to need it again when decrypting, so I put it in a seperate function to keep things clean:
Using PBKDF2 with SHA1 as hashing algorithm to generate the key. Be aware that the keySize is expected in ‘Words’!
CryptoJS’ encrypt() method also requires quite some parameters. Note that we need to explicitly name what padding we want to be applied (unlike in PHP where PKCS5 was used by default). Also note that PKCS5 and PKCS7 are interchangeable, so encrypting with one and decrypting with the other works just fine.
The returned ciphertext will be an array of Words.
To be able to safely transfer the encrypted ciphertext around the web, we’re going to encode it using a modified version of Base64. But since IV, salt and ciphertext are WordArrays, we need to convert them first.
This is easy regarding the ciphertext as it comes with a built-in ‘toString()’ function that returns the Base64-encoded version of it. For IV and salt we first have to convert them to Base64 by hand.
To get raw data from all of the three, we decode them using atob(). This raw data we pass to our base64UrlEncode:
This replaces ‘+’ with ‘-‘ and ‘/’ with ‘_’ to avoid problems in URLs.
Signing our resulting encoded IV, salt and ciphertext for integrity is easy in CryptoJS:
This gives us a SHA256-HMAC for our Base64-string that we append to it separated by a ‘:’ for easy extraction later.
Crypto.encrypt() and you’re done! Here’s an example markup: