Skip to content

How to generate a secret key for asymmetric cryptography over an unsecure network using ECDH in client side JavaScript

Posted on:July 15, 2023 at 03:57 PM
Reading time:12 minutes

In today’s digital world, secure communication between two parties is of paramount importance. One widely adopted cryptographic protocol for secure key exchange is the Elliptic Curve Diffie-Hellman (ECDH) algorithm. In this blog post, we will explore how to implement ECDH secret key generation using the Web Crypto API in client-side JavaScript, enabling secure communication between clients in a web application.

Table of contents

Open Table of contents

Understanding ECDH

So you want to encrypt and decrypt your data. You’ve checked out different encryption / decryption methodologies and algorithms and realized that they require some sort of secret key which lets the algorithm scramble certain data so that only the people with the same secret key can then unscramble the data into something understandable. But you have a problem: how can you 1. generate this so called secret key?, and 2. exchange it securely with other people who you want scrambling / unscrambling data without having to be there physically and telling them in person or writing it down on a piece of paper and handing it to them? (because if you send it any other way, for example via the internet, there is a chance someone might be spying on you and might gain access to the secret key). This is where Elliptic Curve Diffie-Hellman (ECDH) comes in.

ECDH is an asymmetric key exchange algorithm that allows two parties to establish a shared secret over an insecure channel. It utilizes elliptic curve cryptography to derive a secret key, which can then be used to encrypt and decrypt messages exchanged between the parties.

So let’s look at how this works in layman’s terms. Imagine we have two people, Alice and Bob, who want to securely exchange a secret key over an insecure communication channel (like the internet). They want to do this without any eavesdroppers being able to figure out the key.

Step 1: Setup

Alice and Bob agree on a set of parameters for the elliptic curve. An elliptic curve is a specific type of curve with some mathematical properties that make it suitable for cryptography.

They also agree on a base point (a specific point) on the curve. This base point is shared publicly and is the starting point for their calculations.

Step 2: Private and public keys

Both Alice and Bob choose their own private keys. These private keys are just random numbers known only to themselves.

To generate their public keys, they perform a mathematical operation called scalar multiplication. It involves taking their private key and repeatedly adding the base point to itself (like adding the same point multiple times).

For example, if Alice’s private key is 5, she will calculate her public key as 5 times the base point (5 times add the base point to itself). The resulting point after scalar multiplication becomes their public key. Public keys are shared openly with each other and the rest of the world.

Step 3: Key exchange

Now comes the magic part. Alice and Bob exchange their public keys over the insecure communication channel.

After receiving each other’s public keys, they again perform scalar multiplication but this time using their private key and the other party’s public key.

The mathematical property of elliptic curves ensures that the resulting point from this operation will be the same for both Alice and Bob. This resulting point is the shared secret.

Step 4: Shared secret

The shared secret is a point on the curve, but it’s not directly used as the secret key for encryption. Instead, a one-way hash function is applied to this point, which produces a fixed-size secret key.

This secret key can be used for symmetric encryption (like AES) to securely communicate between Alice and Bob. Since they both know the secret key, they can encrypt and decrypt messages without anyone else knowing the key.

Step 5: Eavesdroppers

Even if an eavesdropper intercepts the public keys exchanged between Alice and Bob, they won’t be able to calculate the shared secret easily. This is because the private keys are not exchanged or revealed, and the mathematics involved in scalar multiplication on elliptic curves makes it very difficult to reverse-engineer the private keys from the public keys.

This way, the Elliptic Curve Diffie-Hellman key exchange allows Alice and Bob to establish a shared secret over an insecure channel without revealing their private keys, making it a secure method for key exchange in cryptography.

Web Crypto API

The Web Crypto API provides a set of cryptographic functions and algorithms that can be used within web applications. It offers a secure environment for generating and managing cryptographic keys and performing various operations, including ECDH key generation.

Implementation Steps

To implement ECDH secret key generation using the Web Crypto API in client-side JavaScript, follow these steps:

Step 1: Check browser support

Before proceeding, ensure that the browser supports the Web Crypto API. You can use the following code snippet to check for API support:

if (
  !window.crypto ||
  !window.crypto.subtle ||
  !window.crypto.subtle.generateKey ||
  !window.crypto.subtle.exportKey
) {
  console.log("Web Crypto API is not supported in this browser.");
  // Handle unsupported scenario
}

Step 2: Create a function to generate key pairs

Create a function which calls the generateKey() function of window.crypto.subtle which generate ECDH key pairs (a secret key which you share with nobody and keep to yourself, and a public key which you will share to the people you trust). Choose an appropriate elliptic curve for the key generation, such as “P-256” or “P-384”.

const generateKeyPair = async () => {
  const keyPair = await window.crypto.subtle.generateKey(
    {
      name: "ECDH",
      namedCurve: "P-384",
    },
    true,
    ["deriveKey", "deriveBits"]
  );
  return keyPair;
};

Step 3: Create a function to generate a secret key

Create a function which will derive the final secret key, based on your personal key generated in step 2 and the public key from the other trusted user which they would have generated also on their side using step 2. We can call the deriveKey() function of window.crypto.subtle to do this. Remember that the public key can be exchanged between users even on an insecure channel like the internet because the public key alone does not enable the eavesdropper to encrypt and decrypt messages.

