Implementing Public Key Cryptography in JavaScript
January 25, 2021
Cryptography refers to the encoding and decoding of messages to maintain confidentiality, integrity, and authentication of information in transit. Public key cryptography is also known as asymmetric cryptography. In this method, there is a public key and a private key. The public key is known widely whereas the private key is the secret to a communicating pair.
When a pair wants to communicate, the sender encrypts the message using the public key of the recipient to come up with the ciphertext. When the recipient receives the message, they decrypt the message using their private key. The private key of the sender and the public key of the receiver is encoded in the ciphertext. This eliminates the key distribution problem.
Prerequisites
Before we begin it would help if you have the following:

Node.js installed on your computer.

Some basic knowledge of the JavaScript programming language.

Some basic knowledge of cryptography.
What we will cover
 Algorithms using public key cryptography
 Libraries for public key cryptography in JavaScript
 Using TweetNaCl.js to implement public key cryptography
 Maninthemiddle attack
 Using precomputed keys
 Maintaining Public keys
 Maintaining Private or Secret keys
 Platforms that employ public key cryptography
Algorithms using publickey cryptography
Since its initial release in 1976, different algorithms have applied this mechanism. The following are some of the algorithms using publickey cryptography:

RSA (RivestShamirAdelman): RSA was designed by Rivest, Shamir, and Adelman. It was first published in 1977 and it is currently widely used for secure data transmission.

Elliptic curve cryptography: Elliptic curve cryptography was suggested in 1985 by Neal Koblitz and Victor S.miller. It’s based on an algebraic structure over finite fields of elliptic curves.

Diffie Hellman protocol: The Diffie Hellman protocol is a mechanism of using the public channel to exchange cryptographic keys. It was designed by Whitfield Diffie and Martin Hellman and was first published in 1976.
Libraries for publickey cryptography in JavaScript
There are different libraries for implementing publickey cryptography in JavaScript. The following are the most commonly used.

NaCL: It’s a highspeed library for carrying out encryption, decryption, and network communication. It uses an elliptic curve cryptography algorithm.

TweetNaCL: It was among the first cryptographic libraries to be released and it was originally written in the C programming language. TweeNaCL.js is the JavaScript version of the library. It uses the Diffie Hellman algorithm.
Using TweetNaCL.js to implement publickey cryptography
For this article, we’ll use TweetNaCL.js
to implement the concept of publickey cryptography.
First, let’s install the dependencies to use in the application. You can use npm
or yarn
to install these packages.
npm install tweetnacl tweetnaclutil
or
yarn add tweetnacl tweetnaclutil
Practical scenario
We have two communicating pairs, David and Viktoria. When David is sending a message to Viktoria, he encrypts it using Viktoria’s public key. When Viktoria receives it, she decrypts it using her private key. When she decides to reply to the message, she encrypts her message using David’s public key, and on David receiving the message, he decrypts it using his private key.
Importing libraries
//import the libraries
const nacl = require('tweetnacl');
nacl.util = require('tweetnaclutil');
Generating the keys
//Generate the keys
const david = nacl.box.keyPair();
const viktoria = nacl.box.keyPair();
We have generated the keys for both David and Viktoria. A pair consists of the public key and the private key. The keys are of type Uint8Array(32)
.
For example, David’s key pair will look like this:
{
publicKey: Uint8Array(32) [
159, 32, 160, 185, 143, 29, 55, 23,
111, 203, 90, 224, 64, 90, 65, 75,
80, 149, 12, 124, 83, 145, 72, 162,
96, 163, 121, 157, 62, 78, 203, 52
],
secretKey: Uint8Array(32) [
72, 210, 106, 229, 196, 53, 2, 88,
124, 87, 128, 174, 185, 0, 192, 52,
5, 162, 11, 39, 23, 183, 103, 165,
40, 128, 179, 242, 38, 132, 78, 241
]
}
David encrypting the message
function davidEncrypting(){
const one_time_code = nacl.randomBytes(24);
//Get the message from david
const plain_text = "Hello there Viktoria";
//Get the cipher text
const cipher_text = nacl.box(
nacl.util.decodeUTF8(plain_text),
one_time_code,
viktoria.publicKey,
david.secretKey
);
//message to be sent to Viktoria
const message_in_transit = {cipher_text,one_time_code};
return message_in_transit;
};

We’ll generate a onetime code, a code i.e., only valid for the current process.

We’ll get the plain text from David.

We’ll compose the ciphertext using
nacl.box()
passing the following parameters:
A string of Unicode characters generated by passing plain text as a parameter through
decodeUTF8()
. 
Onetime code.

Viktoria’s public key.

