JavaScript: A very simple way to implement electronic signature

javascript, node.js

Solution

Electronic signatures are by definition much more complex then a hash. While you can just generate a hash from a message, for a digital signature you usually want a private key and enforce that only someone who knows the private key can produce a valid signature. Next you obviously need the corresponding public key to verify the message.

So usually you have 3 steps for this:

- you need to create a public/private key pair.

- you need to sign your message with the private key on a system that is trustful. Only this system should have the private key.

- you verify the message with the public key, to ensure that the trustful system has signed it.

So the first interesting question is, how do you want to store/distribute your keys, and on what kind of systems do you want to sign/verify? It's a common use case that you sign and verify in different programming languages. However for now lets assume you want to do everything in JavaScript.

Also always remember a simple problem: If you can't be sure the message is from the valid sender, how can you ensure that the public key you are using to verify the message is from the valid sender? You could distribute it with your software, but for a website you have to trust your TLS connection for this, and if you trust your TLS connection you can also use it to transfer the message itself.

I think the best solution is to use the Web Cryptography API. Here you can find helpful examples.

First you need to generate your keys:

async function generateKey() {
  const key = await window.crypto.subtle.generateKey({
      name: "RSASSA-PKCS1-v1_5",
      modulusLength: 4096,
      publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
      hash: {
        name: "SHA-512"
      },
    },
    true,
    ["sign", "verify"]
  );

  return {
    privateKey: await window.crypto.subtle.exportKey(
      "jwk",
      key.privateKey,
    ),
    publicKey: await window.crypto.subtle.exportKey(
      "jwk",
      key.publicKey,
    ),
  };
}

`window.crypto.subtle.exportKey` gives you a JSON that you can transform back and forth to a simple string by `JSON.strinify`/`JSON.parse` so you can store it somewhere.

The next step is to sign your message. Notice that you probably want to do this at a different time then generating the keys.

async function sign(privateKeyJwk, message) {
  const privateKey = await window.crypto.subtle.importKey("jwk", privateKeyJwk, {
    name: "RSASSA-PKCS1-v1_5",
        hash: {name: "SHA-512"},
  }, false, ['sign']);
  const data = new TextEncoder().encode(message);

  const signature = await window.crypto.subtle.sign({
      name: "RSASSA-PKCS1-v1_5",
    },
    privateKey,
    data,
  );

  // converts the signature to a colon seperated string
  return new Uint8Array(signature).join(':');
}

this function takes the jwk of the private key we created in the first step and a simple string as a message to sign. It returns a signature as colon separated string. This works for now, even if Base64 for example would be more efficient.

Now the last step is to verify your message. You probably want to do this later on a different machine, where you only have the public key and a maybe corrupted message and you want to verify if the message was corrupted or not. Its absolutely essential that you can trust that the private key was not corrupted.

async function verify(publicKeyJwk, signatureStr, message) {
    const signatureArr = signatureStr.split(':').map(x => +x);
  const signature = new Uint8Array(signatureArr).buffer

  const publicKey = await window.crypto.subtle.importKey("jwk", publicKeyJwk, {
    name: "RSASSA-PKCS1-v1_5",
        hash: {name: "SHA-512"},
  }, false, ['verify']);
  const data = new TextEncoder().encode(message);

  const ok = await window.crypto.subtle.verify({
      name: "RSASSA-PKCS1-v1_5",
    },
    publicKey,
    signature,
    data
  );
  return ok;
}

for this you need the jwk of the public key that we created in the first step, the message used in the second step as a string and the signature created by the second step as the colon separated string.

This will result in a boolean indicating if the message is valid or not.

Problem

Is there a very simple way in JS to make an electronic signature that can be handled with as much ease as checksums (or hash)? So if this is the scenario: ``` ------------------------------------ Locked section for client ------------------------------------ | YYYY.MM.DD ......................| | ........... ......................| | Bla bla bla ......................| | Bla bla bla Bla bla bla..Bla bla .| | Bla bla bla Bla bla bla..Bla bla .| | Bla bla bla Bla bla bla..Bla bla .| | Bla bla bla Bla bla bla..Bla bla .| | Bla bla bla ......................| | Bla bla bla ......................| ------------------------------------ | HASH: HA2S2EM3CA12EDIAJED | ------------------------------------ "Open" comment textfield for clients ------------------------------------ | HE34ADOV2DSASA452123 ...(signer A)| | GHEAVOED12dHSAV2123J ...(signer B)| ``` `HE34ADOV2DSASA452123` is generated by a private key owned by the signer. Then the decryption (with some sort of public key) of `HE34ADOV2DSASA452123` would give something like `YYYY.MM.DD Bla bla bla` or return the hash (`HA2S2EM3CA12EDIAJED`) of the section. Likewise the decryption of `GHEAVOED12dHSAV2123J` would give something like `YYYY.MM.DD Bla bla bla` or return the hash (`HA2S2EM3CA12EDIAJED`) of the section. Note there is no requirement for this to be secured against evil master minds, just against "layman" fraud...

Original source