Designing an End-to-End-Encrypted contact form with OpenPGP.js
Image from unsplash

Designing an End-to-End-Encrypted contact form with OpenPGP.js

Written by Pranav Chakkarwar on 01 Sep 2021 • 13 minutes to read

Test this e2ee form on my contact page.

Inspiration

In Jul of 2021 Tutanota introduced secure connect for Journalists. I then forgot about it for a month. One fine day it stuck me that I could use Protonmail’s openPGP JavaScript library to implement something similar to secure connect. Within an hour I was able to implement an basic end-to-end-encrypted contact form, thanking my skills to write 10 lines of code and thanks to the contributors who wrote much more lines to implement PGP in JavaScript. Tutanota’s secure connect has many more features than my form but you can still support open knowledge.

Should I use it on my website?

Yes, if you can, it’s fantastic and If you handle sensitive information on your site, you should definitely implement it.

Tutanota introduced secure connect for Journalists and maybe your user’s data isn’t that sensitive. Most form backend providers use an endpoint with https enabled and any email with user’s submitted data is sent to your inbox over an encrypted channel between form’s email provider and your email provider (assuming both use encrypted connections). That means the data submitted by an user on your website is already encrypted between the user, form’s backend provider, form’s email provider, your email provider and you.

You can use this e2ee form if you want any submitted data to only stay between an user and you (end-to-end-encrypted). This solution is also resistant to person-in-the-middle attacks.

Building a basic contact form

Add two inputs (name and email) outside the form because you don’t want to send that data over email in plain text. Surround the inputs with a div and assign id=useless to hide it using JavaScript after encrypting a message.

<div id="useless">
  <input
    class="form-input"
    type="text"
    id="visitor-name"
    placeholder="Your Name"
  />

  <input
    class="form-input"
    type="email"
    id="visitor-contact"
    placeholder="Your Email"
  />
</div>

Add a basic form with a submit button and two input fields (textarea and a input with an access key from Web3Forms.com). A paragraph with id=form-result can be modified using JavaScript to indicate submission status of the form.

<form id="e2ee-form">
  <textarea
    class="form-input"
    id="visitor-message"
    name="message from visitor"
    cols="70"
    rows="10"
    placeholder="Your Message"
  ></textarea>

  <input class="form-input" type="hidden" name="apikey" value="ACCESS_KEY" />

  <p id="form-result"></p>

  <button class="btn" id="submit-button" hidden>Send encrypted message</button>
</form>

Lastly, we need a button to call an encryptUserMessage() function. Also, include all JavaScript files for encrypting a message and submitting the form with AJAX.

You can either download the openPGP.js script or let UNPKG deliver it for you. Instructions are given on openPGP.js github readme.

<button class="btn" id="encrypt-button" onclick="encryptUserMessage()">
  Encrypt my message
</button>

<script src="openpgp.min.js"></script>
<script src="main.js"></script>

Add some CSS to make the form look pretty

body {
  font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
  text-align: center;
}

.form-input {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  background-color: #fff;
  border-color: #d1d5db;
  border-width: 1px;
  border-radius: 0.375rem;
  padding-top: 0.5rem;
  padding-right: 0.75rem;
  padding-bottom: 0.5rem;
  padding-left: 0.75rem;
  font-size: 1rem;
  line-height: 1.5rem;
  margin: 0.5rem;
}

.btn {
  color: #212529;
  background-color: #ffc107;
  border-color: #ffc107;
  font-weight: 400;
  text-align: center;
  white-space: nowrap;
  vertical-align: middle;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  border: 1px solid transparent;
  padding: 0.375rem 0.75rem;
  font-size: 1rem;
  line-height: 1.5;
  border-radius: 0.25rem;
  cursor: pointer;
}

#submit-button {
  color: #fff;
  background-color: #1e7e34;
  border-color: #1c7430;
}

Add JavaScript to encrypt the form data and POST it to a backend

openPGP provides a readKey function to read your armoredKey. Store this result in a const to use it for encrypting a message.

const key = `PASTE YOUR PUBLIC KEY HERE`;
const publicKey = await openpgp.readKey({ armoredKey: key });

Once an user enters their email, name, message, combine all three as one whole message so it can be encrypted by openPGP.

var combinedMessage =
  "Message: " +
  document.getElementById("visitor-message").value +
  "\n" +
  "Name: " +
  document.getElementById("visitor-name").value +
  "\n" +
  "Contact: " +
  document.getElementById("visitor-contact").value;

Encrypting a message is fairly simple and can be done by using a encrypt function from the openPGP library. You just need to pass a message and your public key as arguments to the function.

Hide all unnecessary fields (email, name, encrypt message button) so an user won’t accidentally encrypt their message twice. You also need to change the value of textarea with the encrypted message as the form will send any text that is included within the textarea. It also serves as a visual cue to the user that their message has been encrypted!

var encrypted = await openpgp.encrypt({
  message: await openpgp.createMessage({ text: combinedMessage }),
  encryptionKeys: publicKey,
});

