Encrypted client-server communication (protection of privacy and integrity with AES and RSA in details)

A very important aspect in the world of software development is the security of data that flows through open communication channels. In our web applications, there is an intensive exchange of data via different protocols ,like http, between client applications which presented as browser, mobile and desktop applications and server side applications. The importance and confidentiality of data may be different depending on the specifics of the web application, and the possibility of interception by a third party increases with perfection of hacking techniques in the world of IT. What can be done to prevent access to the data by your traffic listener? If we exchange with data between the client applications and server we don’t want the information to be stored as open text on the server, which will be accessible in case of server crack or social engineering, or man in the middle attack. How to avoid it?

Lets take a look on https protocol. It provides an average security against interceptions, but at the same time, there are ways to intercept traffic with protocol vulnerabilities. We’ll try to completely solve these problems with an encryption of application data at the level of information exchange between client and server. Remark: we recommend to always use https in any way, even with additional client-server encryption.

In this article we will look at realization example of an exchange by encrypted messages between server-side application and browser. This time, the AES and RSA algorithms are being used. WebSocket was chosen as messaging protocol (to be honest, I just wanted to write encrypted chat as an example at the beginning, but decided to do simpler application and explain it better). At the same time it’s the fastest messaging version between the backend and front-end, it helps to write less code maintenance and concentrate on the process of encryption. The client side will be represented by the web browser and encryption implementation will be written with browser javascript. The server part was written in Nodejs.

Thus, the tools are selected, the next step is making a choice of encryption libraries which are used by the client and server side. It is important that client and server libraries, you want to select, support the same set of encryption algorithms, encryption modes, and the length of the keys that can be set for encryption.

Another remark here: do not create your own cryptography security schemes and do not implement standard ones on your own. You will most probably do major security mistakes which will make your application insecure!

As for the browser-javascript and Nodejs there is quite a wide choice of encryption libraries. Following libraries considered in example below:

javascript — WebCryptoAPI

https://www.w3.org/TR/WebCryptoAPI/
https://github.com/diafygi/webcrypto-examples

Nodejs — Crypto, и node-rsa

https://nodejs.org/api/crypto.html
https://www.npmjs.com/package/node-rsa

The article reveals following questions:

1) Basic information about the AES and RSA algorithms.

2) Environment preparation.

3) Generation of public and private RSA keys for a client and a server with Nodejs.

4) Exchange of encrypted RSA messages between a client and a server.

5) AES key generation on the server side and it’s transfer to the browser in an encrypted form using RSA.

6) Exchange of AES messages between a client and a server.

The sample code is available at the following address:

https://github.com/weblab-technology/rsa-aes-client-server-encryption-nodejs-example

1. Basics about the AES and RSA algorithms

AES is a symmetric encryption algorithm. It uses the same key for encryption and decryption. Large amounts of data can be encrypted using a symmetric encryption algorithm.

Vector (IV) is generated before encryption initialization, which is a number of bytes. Generally, it should be random or pseudorandom. This setting does not allow an attacker to get relations between encrypted messages segments, making a hack more complicate therefore. Read more:

https://en.wikipedia.org/wiki/Initialization_vector

An important parameter for AES is the encryption mode. It defines, how the sequence of open data blocks converted into encrypted ones.
For AES, there are such modes: ECB, CBC, РСВС, CFB, OFB, CTR. Details:

https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation

AES key length is 128 , 192, 256 bits.

RSA — an asymmetric encryption algorithm, based on using public and private keys. A message is encrypted using a public key and can be decrypted only with a private key.

The public key may be available to all system users, and private should be kept confidential in an encrypted form. Unlike symmetric encryption, asymmetric encryption is a heavy operation and requires significant computing power. Also there is a limit on message length which can be encrypted using an asymmetric algorithm depending on a key length. Typically, this algorithm is used to obtain a key of the symmetric encryption algorithms by which an encryption takes place after, and for digital certificates signing.
RSA key length is 1024, 2048, 4096 bits.

