Client-side JS Encryption

Now that the Virtru SDK knows you’re Alice, you can protect your first piece of data.

Encryption Basics

Before calling encrypt, you need to specify a few simple parameters.

You don’t need to include your email address when encrypting. You will already have access to anything you encrypt because you authenticated. But if you want anyone else to have access (like another one of your emails, [email protected]), you could include them here:

  // specify access controls
  const policy = new Virtru.PolicyBuilder()
    .addUsersWithAccess(["[email protected]"])
    .build();

  // protect & output
  const encryptParams = new Virtru.EncryptParamsBuilder()
    .withStringSource(unprotectedString)
    .withDisplayFilename(unprotectedFile)
    .withPolicy(policy)
    .build();

Call encrypt and check out the resulting file:

  const protectedStream = await client.encrypt(encryptParams);
  let protectedExt = ".tdf.html"; // HTML format
  const protectedFile = unprotectedFile + protectedExt;
  await protectedStream
    .toFile(protectedFile)
    .then(() => console.log(`Encrypted file ${protectedFile}`));

Here's the complete source code:

// Encryption example - HTML format
let client;

// assumes Virtru auth widget
function afterAuth(email) {
  client = new Virtru.Client({ email });
}

async function protect(unprotectedString) {
  let unprotectedFile = "sensitive.txt";

  // specify access controls
  const policy = new Virtru.PolicyBuilder()
    .addUsersWithAccess(["[email protected]"])
    .build();

  // protect & output
  const encryptParams = new Virtru.EncryptParamsBuilder()
    .withStringSource(unprotectedString)
    .withDisplayFilename(unprotectedFile)
    .withPolicy(policy)
    .build();
  const protectedStream = await client.encrypt(encryptParams);
  let protectedExt = ".tdf.html"; // HTML format
  const protectedFile = unprotectedFile + protectedExt;
  await protectedStream
    .toFile(protectedFile)
    .then(() => console.log(`Encrypted file ${protectedFile}`));
}

Now, your sensitive data is safe. Keep reading to see how or access your data by decrypting.


Encryption Lifecycle

That one little encrypt call did a lot of work behind the scenes to keep your sensitive data safe:

Protecting a file and sending the policy and key materials to VirtruProtecting a file and sending the policy and key materials to Virtru

Protecting a file and sending the policy and key materials to Virtru

  • Step 1: Your app authenticates with Virtru's entity service.

    This was when you proved you were [email protected].

  • Step 2: Then, an encrypt call protects any given data as ciphertext. This ciphertext is local to where encrypt was called (a browser, end user device, or server). Although encrypt uses the open Trusted Data Format (TDF), you can save the ciphertext in a file format that you can open anywhere—HTML.

    This was when your sensitive data became protected.

  • Step 3: While an encrypt call keeps the protected data as local ciphertext, a secure decryption key travels back to Virtru’s key server for safe-keeping. But this key server doesn’t hand out keys to anyone…

    This is why you don’t have to manage keys yourself.

  • Step 4: While an encrypt call keeps the protected data as local ciphertext, the SDK saves the access controls for the protected data in Virtru’s access server. The access server determines if a particular authenticated user (Step 1) can access protected data (Step 2) using a decryption key (Step 3).

    This is the crucial backend you don’t have to build or host. It never has access to your local data.


Encrypted File Formats

We generated a .tdf.html file above. This is the SDK default, but not the only option.

HTML FormatZIP Format
File Extension.tdf.html.tdf
File SizeFile sizes less than 100 MBAny file size
DecryptionAny Virtru SDKAny Virtru SDK
User ExperienceOpen file anywhere to redirect to Virtru's Secure ReaderDrag & drop in Virtru's Secure Reader

If you need a protected ZIP file, add it to the encrypt params and set the file extension:

// Encryption example - ZIP format
let client;

// assumes Virtru auth widget
function afterAuth(email) {
  client = new Virtru.Client({ email });
}

async function protect(unprotectedString) {
  let unprotectedFile = "sensitive.txt";

  // specify access controls
  const policy = new Virtru.PolicyBuilder()
    .addUsersWithAccess(["[email protected]"])
    .build();

  // protect & output
  const encryptParams = new Virtru.EncryptParamsBuilder()
    .withStringSource(unprotectedString)
    .withDisplayFilename(unprotectedFile)
    .withZipFormat() // ZIP format
    .withPolicy(policy)
    .build();
  const protectedStream = await client.encrypt(encryptParams);
  let protectedExt = ".tdf"; // ZIP format
  const protectedFile = unprotectedFile + protectedExt;
  await protectedStream
    .toFile(protectedFile)
    .then(() => console.log(`Encrypted file ${protectedFile}`));
}

