FluentCrypto: Cryptography in Easy Mode: Background and Motivation

cover
12 Jun 2024

Authors:

(1) Simon Kafader, University of Bern, Bern, Switzerland (simon.kafader@inf.unibe.ch);

(2) Mohammad Ghafari, University of Auckland, Auckland, New Zealand (m.ghafari@auckland.ac.nz).

III. BACKGROUND AND MOTIVATION

Node.js is a popular open-source, cross-platform, backend JavaScript runtime environment to build scalable network applications.[1] It provides a built-in library called “crypto” which developers can use to perform cryptographic operations on data. According to the latest survey conducted by the Stack Overflow website in 2020,[2] for the second year in a row, Node.js is a worldwide leader among frameworks. However, to the best of our knowledge, there exists no work in academia to address crypto misuses in this environment.

Consider Listing 1 that shows a code example to encrypt data with a private key. We access the cryptography module at line 1. We specify the encryption algorithm i.e., “des”, derive the encryption key using the Scrypt function, and define the initialization vector (iv) at line 2-4, respectively. Instances of the Cipher class are used to encrypt data, and we create and initialize such an instance at line 5. The cipher.update() method performs the actual encryption. It takes three arguments i.e., the data, the input encoding, and the output encoding. We call this method at line 6 to encrypt a piece of “utf-8”-encoded text and store the result in the hex format. In the end, at line 7, we call the cipher.final() method to indicate that the Cipher object is no longer needed and the encryption process is finalized.

There are a number of caveats that developers should take into account to ensure that this code and its counterpart for decryption work correctly. For instance, the default string encoding used by the crypto module is utf-8. We also need a key of a certain length, depending on which cipher algorithm we choose for encryption. In this example, the iv and the key must have the same length i.e., number of bytes. If the encoding of the input data is given to the cipher.update() method, the data argument is a string in the specified encoding. Otherwise, data must be a Buffer. Likewise, if the encoding of the output is specified, a string in the specified encoding is returned. Otherwise, a Buffer is returned.

Even if we assume that a novice developer will figure out how to write the code in Listing 1 that is functioning, it suffers from a number of security issues. We used Scrypt, a password-based key derivation function, which requires three arguments namely password, salt and the key length to construct the secret key. A salt is combined with the password to avoid producing an identical key for the same password, which substantially lessens the impact of pre-computed hash attacks especially against commonly used passwords[3]. Therefore, salt should be as unique as possible and it is recommended that salt

Listing 1: An example of insecure symmetric encryption

is random and at least 16 bytes long. However, in the example, the salt is a fixed value and does not help in deriving distinct keys. The next security issue concerns the IV whose purpose is to ensure that an identical message encrypted with the same key leads to different encrypted outputs. This requires the IV to be unpredictable and unique; and ideally, cryptographically random so that an attacker cannot predict ahead of time what a given IV will be. Nevertheless, in the example, the IV is always a zero-filled buffer. Finally, DES is no longer a secure algorithm due to its comparatively short key size.

We investigated obstacles as well as security risks that are associated with hashing, symmetric and asymmetric encryption in Node.js. We decided to focus on these cryptographic tasks as research as well as our experience show that they are commonly used to secure software systems [4]. It is notable that a developer must have knowledge about, for instance, the choice of algorithm, the correct key and IV generation process, and input and output encoding. Even worse, error messages are sometimes cryptic. For example, by default, Node.js crypto API assumes that the input encoding must be a buffer, and the failure in setting the right character encoding during decryption yields the “error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length” error, which does not guide developers per se toward resolving the issue.

It would have been much easier if developers could have just told the API that, for example, they want to encrypt data and then decrypt it later, without necessarily having to search for a (secure) algorithm; understanding how long the key and the IV have to be, and how to generate them securely; and with more useful error messages when something goes wrong.

This paper is available on arxiv under CC BY 4.0 DEED license.


[1] https://nodejs.org

[2] Stack Overflow Developer Survey 2020

[3] NIST recommendation for password-based key derivation