RSA uses certain encryption schemes that applies algorithms of adding data which means nothing to encrypted information, aimed at improving process reliability:

RSA-OAEP
RSA-PKCS1-v1_5
More details can be found here:
RSA-PKCS1-v1_5
https://en.wikipedia.org/wiki/Padding_(cryptography)
https://en.wikipedia.org/wiki/RSA_(cryptosystem)#Padding

It should be noted that RSA-PKCS1-v1_5 scheme is considered to be insufficiently reliable, you better use RSA-OAEP.

2. Environment preparation

An example of encrypted messages exchange will be performed in the following sequence:

  1. Client decrypts the message and writes it on screen. Then RSA encrypted message sends to backend.
  2. Backend decrypts RSA message from a client and log it into console. Server generates AES key, and sends it a client.
  3. Client decrypts RSA encrypted AES key, logs it on screen. Client sends AES encrypted message to a server.
  4. Server decrypts AES message from client and logs it on console. Server sends AES encrypted message to client.
  5. Client decrypts AES message and it is logged on screen.

First of all, let’s create a basic structure of an application that will allow to give html page and handle Websockets. We use extensions for Nodejs: express and socket.io.

Code for this article is represented in this repository: https://github.com/weblab-technology/rsa-aes-client-server-encryption-nodejs-example

This example’s version of Nodejs is 6.9.4. For those, who use docker, stick with the docker-compose.yml file which can be found in the sample repository (see docker-compose).

We designate directory with the project as a APP_ROOT and perform the following steps:

First, create APP_ROOT/package.json:

2. Add reliances:

npm install — save express
npm install — save socket.io

3. Create a directory for static APP_ROOT/static, and a file in it — APP_ROOT/static/index.html where browser calculation will take place. Content of APP_ROOT/static/index.html:

4. Let’s create APP_ROOT/index.js file to put there a start of a web server and a Websockets connection handler :

5. Run the command node index.js and check that everything is working, then open a browser and type localhost:3000. Now an example background is ready , let’s move on to encryption issues.

In order to keep the code easy to read we’ll create a function — wrappers for AES and RSA encryption algorithms, for clients and a server, just keep it in mind.

3. Generation of public and private RSA keys for the client and server with Nodejs

At this stage, we will generate public and private keys for the client and server in PEM format. To do this, follow next steps:

  1. Create following folders and files:
  • folder APP_ROOT/components/;
  • folder APP_ROOT/keys/;
  • file APP_ROOT/init.js;
  • file APP_ROOT/components/rsa-wrapper.js;

2. To generate keys we use library node-rsa. Install it:

npm install — save node-rsa

3. To generate keys, you need to choose a great value of open exponential. Typically, prime Ferma numbers are selected: 17, 257, 65537. This value acts as an argument of key generation function . The number 65537 is an acceptable compromise between speed and reliability.

Open APP_ROOT/components/rsa-wrapper.js and add the code below:

https://en.wikipedia.org/wiki/RSA_(cryptosystem)#Key_generation

4. Create and open file APP_ROOT/components/init.js:

5. Run node init.js.

Message appeared “Keys generated …”

Go to folder APP_ROOT/keys/ . There should be generated keys for the client and the server:

  • APP_ROOT/keys/client.private.pem
  • APP_ROOT/keys/client.public.pem
  • APP_ROOT/keys/server.private.pem
  • APP_ROOT/keys/server.public.pem

6. Copy client keys and the server public key to APP_ROOT/static/index.html file. Go to APP_ROOT/static/index.html file under the body tag and insert markup:

In textarea we paste content of files from APP_ROOT/keys/:

  • textarea id=”server_public” — APP_ROOT/keys/server.public.pem
  • textarea id=”client_public” — APP_ROOT/keys/client.public.pem
  • textarea id=”client_private” — APP_ROOT/keys/client.private.pem

