Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript: A very simple way to implement electronic signature

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...

like image 638
Norfeldt Avatar asked May 05 '17 13:05

Norfeldt


3 Answers

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:

  1. you need to create a public/private key pair.
  2. you need to sign your message with the private key on a system that is trustful. Only this system should have the private key.
  3. 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.

like image 156
Lux Avatar answered Oct 17 '22 16:10

Lux


Try node module XML Advanced Electronic Signatures.

It uses Web Crypto for cryptographic operations. Hence it can be used both in browsers and Node.js.

like image 3
Harikrishnan Avatar answered Oct 17 '22 17:10

Harikrishnan


That looks like a proper "digital signature" in the technical sense - e.g. using a private key to sign and a public key to verify the signature. The "qualified" signatures need to be issued by a qualified provider that is basically a trusted Certificate Authority. The keys for placing qualified signatures have to be issued on secure devices (smart cards and HSMs) so that nobody but the owner can have access to the private key.

But the legal distinction between advanced and qualified signatures isn't entirely clear - the Regulation explicitly states that non-qualified signatures also have legal value. Working with qualified signatures (with smartcards) in browsers is a horrifying user experience - in most cases, it goes through a Java Applet, which works basically just on Internet Explorer and a special build of Firefox nowadays. Alternatives include desktop software and local service JWS applications that handle the signing, but smartcards are a big issue and offtopic at the moment.

So, how do we allow users to "place" an electronic signature? I had an idea that this could be done entirely using the WebCrypto API that's more or less supported in browsers these days. The idea is as follows:

Let the user type in a password for the purpose of signing. Derive a key from the password (e.g. using PBKDF2). Sign the contents of the form that the user is submitting with the derived key. Store the signature alongside the rest of the form data. Optionally, store the derived key for verification purposes. Here's a JavaScript gist with an implementation of that flow.

Many of the pieces are taken from the very helpful webcrypto examples repo. The hex2buf, buf2hex, and str2ab functions are utilities (that sadly are not standard in JS).

What the code does is straightforward, even though it's a bit verbose. All the operations are chained using promises and "then," which, to be honest, is a bit tedious to write and read (but inevitable I guess): Class 2 Digital Signature , Class 3 Digital Signature

The password is loaded as a raw key (after transforming to an array buffer). A secret key is derived using PBKDF2 (with 100 iterations). The secret key is used to do an HMAC "signature" on the content filled in by the user. The signature and the key are stored (in the UI in this example). Then the signature can be verified using: the data, the signature, and the key.

like image 2
user10645488 Avatar answered Oct 17 '22 17:10

user10645488