I'd like to share some interesting findings I made while implementing a cross platform crypto format.
Usually you will need these features from a crypto implementation:
- Random data generation
- Checksum and HMAC calculations, like using
- Key derivation from a password, like using
- Encryption and decryption, like using
This is a pretty basic set of requirements and most crypto implementations support these. But as always the devil is in the details, because if you make a small mistake you will just get a wrong result and this result will not help you find the cause of the bug. It is wrong or it is right. Basta.
So cryptography is applied on raw binary data. This may already be the source of troubles:
- Did you use the correct encoding, like
- Is the implementation of your
Hexhelpers correctly working?
- Is there a good way to concatenate data or slice it?
On iOS and macOS you will usually use
Buffer which is quite nice, but on Web you will find yourself using
Uint8Array pretty soon, which has no nice native toolset for converting between UTF-8 strings, hex and base64 presentations.
IV and Salt
Random data makes it harder for an attacker to crack encrypted data and passwords, so using initialization vectors (IV) for encryption and a salt for password generation is a good practice.
But IV support on node.js is totally broken and ends in an exception for certain circumstances. I was pretty surprised about that, but I was not able to find a workaround, which made me switch to WebCryptography.
Another trap you can fall into eventually while testing is exactly this randomness of IV and salt if you forget to preset those in your test cases, because the result obviously will always differ if different random elements are created. Sounds obvious, but will happen ;)
You will read a lot about which algorithms are the best, but in real life you'll end up with taking what is there and implemented on all platforms you are targeting.
SHA512 are standard. For keyword derivation you will usually only find support for
PBKDF2 everywhere. For symmetric encryption the standard is
AES. But it comes with different flavors. I found
CBC was a good compromise that is available almost everywhere.
CTR is also pretty widely supported.
Package the Secret
Ok, you now have what you need and start happy encryption. It would be great, if all you need were self-contained, but usually it is not. You will need to store the salt somewhere to rebuild your key and you will also need the IV to decrypt your data.
So let's store the IV with the encrypted data, like:
IV + cipherText = package.
But we should also make sure the data cannot be manipulated, therefore we add an
HMAC over the data of the previous package and add it as well:
IV + cipherText + HMAC = package.
That's nice. Now you can use the handy tools I mentioned in Binary Data to deconstruct it for decryption ;)
I'm not a crypto expert and I'll be very happy to hear your feedback about it e.g. on Twitter @holtwick.