const deriveSecretKey = ({ privateKey, publicKey }) => {
  return window.crypto.subtle.deriveKey(
    {
      name: "ECDH",
      public: publicKey,
    },
    privateKey,
    {
      name: "AES-GCM",
      length: 256,
    },
    false,
    ["encrypt", "decrypt"]
  );
};

Step 4: Create 2 functions to encrypt and decrypt messages using the secret key

Now that we have a function to generate the secret key (step 3), we can create 2 functions which use the secret key to encrypt and decrypt data using AES. We can utilize the encrypt() and decrypt() functions of window.crypto.subtle to do this.

const encrypt = async ({ secretKey, message }) => {
  const enc = new TextEncoder();
  const encoded = enc.encode(message);
  const ciphertext = await window.crypto.subtle.encrypt(
    {
      name: "AES-GCM",
      iv,
    },
    secretKey,
    encoded
  );

  const buffer = new Uint8Array(ciphertext, 0, 5);
  return ciphertext;
};

const decrypt = async ({ secretKey, message }) => {
  const decrypted = await window.crypto.subtle.decrypt(
    {
      name: "AES-GCM",
      iv,
    },
    secretKey,
    message
  );

  const dec = new TextDecoder();
  return dec.decode(decrypted);
};

Step 5: Putting it all together

Now that we setup all the functions, let’s see how we can combine everything to start encrypting and decrypting messages between 2 users. Let’s assume we have 2 users: Alice and Bob.

const agreeSharedSecretKey = async () => {
  // NOTE: Generate 2 ECDH key pairs: one for Alice and one for Bob
  // NOTE: In more normal usage, they would generate their key pairs separately and exchange public keys
  const alicesKeyPair = await generateKeyPair();
  const bobsKeyPair = await generateKeyPair();

  // NOTE: Generate an Initialization Vector (more info below)
  const iv = window.crypto.getRandomValues(new Uint8Array(12));

  // NOTE: Alice then generates a secret key using her private key and Bob's public key.
  const alicesSecretKey = await deriveSecretKey({
    privateKey: alicesKeyPair.privateKey,
    publicKey: bobsKeyPair.publicKey,
  });

  // NOTE: Bob generates the same secret key using his private key and Alice's public key.
  const bobsSecretKey = await deriveSecretKey({
    privateKey: bobsKeyPair.privateKey,
    publicKey: alicesKeyPair.publicKey,
  });

  // NOTE: Alice encrypts a message (string) using the secret key she derived
  const encrypted = await encrypt({
    secretKey: alicesSecretKey,
    message: "haha secret message for you",
  });

  // NOTE: Bob decrypts the encrypted message Alice encrypted using the secret key he derived
  const decrypted = await decrypt({
    secretKey: bobsSecretKey,
    message: encrypted,
  });

  console.log("encrypted:: ", encrypted);
  console.log("decrypted:: ", decrypted);
};

agreeSharedSecretKey();

Exporting public keys

The above example is a simple one just to illustrate how it works in theory. However in practice, Alice and Bob would exchange their public keys via the internet. To use the generated public key for sharing, export it using the exportKey() function of window.crypto.subtle. To import the shared key use importKey() function of window.crypto.subtle. The exported key can then be shared with the other party and imported.

const generateExportableKey = async key => {
  const exportableKey = await window.crypto.subtle.exportKey("jwk", key);
  return exportableKey;
};

const importKey = async key => {
  const importedKey = await window.crypto.subtle.importKey(
    "jwk",
    key,
    {
      name: "ECDH",
      namedCurve: "P-384",
    },
    false,
    []
  );
  return importedKey;
};

Initialization Vectors

In step 5, we setup an Initialization Vector (IV) using:

const iv = window.crypto.getRandomValues(new Uint8Array(12));

An IV in AES (Advanced Encryption Standard) is like a starting point for the encryption process. Think of it as a unique and random number that helps to mix things up and make each encryption session different.

Imagine you want to send a secret message to your friend, but you don’t want anyone else to be able to read it. You decide to use AES, a powerful encryption method.

When you use AES, you need a key, which is like a secret code that you and your friend agree upon beforehand. In our case we used ECDH to derive this secret key. This key is used to encrypt and decrypt the message. But there’s a challenge: if you use the same key for encrypting the same message multiple times, someone clever enough might notice a pattern and break the encryption.

Here’s where the IV comes to the rescue. The IV is a random number that you create for each new message you want to send. It’s like a unique ingredient you mix into your encryption process. Since it’s different for each message, it prevents patterns from forming, making it much harder for attackers to break the encryption.

The AES encryption process takes the combination of your secret key and the IV, and it jumbles up the message in a highly secure way. The resulting encrypted message, or ciphertext, looks like complete gibberish to anyone who doesn’t have the key and the IV.

When your friend receives the ciphertext, they use the same key and IV to decrypt it. The AES decryption process takes the encrypted message and reverses the encryption, revealing the original secret message.

To sum up, the IV in AES is a random number used as a starting point for the encryption process. It adds an extra layer of security by ensuring that each encryption session is unique, making it much harder for attackers to crack the encryption and read your secret messages.

Conclusion

By following these steps and utilizing the Web Crypto API, you can implement ECDH secret key generation in client-side JavaScript. This enables secure communication between clients by deriving a shared secret over an insecure channel. Remember to handle errors and appropriately manage the keys in your application to ensure the security and integrity of your communications.

Please note that cryptographic implementations require careful consideration of various factors such as key management, protocol design, and security best practices. It is advisable to consult relevant cryptographic resources and engage with security experts when implementing cryptographic solutions.

References