David’s private key.

The message to be sent to the recipient as per the library is:

Ciphertext.

Onetime code.
Viktoria decrypting the message
function viktoriaDecrypting(message){
//Get the decoded message
let decoded_message = nacl.box.open(message.cipher_text, message.one_time_code, david.publicKey, viktoria.secretKey);
//Get the human readable message
let plain_text = nacl.util.encodeUTF8(decoded_message)
//return the plaintext
return plain_text;
};
Output:
Hello there Viktoria
We are decoding the message using the ciphertext, onetime code, David’s public key, and Viktoria’s secret key. The message is then encoded to UTF8
using encodeUTF8()
so that its humanreadable.
Maninthemiddle attack
In publickey cryptography, the public key is disseminated widely. This means that an attacker may get the public key of another party. If party A is communicating with party B, an attacker may impersonate himself or herself such that when party A is sending a message to party B, the message reaches the attacker before reaching party B.
The attacker then modifies the message and sends the modified message to party B. When party B decides to reply, the message is again sent to the attacker who modifies the message and sends the modified message to party A. In such a situation, the integrity of the message is not preserved. This is a major threat to publickey cryptography.
Using precomputed keys
To curb the above threat, we use precomputed keys. Here, while encrypting and decrypting, instead of using the public key of the other party which could be impersonated, we use a shared key. A shared key is a special combination key of the recipient’s public key and the sender’s secret key.
David encrypting the message
function davidEncrypting(){
//David computes a one time shared key
const david_shared_key = nacl.box.before(viktoria.publicKey,david.secretKey);
//David also computes a one time code.
const one_time_code = nacl.randomBytes(24);
//Davids message
const plain_text = "Hey!!, our communication is now more secure";
//Getting the cipher text
const cipher_text = nacl.box.after(
nacl.util.decodeUTF8(plain_text),
one_time_code,
david_shared_key
);
//message to be transited.
const message_in_transit = {cipher_text,one_time_code};
return message_in_transit;
};

We’ll compute a shared key based on Viktoria’s public key and David’s secret key.

We’ll generate a onetime code.

We’ll get the plain text from David.

We’ll compute the ciphertext using
nacl.box.after()
passing the following parameters:
A string of Unicode characters generated by passing the plain text through
decodeUTF8()
. 
Onetime code.

David’s shared key.

The message to be sent to the recipient is comprised of:

Ciphertext.

Onetime code.
Viktoria decrypting the message
function viktoriaDecrypting(message){
//Getting Viktoria's shared key
const viktoria_shared_key = nacl.box.before(david.publicKey,viktoria.secretKey);
//Get the decoded message
let decoded_message = nacl.box.open.after(message.cipher_text,message.one_time_code,viktoria_shared_key);
//Get the human readable message
let plain_text = nacl.util.encodeUTF8(decoded_message)
//return the message
return plain_text;
};
Output:
Hey!!, our communication is now more secure

We’ll get Viktoria’s shared key.

We’ll decode Viktoria’s message using the ciphertext, onetime code, and her shared key.

We’ll encode the message to
UTF8
usingencodeUTF8()
so that its humanreadable.
Maintaining public keys
Public key infrastructure is a body responsible for maintaining and registering public keys. Practical areas that use public key infrastructure are banks. All banks have their keys stored and maintained by one body. So when one bank wants to transfer funds to another they get the keys from the common body. Public key infrastructure ensures the credibility of public keys thereby preventing man in the middle attacks.
Maintaining private or secret keys
It’s very important to maintain these keys since they are crucial and critical. Currently, they are stored online in databases or through some other medium. They are usually encrypted using encryption algorithms such as Advanced Encryption Standard (AES)
to promote integrity before storing them.
Platforms that employ publickey cryptography
Some of the platforms that use publickey cryptography in production include:
Conclusion
Public key cryptography solves the key distribution problem but suffers the threat of maninthemiddle attack. There are different methods to solve it and among them is using precomputed keys. The method is still in demand and is used by a lot of successful companies.
In this article, we have covered an introduction to publickey cryptography, algorithms that use publickey cryptography, libraries to use when implementing publickey cryptography in JavaScript.
We also implemented publickey cryptography using tweetnacl.js
, we went over maninthemiddle attack, a brief example using precomputed keys, how we maintain public keys and private keys, and we mentioned platforms using publickey cryptography today.
You can access the finalized code from here.
Happy Coding!
Resources
Peer Review Contributions by: Mohan Raj
About the author
Kennedy Mwangi
Kennedy is an Information technology graduate from Karatina University. He is fluent in web development with both the frontend and backend using JavaScript. He is very passionate about Linux and Android development.