Implementing Crypto
I'd like to share some interesting findings I made while implementing a cross platform crypto format.
Basics
Usually you will need these features from a crypto implementation:
- Random data generation
-
Checksum and HMAC calculations, like using
SHA2
-
Key derivation from a password, like using
PBKDF2
-
Encryption and decryption, like using
AES
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.
Binary Data
So cryptography is applied on raw binary data. This may already be the source of troubles:
-
Did you use the correct encoding, like
UTF-8
? -
Is the implementation of your
Base64
andHex
helpers correctly working? - Is there a good way to concatenate data or slice it?
On iOS and macOS you will usually use NSData
which is fine. In the Javascript world you will soon be in trouble. If your target is node.js you can 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 ;)
Choosing Algorithms
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.
For checksums SHA256
and 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 ;)
Warning
I'm not a crypto expert and I'll be very happy to hear your feedback about it e.g. on Twitter @holtwick.