document.getElementById("visitor-message").value = encrypted;
document.getElementById("submit-button").removeAttribute("hidden");
document.getElementById("encrypt-button").setAttribute("hidden", "");
document.getElementById("useless").setAttribute("hidden", "");

Lastly, create a async function so you can call it using a button.

Below is the full function to encrypt a message.

async function encryptUserMessage() {
  const key = `PASTE YOUR PUBLIC KEY HERE`;
  const publicKey = await openpgp.readKey({ armoredKey: key });

  var combinedMessage =
    "Message: " +
    document.getElementById("visitor-message").value +
    "\n" +
    "Name: " +
    document.getElementById("visitor-name").value +
    "\n" +
    "Contact: " +
    document.getElementById("visitor-contact").value;

  var encrypted = await openpgp.encrypt({
    message: await openpgp.createMessage({ text: combinedMessage }),
    encryptionKeys: publicKey,
  });

  document.getElementById("visitor-message").value = encrypted;
  document.getElementById("submit-button").removeAttribute("hidden");
  document.getElementById("encrypt-button").setAttribute("hidden", "");
  document.getElementById("useless").setAttribute("hidden", "");
}

To submit the contact form with AJAX I have used Web3Form’s code (with a few changes). The code should be self explanatory.

const form = document.getElementById("e2ee-form");
const result = document.getElementById("form-result");

form.addEventListener("submit", function (e) {
  const formData = new FormData(form);
  e.preventDefault();
  var object = {};
  formData.forEach((value, key) => {
    object[key] = value;
  });
  var json = JSON.stringify(object);
  result.innerHTML = "Please wait...";

  fetch("https://api.web3forms.com/submit", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Accept: "application/json",
    },
    body: json,
  })
    .then(async (response) => {
      let json = await response.json();
      if (response.status == 200) {
        result.innerHTML = "Congrats your message is recieved!";
        document.getElementById("visitor-message").setAttribute("hidden", "");
        document.getElementById("submit-button").setAttribute("hidden", "");
      } else {
        console.log(response);
        result.innerHTML = "Something went wrong!";
      }
    })
    .catch((error) => {
      console.log(error);
      result.innerHTML = "Something went wrong!";
    });
});

Get yourself an e2ee contact form using my code on github or checkout my contact form

end-to-end-encrypted-contact-form

  1. Fork my github repo
  2. Get an access key at Web3Forms.com
  3. In index.html, replace YOUR ACCESS KEY HERE with your access key.
  4. In main.js, replace YOUR PUBLIC KEY HERE with your public key.
  5. Go to repo settings —> Pages —> Choose source main and click save. It may take some time for github to publish your site. When published, github will give you a link to your contact form.

If you don’t have a PGP key, an easiest way is to get a protonmail account and export the public key or do a quick search on securely generating a pgp key for your operating system.

Takeaways

hQEMA77em1ZcD0YZAQgAlbmnHZKedMbPx6vAheOj4j7LJnMHzGDkXZ9KM5MZcimIMeSNfCjSt2fNrfpVTdkZVNaJ6CH638E5w3Jx/GlPi+ECi+Ci77mwxTw3yoJfRm2c+YY/wQ8hcNLLtDB++K4RFiEORUoayDaXRuS5pktYbneB+Rdy67imLFcopoGZQ2Mt+EkLgHRt2NgsjfWK3/aHwuB37gNvqPSj/8hz/g/vCGjPBb7xdl68c0W29bYA0pdYKlm9c2GCC2UIdEAsR7kaLaH/KxhDdjc03MwdOE3K1wcdjq5RNkp7RcKSJHojHAFBQYFJrFwQS9vXLZ+EfeOmS8BRpN3WhViiscZCUHFMnCGWui9cV1CI0iDadGnsdh3HcLDTnP5

—–END PGP MESSAGE—–

Send your contact form to friends and colleagues 🥳



Enjoyed this article?


Comments

Post a comment

Comments will be manually approved and will take time to reflect.

You may also Enjoy

Crafting a minimal, fast and yet a feature-rich blog (Introduction)

Crafting a minimal, fast and yet a feature-rich blog (Introduction)

Published on 24 Nov 2021 • 5 minutes to read

Build your identity on the internet using a blog and share your ideas. The internet is open and w...
Facebook isn't filtering photos, but thoughts

Facebook isn't filtering photos, but thoughts

Published on 29 Oct 2021 • 15 minutes to read

Do you wake up to hate speech, misinformation, and disinformation every day? Are you inclined to ...
A simple and effective guide to online privacy

A guide to online privacy

Published on 17 Jul 2021 • 5 minutes to read

Protecting our privacy online tends to be especially important nowadays, but arguing won't solve ...
Understanding the importance of design with the help of dark patterns on the internet

Avoid using dark patterns!

Published on 27 May 2021 • 3 minutes to read

Dark patterns cause more harm than good! It not just the visuals that count but the whole experie...