Hidde-Jan Jongsma


Hidde-Jan Jongsma


Jan 11, 2023

A technical deep dive into Tilig’s encryption

Tech talk

Hidde-Jan, our Head of Security, explains how we use cryptography at Tilig, and why we do things slightly differently than other password managers.

Why do we need encryption?

Password managers handle passwords a bit differently than ordinary websites. Sites that use your email address and a password to log you in actually don’t need to store your password at all. Instead, they store a certain fingerprint of your password. Each password has a unique fingerprint, which is computed using a complicated one-way mathematical function called a hashing function. The result is called a hash of the password. This is very safe, since you can’t figure out the password from the hash. Even if a hacker gets access to the hash, they won’t be able to guess your password.

But this doesn’t work for password managers. Password managers are more like a safe, where put your passwords in, and also want to get them back out. A safe where you can never take stuff out again wouldn’t be helpful now, would it? So password managers instead use a complicated two-way mathematical function. This is called encryption. Getting the password back out is called decryption. The mathematical field that deals with these complicated one-way and two-way functions is called cryptography. Cryptography is the playground of Math PhDs and professors in Computer Science.

Cryptography is a tricky field, which makes it very easy to get details wrong. Getting even the tiniest detail wrong can mean the difference between something that’s secure 20 years from now, the encryption being utterly useless. Programmers are explicitly told to “never roll your own cryptography”, and to always use existing software libraries (written by Math PhDs and professors in Computer Science) to make sure they don’t make any catastrophic mistakes. But how do programmers decide which type of cryptography to use? And how do they decide between libraries that both implement the cryptography functions of their choice?

The popular kids

While looking for candidates, two options will certainly stand out: RSA and AES. In short, RSA is an asymmetric algorithm. It uses a pair of keys to encrypt data. One half of the pair is public and can be known to anyone (the public key), and the other half is secret and you should definitely keep it to yourself (the private key). With RSA, the public key is used for encryption and the private key for decryption. AES is a symmetric algorithm, and only uses a single key, the secret key, for both encryption and decryption. Asymmetric encryption functions are useful for when you want to encrypt something for someone else over an insecure channel (like the internet!). You can use their public key, and only they can decrypt it using their private key. Symmetric functions are generally useful when you want to encrypt a lot of data (like a file) or when you already exchanged a secret key with the person you want to send some encrypted data to.

RSA and AES are extremely mature, secure and used by banks, businesses and governments all over the world to secure data. But there is a catch: RSA and AES are more like families of cryptographic functions. They both come in a variety of implementations with different key lengths, cipher modes, and different levels of security. If we would look just at RSA, there are for example cipher modes AOEP, PKCS#1 v1.5, PSS and for AES we have GCM, CBC and CCM and many more. Wow! That’s a lot of options. So which one should we pick? It is not the first time this question is asked as can be seen at this StackOverflow post: How to choose an AES encryption mode (CBC ECB CTR OCB CFB)?

Tilig is a password manager that works everywhere, for everyone. So whatever choice we make, we need to make sure it works on Android, on iOS, in the browser, and in our browser extensions. At first, we chose RSA-AOEP for our asymmetric encryption. This worked reasonably well, but we needed three different libraries to support all our platforms, with each library having their own distinct way of being used. That’s three different opportunities to do things wrong. Having libraries that didn’t work exactly the same on every platform also caused subtle, but not terrible, bugs. We wanted something better, so we came up with the following requirements for our cryptography library:

  • We want a single library (family) that supports all our platforms;
  • It needs to have a very simple API, that’s hard to abuse;
  • It should be fast everywhere;
  • It should use standardised, provably secure cryptography;
  • It should be open source.


Honestly, with these requirements there was no real competition. We picked libsodium. Libsodium is based on the well-known NaCl library (pronounced “salt”, cryptologists like their puns), created by the famous mathematician Daniel J. Bernstein. The cryptographic functions in libsodium are high level and very difficult to misuse. With libsodium, we can use a single codebase on all our platforms (yes, even in the browser using WebAssembly) to do everything related to cryptography. The most important encryption functions in libsodium are called XSalsa20 and XChaCha20. They are considered very secure and have no known weaknesses. Libsodium itself was even checked with a fine comb by cryptography experts with a very good result.

From this point on, we will get a bit technical. So what does libsodium offer use out of the box? The most important high level cryptographic concepts in libsodium are: SecretBox, Box, and SealedBox. (See, don’t need a Math PhD to make puns.) SecretBox uses XSalsa20 to perform symmetric authenticated encryption. This means you need a single secret key to encrypt and decrypt a message, and without the key you can’t even fake an encrypted message. Compared to AES-GCM, which offers the same features, SecretBox uses a 24 byte initialization vector (IV). Twice as large as the 12 bytes from AES-GCM, making it safe enough to use a random IV instead of an incrementing counter. This makes it simpler and safer to use.

The asymmetric variant of SecretBox is simply called Box. It allows one person, Alice, to encrypt a message to another person, Bob, using her own private key and Bob’s public key. Only Bob can decrypt the message from Alice and also verify that it was Alice who sent it. SealedBox is an anonymous variant of Box, that allows Alice to send Bob an encrypted message without Bob being able to see where it came from. In libsodium, Box is based on SecretBox, and SealedBox is based on Box. This means they all enjoy the same level of security.

Is there no one else?

Wait really? There’s only one library that offers cross-platform high level cryptographic functions? Well, pretty much. We have OpenSSL or BoringSSL which are mature, open source and available on multiple platforms, and there are also efforts to bring it to WebAssembly (in part by the same person behind libsodium, the amazing Frank Denis or jedisct1), but they still just offer RSA, AES and also XChaCha20 without deciding on a small set of high level cryptographic concepts. Meaning we still need to do our own picking and choosing. There was one project that actually caught our eye. That was the Tink library by Google.

Tink was all we wanted: high level, misuse proof, open source, created by trusted people, and available on a lot of platforms. There was just one issue: Tink isn’t available for the web. Their javascript implementation is marked as alpha, test only, and there’s no indication from Google that it will ever become mature. At Tilig we don’t feel comfortable building our whole security model on such a thing. Luckily, libsodium offers almost the same ease of use and an equivalent level of security.