And you’ll have a ZIP file with the same level of protection as HTML, but the caveats in the table above.

Drag & Drop Client-side Encryption Example

Here is a complete example with drag & drop uploads that get encrypted. Run the following code with Parcel as in the quick start.

<html>
  <head>
    <title>Protect | Virtru SDK for JavaScript - Sample Application</title>
    <link
      href="https://sdk.virtru.com/js/latest/auth-widget/index.css"
      rel="stylesheet"
    />
    <link href="./protect.css" rel="stylesheet" />
    <script src="https://sdk.virtru.com/js/latest/auth-widget/index.js"></script>
    <script src="https://sdk.virtru.com/js/2.1.0/virtru-sdk.min.js"></script>
  </head>
  <body class="unauthorized">
    <h1>Encrypt a File</h1>
    <div id="auth"></div>
    <div id="input">
      <div id="drop_zone">
        <p>Drag a sensitive file here to encrypt it...</p>
      </div>
    </div>
    <div id="waiting">
      <p>Encrypting file...</p>
    </div>
    <div id="download">
      <a id="download_link" rel="noopener">Save Protected File</a>
    </div>
    <div id="error_box">
      <h3 id="error">An error has occurred</h3>
      <p id="detail"></p>
    </div>

    <script src="./protect.js"></script>
  </body>
</html>
// Virtru Client. This is set by the auth handler.
let client;

function dragOverHandler(e) {
  e.preventDefault();
}

// Set the error message
function setAlert(error, detail) {
  if (error) {
    document.getElementById("error").textContent = error;
  }
  if (detail) {
    document.getElementById("detail").textContent = detail;
  }
}

// Encrypt a file, from a passed in File object.
async function protect(file) {
  const body = document.getElementsByTagName("body")[0];
  try {
    body.className = "working";
    const encryptParams = new Virtru.EncryptParamsBuilder()
      .withArrayBufferSource(await file.arrayBuffer())
      .withDisplayFilename(file.name)
      .withHtmlFormat();
    // To allow other people to access it, add a Policy here using the PolicyBuilder.
    const ciphertextStream = await client.encrypt(encryptParams.build());
    const ciphertextString = await ciphertextStream.toString();
    const a = document.getElementById("download_link");
    a.download = file.name + ".tdf.html";
    a.href =
      "data:text/html;charset=utf-8," + encodeURIComponent(ciphertextString);
    body.className = "done";
  } catch (e) {
    console.warn({ type: "encrypt failure", cause: e });
    if (e && e.message) {
      setAlert("Encrypt service error", `${e.message}`);
    } else {
      setAlert(
        "Encrypt service error",
        "Try refreshing credentials or starting over."
      );
    }
    body.className = "error";
    return;
  }
}

async function dropHandler(e) {
  e.preventDefault();

  const edt = e.dataTransfer;
  if (edt.items) {
    // Use DataTransferItemList interface to access the file(s)
    // Not supported on IE or Safari.
    for (const item of edt.items) {
      if (item.kind === "file") {
        protect(item.getAsFile());
      }
    }
  } else {
    // Use DataTransfer interface to access the file(s)
    for (const file of edt.files) {
      protect(file);
    }
  }

  // Pass event to removeDragData for cleanup
  removeDragData(e);
}

function removeDragData(ev) {
  console.log("Removing drag data");

  if (ev.dataTransfer.items) {
    // Use DataTransferItemList interface to remove the drag data
    ev.dataTransfer.items.clear();
  } else {
    // Use DataTransfer interface to remove the drag data
    ev.dataTransfer.clearData();
  }
}

function afterAuth(email) {
  client = new Virtru.Client({ email });
  const body = document.getElementsByTagName("body")[0];
  body.className = "start";
}

const dropZone = document.getElementById("drop_zone");
dropZone.ondrop = dropHandler;
dropZone.ondragover = dragOverHandler;
Virtru.AuthWidget("auth", { afterAuth });
#input,
#auth {
  display: none;
}
#drop_zone {
  border: 4px dashed salmon;
  margin: 0em;
  padding: 2em;
  width: 200px;
  height: 100px;
}
#waiting {
  border: 8px double gray;
  margin: 0em;
  padding: 2em;
  width: 200px;
  height: 100px;
  display: none;
}
#download {
  border: 4px solid blue;
  margin: 0em;
  padding: 2em;
  width: 200px;
  height: 100px;
  display: none;
}
#error_box {
  background: black;
  border-radius: 4px;
  margin: 0em;
  padding: 2em;
  width: 200px;
  height: 100px;
  display: none;
  color: white;
}
.unauthorized #auth,
.start #input,
.working #waiting,
.done #download,
.error #error_box {
  display: block;
}

Did this page help you?