4. RSA encrypted messages exchange between a client and a server

In this section, a client will receive an encrypted message from a server, which being decrypted and displayed, and then sent back (RSA message) to a server, which the server decrypts. It’s that simple :)

  1. Add functions to RSA wrapper.

APP_ROOT/components/rsa-wrapper.js:

2. Go to the APP_ROOT/index.js file and add a constant:

Before http server run, load keys:

3. Run the server, node index.js, check if all keys are loading, and now line’s going to be encrypted and decrypted by testing sample.

Server public encrypting
Encrypted RSA string
… encrypted format …
Decrypted RSA string …
Server init hello

Great, RSA encryption testing script works on a server side.

4. Now go to file APP_ROOT / index.js, look for an event handler for websockets connection:

Add the following server-side code to connection event handler. We add the code for encrypting the message using RSA and send it to a client:

5. Now let’s look at the code in browser. To work with RSA messages we need a library for encodings, we use an external library for this purpose:

https://github.com/inexorabletash/text-encoding

It’s needed to work with text encodings conversion while working with encrypted messages. Add files of this library:

APP_ROOT/static/js/encoding.js

APP_ROOT/static/js/encoding-indexes.js

APP_ROOT/static/js/converter-wrapper.js — a set of methods for converting base64 strings to ArrayBuffer and vice versa.

A set of wrappers were created within an example, should be simply copied to your project from a finished example in the repository. Using these functions is related to Web CryptoAPI library in browser has parameters like ArrayBuffer, plus this type of values are sent back and it’s extremely important to correctly convert these parameters to work with them on a server.

6. Also add a wrapper for the crypto RSA browser library.

7. Let’s get to APP_ROOT/static/index.html. Add necessary scripts to this file after socket.io.js connected

Also add an encryption processing code:

8. Now run node index.js and type localhost: 3000 in the browser

We will see base64 encoded message in the browser and decrypted message from the server. Everything worked out, browser understands RSA message from the server. If we go into the Nodejs execution console, we’ll see RSA message from the client in encrypted base64 form and in decrypted form. Now server realize RSA messages from the client.

5. AES key generation on the server side and it’s transfer to the browser in an encrypted form using the RSA

  1. Create an AES wrapper to work with the crypto library on node.js

2. Next, going to APP_ROOT/index.js and add aesWrapper module:

To the end of connection event handler, we add a code for AES key generation and it’s dispatch to the client side, encrypted with RSA:

3. On the client side, add a file that will work with the AES functions of Web CryptoAPI APP_ROOT/static/js/aes-wrapper.js:

4. Plug in aes-wrapper.js to APP_ROOT/static/index.html

Add a handler for receiving and decrypting keys from the server

5. Restart the server and look at the log. You’ll see a message with encrypted and decrypted key in base64 format в base64 (from server to client). Mission accomplished :)

6. Client and server AES messages exchange.

  1. Go to APP_ROOT/index.js. Look for web socket connection handler and add AES messages exchange handler.

2. Go to APP_ROOT/static/index.html and to the code of AES key receiving we add:

As well as:

3. Restart the server and look at the browser’s log.

Finally. As a result, we got a simple script to establish connection between client and server and obtain the session key. Should be mentioned that a private key of showed example above is stored in clear form, as a rule it should be encrypted with a password.

Also, when passing session keys, highly recommended to use a digital signature of the server in order to avoid packets replacement by a hacker.

We hope you enjoy this subject and want to learn a little bit more about cryptography.

Repository with code:

https://github.com/weblab-technology/rsa-aes-client-server-encryption-nodejs-example

Useful links:

https://coolaj86.com/articles/asymmetric-public--private-key-encryption-in-node-js/
https://coolaj86.com/articles/symmetric-cryptography-aes-with-webcrypto-and-node-js/
by Andrey Gus, Backend Developer
Dima Dmytriienko, editor & Marketing Specialist
with help of Oleksandr Knyga, Software Engineer